/*
 * DocroGen.cpp $Revision: 1.1.1.1 $  - MiX-edition
 *
 *  - create TestCase skeletons 
 *  for CppUnit 1.6.2 (http://sourceforge.net/projects/cppunit/)
 *
 * by 'episteme'
 *
 * [requirements]
 * - MiX 0.4.4 http://www.soup.jp/lib/MiX.html
 *
 * [build instruction]
 * - Windows / Microsoft Visual C++ 6.0:
 *     cl -GX -I%MiX%\src DocroGen.cpp %MiX%\projects\vc6\Release\libMiX.lib
 *     (where %MiX% : MiX root directory)
 *
 * [thanks to]
 * - SHIBUKAWA, Yoshiki : Creator of TestRunnerFactory.rb that inspires me.
 * - MORINO, Shin'ya    : Creator of TexyGen/SourceGen : generate XML for DocroGen
 * - KUBO, Yotaro       : Creator of MiX : light-weight XML parser
 */

#include <iostream> // cout, cerr, endl
#include <fstream>  // ifstream, ofstream
#include <string>   // string
#include <vector>   // vector
#include <iterator>

#include <MiX/MiX.h>

inline bool exists(const std::string& file) {
  std::ifstream strm(file.c_str());
  return strm.is_open();
}

void generate_suite(MiX::Element<char>& testSuite) {
  std::vector<std::string> includes;
  std::vector<std::string> testCases;
  std::vector<std::string> variables;
  std::vector<std::string> methods;
  std::vector<std::string>::iterator iter;
  MiX::NodeList<char>& children = testSuite.getChildren();
  for ( MiX::NodeList<char>::iterator it = children.begin();
        it != children.end(); ++it ) {
    MiX::Element<char>* child = dynamic_cast<MiX::Element<char>*>(*it);
    if ( child ) {
      std::string tagName = child->getName();
      std::string name = (*child)["name"].getValue();
      if ( tagName == "include" ) {
        if ( child->hasAttribute("system") && (*child)["system"].getValue() == "true" ) {
          includes.push_back('<' + name + '>');
        } else {
          includes.push_back('"' + name + '"');
        }
      } else if ( tagName == "TestCase" ) {
        testCases.push_back(name);
      } else if ( tagName == "variable" ) {
        variables.push_back((*child)["type"].getValue() + " " + name);
      } else if ( tagName == "method" ) {
        std::string args;
        if ( child->hasAttribute("args") ) {
          args = (*child)["args"].getValue();
        }          
        methods.push_back((*child)["type"].getValue() + " " + name +
                        "(" + args + ")");
      }
    }
  }
  std::string name = testSuite["name"].getValue();
  // header
  if ( !testSuite.hasAttribute("generate") || testSuite["generate"].getValue() != "false" ) {
    std::cerr << "create '" << name << ".h'" << std::endl;
    std::ofstream strm((name+".h").c_str());
    strm << "#ifndef " << name << "_h\n"
            "#define " << name << "_h\n"
            "\n"
            "#include <cppunit/TestCase.h>\n"
            "#include <cppunit/TestCaller.h>\n"
            "#include <cppunit/TestSuite.h>\n"
            "\n";
    for ( iter = includes.begin(); iter != includes.end(); ++iter ) {
      strm << "#include " << *iter << '\n';
    }
    strm << "\n"
            "class " << name << " : public CppUnit::TestCase {\n";
    for ( iter = variables.begin(); iter != variables.end(); ++iter ) {
      strm << "  " << *iter << ";\n";
    }
    for ( iter = methods.begin(); iter != methods.end(); ++iter ) {
      strm << "  " << *iter << ";\n";
    }
    strm << "public:\n";
    if ( testSuite.hasAttribute("setUp") && testSuite["setUp"].getValue() == "true" ) {
      strm << "  virtual void setUp();\n";
    }
    if ( testSuite.hasAttribute("tearDown") && testSuite["tearDown"].getValue() == "true" ) {
      strm << "  virtual void tearDown();\n";
    }
    for ( iter = testCases.begin(); iter != testCases.end(); ++iter ) {
      strm << "  void " << *iter << "();\n";
    }
    strm << "\n"
            "  static CppUnit::Test* suite();\n"
            "};\n"
            "\n"
            "#endif"
         << std::endl;
  }
  // suite
  {
    std::cerr << "create '" << name << "_suite.impl'\n";
    std::ofstream strm((name+"_suite.impl").c_str());
    strm << "CppUnit::Test* " << name << "::suite() {\n"
            "  typedef CppUnit::TestCaller<" << name << "> caller;\n"
            "  CppUnit::TestSuite* suite = new CppUnit::TestSuite(\"" << name << "\");\n";
      for ( iter = testCases.begin(); iter != testCases.end(); ++iter ) {
        strm << "  suite->addTest(new caller(\"" << *iter
             << "\", &" << name << "::" << *iter << "));\n";
      }
      strm << "  return suite;\n"
              "}\n";
  }    
  // implementation
  if ( !exists(name+".cpp") ) {
    std::cerr << "create '" << name << ".cpp'" << std::endl;
    std::ofstream strm((name+".cpp").c_str());
    strm << "#include \"" << name << ".h\"\n\n";
    if ( testSuite.hasAttribute("setUp") && testSuite["setUp"].getValue() == "true" ) {
       strm << "void " << name << "::setUp() {\n}\n\n";
    }
    if ( testSuite.hasAttribute("tearDown") && testSuite["tearDown"].getValue() == "true" ) {
       strm << "void " << name << "::tearDown() {\n}\n\n";
    }
    for ( iter = methods.begin(); iter != methods.end(); ++iter ) {
      std::string tmp = *iter;
      tmp.insert(tmp.find(' ')+1, name+"::");
      strm << tmp << "{\n}\n\n";
    }
    for ( iter = testCases.begin(); iter != testCases.end(); ++iter ) {
      strm << "void " << name << "::" << *iter << "() {\n"
              "  CPPUNIT_ASSERT_MESSAGE(\""
           << name << "::" << *iter << "() not implemented\",false);\n"
              "}\n\n";
    }
    strm << "#include \"" << name << "_suite.impl\"\n";
  }
}

