#include <commands.hxx>
#include <webpage.hxx>
#include <QApplication>
#include <QFile>
#include <QWebView>
#include <QStringList>
#include <QCommandLineParser>
#include <iostream>
#include <sstream>
#include <fstream>
#include <QDateTime>

#include <xml-cxx/xml.hxx>
#include <mrw/string.hxx>

#include <version.hxx>
using namespace NAMESPACE;

/** @page junitXml Test Output XML Format

    The test output XML format emulates JUnit's format, so that JUnit
    tools can be used to evaluate ist, namely on tools like jenkins.

    @code{.xml}
    <testsuites>
      <testsuite name="..." timestamp="..." failures="...">
        <testcase classname="..." name="...">
          <failure message="..." />
          <system-out>...</system-out>
          <system-err>...</system-err>
        </testcase>
        <testcase ... />
      </testsuite>
      <testsuite ...>
        ...
      </testsuite>
    <testsuites>
    @endcode

    The default name for a testsuite is the name of the test script
    file. it can be overwritten using the @c testsuite statement. The
    attribute @c failures contains the number of failed test cases.

    The default classname for a testcase is @c testsuite-preparation
    before the script runs, then in the script it is the name of the
    current command and the classname equals to the testsuite's name.

    If you specify testsuites, testcases in your script file, then
    this overwrites the defaults. Here @c testsuite maps to the
    testsuite's name, @c testcase maps to the next testcase's
    classname.
    
    */

QString format(QString txt, int indent = 2, int cpl = 80) {
  QStringList res;
  QStringList lines(txt.split('\n'));
  QString ind(indent, ' ');
  Q_FOREACH(QString line, lines) {
    line.insert(0, ind);
    for (int pos(indent); line.size()-pos>cpl; ++pos) {
      int pos2=line.lastIndexOf(' ', pos+cpl);
      if (pos2>pos) pos=pos2;
      else pos=line.indexOf(' ', pos+1);
      if (pos<0) break;
      line.remove(pos, 1);
      line.insert(pos, "\n"+ind);
    }
    res+=line;
  }
  return res.join('\n');
}
QString format(std::string txt, int indent = 2, int cpl = 60) {
  return format(QString::fromStdString(txt), indent, cpl);
}

QString help(const Script& s) {
  std::ostringstream ss;
  ss<<"Synopsis"<<std::endl
    <<std::endl
    <<"  webrunner [<OPTIONS>] [<file1> [<file2> [...]]]"<<std::endl
    <<std::endl
    <<"DESCRIPTION"<<std::endl
    <<std::endl
    <<format(description()).toStdString()<<std::endl
    <<std::endl
    <<format(readme()).toStdString()<<std::endl
    <<std::endl
    <<"SCRIPT SYNTAX"<<std::endl
    <<std::endl
    <<format(s.syntax()).toStdString()<<std::endl
    <<std::endl
    <<"SCRIPT COMMANDS"<<std::endl
    <<std::endl
    <<format(s.commands()).toStdString()<<std::endl;
  return QString::fromStdString(ss.str());
}