void generate_runner(MiX::Element<char>& testRunner) {
  // parse
  std::vector<std::string> testSuites;
  MiX::NodeList<char>& children = testRunner.getChildren();
  for ( MiX::NodeList<char>::iterator it = children.begin();
        it != children.end(); ++it ) {
    MiX::Element<char>* testSuite = dynamic_cast<MiX::Element<char>*>(*it);
    if ( testSuite ) {
      testSuites.push_back((*testSuite)["name"].getValue());
      generate_suite(*testSuite);
    }
  }

  if ( !testRunner.hasAttribute("generate") || testRunner["generate"].getValue() != "false" ) {
    std::cerr << "create '" << testRunner["name"].getValue() << ".cpp'" << std::endl;
    std::ofstream strm( (testRunner["name"].getValue()+".cpp").c_str() );
    strm << "#include <iostream>\n"
            "#include <cppunit/TextTestResult.h>\n"
            "#include <cppunit/TestSuite.h>\n\n";
    std::vector<std::string>::iterator iter;
    for ( iter = testSuites.begin(); iter != testSuites.end(); ++iter ) {
      strm << "#include \"" << *iter << ".h\"\n";
    }
    strm << "\n#if 0\n";
    for ( iter = testSuites.begin(); iter != testSuites.end(); ++iter ) {
      strm << "#include \"" << *iter << "_suite.impl\"\n";
    }
    strm << "#endif\n"
            "\n"
            "int main() {\n"
            "  CppUnit::TestSuite suite;\n"
            "  CppUnit::TextTestResult result;\n";
    for ( iter = testSuites.begin(); iter != testSuites.end(); ++iter ) {
      strm << "  suite.addTest(" << *iter << "::suite());\n";
    }
    strm << "  suite.run(&result);\n"
            "  result.print(std::cout);\n"
            "  return 0;\n"
            "}\n";
  }
}

int main(int argc, char* argv[]) {
  std::cerr << "DocroGen $Revision: 1.1.1.1 $ MiX-version" << std::endl;

  if ( argc != 2 ) {
    std::cerr << "DocroGen <file('-' for stdin)>" << std::endl;
    return 1;
  }

  try {
    MiX::DOM_Parser<char> parser;
    parser.setIgnoreSpace(true);
    std::vector<char> data;
    if ( argv[1] == std::string("-") ) {
      std::copy(std::istreambuf_iterator<char>(std::cin),
                std::istreambuf_iterator<char>(),
                std::back_inserter(data));
    } else {
      std::ifstream source(argv[1]);
      std::copy(std::istreambuf_iterator<char>(source),
                std::istreambuf_iterator<char>(),
                std::back_inserter(data));
    }
    data.push_back('\0');
    MiX::Document<char>& document = parser.parse(&data[0]);
    MiX::Element<char>& root = document.getRoot();
    generate_runner(root);
  } catch ( std::exception& ex ) {
    std::cerr << ex.what() << std::endl;
  }
  return 0;
}