int main(int argc, char *argv[]) try {
  bool failed(false);
  QApplication a(argc, argv);
  QWebView p;
  p.setPage(new TestWebPage(&p, true));
  QCommandLineParser parser;
  Script script;
  parser.setApplicationDescription(help(script));
  parser.addHelpOption();
  parser.addOption(QCommandLineOption
                   (QStringList()<<"p"<<"path",
                    "search <path> within the test scripts", "path"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"x"<<"xml",
                    "store XML output in <xmlfile>", "xmlfile"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"r"<<"retries",
                    "on error retry up to <maxretries> times",
                    "maxretries", "0"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"timeout",
                    "set default timeout in seconds",
                    "timeout", "40"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"W"<<"width",
                    "set screenshot size to <width> pixel", "width", "2048"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"H"<<"height",
                    "set screenshot size to <height> pixel", "height", "2048"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"v"<<"version",
                    "show version information"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"s"<<"skipped",
                    "treat skipped test cases as failure in XML output file"));
  parser.addOption(QCommandLineOption
                   (QStringList()<<"t"<<"target-path",
                    "set screenshot target path to <path>", "path",
                    QDir().absolutePath()+QDir::separator()+"attachments"));
  parser.process(a);
  if (parser.isSet("version")) {
    std::cout<<*argv<<std::endl
             <<" from package "<<package_string()<<std::endl
             <<" by "<<author()<<std::endl
             <<" built on "<<build_date()<<std::endl;
    return 0;
  }
  if (parser.isSet("path")) script.path(parser.value("path"));
  int retries(parser.value("retries").toInt());
  int timeout(parser.value("timeout").toInt());
  int width(parser.value("width").toInt());
  int height(parser.value("height").toInt());
  script.defaultTimeout(parser.value("timeout").toInt());
  QString target(parser.value("target-path"));
  p.resize(width, height);
  std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
  Q_FOREACH(QString file, parser.positionalArguments()) {
    int expectedtestcases(-1);
    xml::Node testsuite("testsuite");
    testsuite.attr("name") = QFileInfo(file).baseName().toStdString();
    testsuite.attr("timestamp") =
      QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
    xml::Node testcase("testcase");
    testcase.attr("classname") = "testsuite-preparation";
    try {
      script.reset();
      script.log("=====================");
      script.log("TEST: "+file);
      script.log("---------------------");
      testcase.attr("name") = "open test suite file";
      testsuite<<testcase;
      QFile f(file);
      if (!f.open(QIODevice::ReadOnly))
        throw std::runtime_error("cannot open file "+file.toStdString());
      QString txt(QString::fromUtf8(f.readAll()));
      testcase.attr("name") = "parse test suite file";
      testsuite<<testcase;
      script.parse(txt.split('\n'), file);
      expectedtestcases = script.countSteps()+2;
      if (failed) {
        script.log("FAILED: "+file+" skipped due to previous error");
        testcase.attr("name") = "testsuite";
        xml::Node failure("failure");
        failure.attr("message") = "ignored due to previous failure";
        testsuite<<(testcase<<failure);
        testsuite.attr("failures") =
          mrw::string(expectedtestcases+1-testsuite.children());
        if (parser.isSet("skipped")) {
          testcase.attr("name") = "skipped test case";
          failure.attr("message") = "skipped due to previous failure";
          testcase<<failure;
          for (int i(testsuite.children()); i<expectedtestcases; ++i)
            testsuite<<testcase;
        }
      } else {
        testsuite.attr("failures") = "0";
        (*testsuites)<<testsuite;
        script.run(p.page()->mainFrame(), testsuites,
                   target+QDir::separator()+QFileInfo(file).baseName(),
                   true, retries);
        script.log("SUCCESS: "+file);
      }
    } catch (std::exception& e) {
      failed = true;
      script.log("FAILED: "+file+" with "+e.what());
      xml::Node failure("failure");
      failure.attr("message") = Script::xmlattr(e.what()).toStdString();
      testsuites->last().last()<<failure;
      if (expectedtestcases==-1)
        testsuites->last().attr("failures") = "1";
      else
        testsuites->last().attr("failures") =
          mrw::string(expectedtestcases+1-testsuites->last().children());
      if (parser.isSet("skipped")) {
        testcase.attr("name") = "skipped test case";
        failure.attr("message") = "skipped due to previous failure";
        testcase<<failure;
        for (int i(testsuites->last().children()); i<expectedtestcases; ++i)
          testsuites->last()<<testcase;
      }
    }
    if (expectedtestcases==-1) {
      testsuites->last().attr("tests") =
        mrw::string(testsuites->last().children());
    } else {
      testsuites->last().attr("tests") = mrw::string(expectedtestcases);
    }
    if (script.cout().size())
      testsuites->last()<<(xml::String("system-out") =
                           script.xmlattr(script.cout()).toStdString());
    if (script.cerr().size())
      testsuites->last()<<(xml::String("system-err") =
                           script.xmlattr(script.cerr()).toStdString());
  }
  if (parser.isSet("xml")) { // todo: write xml file
    std::ofstream xmlfile(parser.value("xml").toStdString());
    xmlfile<<(*testsuites)<<std::endl;
  }
  return failed ? 1 : 0;
 } catch (std::exception& e) {
  return 1;
 }
