/*! @file

    @id $Id$
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef EXCEPTIONS_HXX
#define EXCEPTIONS_HXX
#include <stdexcept>
#include <queue>
#include <QDir>

class Exception: public std::exception {
  public:
    Exception(QString w): _what(w) {}
    ~Exception() throw() {}
    const char* what() const throw() {
      static QByteArray bytes;
      bytes = _what.toUtf8();
      return bytes.data();
    }
    void prependFileLine(QString filename, int linenr) {
      if (filename.size()) {
        if (linenr>0)
          _what = "→ in file "+filename+" at line "+QString::number(linenr)+":\n→ "+_what;
        else
          _what = "→ in file "+filename+":\n→ "+_what;
      } else if (linenr>0)
        _what = "→ at line "+QString::number(linenr)+":\n→ "+_what;
    }
  protected:
    QString q(const QString& text) const {
      return '"'+text+'"';
    }
    QString p(const QString& text) const {
      return QString("\n"+text).replace("\n", "\n  ");
    }
    QString pq(const QString& text) const {
      return p(q(text));
    }
    QString pq(const QStringList& texts) const {
      return pq(texts.join("\"\n\""));
    }
  protected:
    QString _what;
};

class ParseError: public Exception {
  public:
    ParseError(QString w): Exception("parse error:"+p(w)) {}
};

class UnknownCommand: public ParseError {
  public:
    UnknownCommand(QString line): ParseError("unknown command:"+p(q(line))) {}
};

class BadArgument: public ParseError {
  public:
    BadArgument(QString arg): ParseError("bad argument:"+p(arg)) {}
    BadArgument(QString arg, QStringList args):
        ParseError("bad argument:"+p(arg)+p("→ arguments:"+pq(args))) {
    }
};

class MissingArguments: public ParseError {
  public:
    MissingArguments(QString args, QString req):
        ParseError("missing arguments:"
                   +p("requires: "+req)
                   +p("got: "+q(args))) {}
};

class MissingLine: public ParseError {
  public:
    MissingLine():
        ParseError("line expectet at end of file") {}
};

class TestFailed: public Exception {
  public:
    TestFailed(QString why): Exception("test failed:"+p(why)) {}
};

class PossibleRetryLoad: public TestFailed {
  public:
    PossibleRetryLoad(QString txt): TestFailed(txt) {}
};

class WrongSignal: public PossibleRetryLoad {
  public:
    WrongSignal(QString signal, QStringList args,
                std::pair<QString, QStringList> received,
                QStringList sigs):
        PossibleRetryLoad("wrong signal received:"
                          +p("expected: "+q(signal+" "+args.join(' ')))
                          +p("received: "+q(received.first+" "+received.second.join(' ')))
                          +p("queue:"+pq(sigs))) {
    }
};

class UnhandledSignals: public TestFailed {
  public:
    typedef std::pair<QString, QStringList> Signal;
    UnhandledSignals(std::queue<Signal> sigs):
        TestFailed("unhandled signals:") {
      while (!sigs.empty()) {
        Signal res(sigs.front());
        _what += q(res.first+" "+res.second.join(' '));
        sigs.pop();
      }
    }
};

class TimeOut: public PossibleRetryLoad {
  public:
    TimeOut(QString cmd): PossibleRetryLoad("command timeout:"+p(cmd)) {}
};

class ElementNotFound: public TestFailed {
  public:
    ElementNotFound(QString selector):
        TestFailed("element not found:"+pq(selector)) {}
};

class DirectoryCannotBeCreated: public TestFailed {
  public:
    DirectoryCannotBeCreated(QString name):
        TestFailed("cannot create directory:"+pq(name)) {}
};

class CannotWriteScreenshot: public TestFailed {
  public:
    CannotWriteScreenshot(QString name):
        TestFailed("cannot write screenshot:"+pq(name)) {}
};

class CannotWriteSouceHTML: public TestFailed {
  public:
    CannotWriteSouceHTML(QString name):
        TestFailed("cannot write html source code:"+pq(name)) {}
};

class FileNotFound: public TestFailed {
  public:
    FileNotFound(QString arg): TestFailed("file not found:"+pq(arg)) {}
};

class NotACertificate: public TestFailed {
  public:
    NotACertificate(QString arg):
        TestFailed("file is not a certificate:"+pq(arg)) {
    }
};

class KeyNotReadable: public TestFailed {
  public:
    KeyNotReadable(QString arg):
        TestFailed("key file is not readable (password?):"+pq(arg)) {
    }
};

class NotUnattended: public TestFailed {
  public:
    NotUnattended(): TestFailed("web page is not in unattended test mode") {}
};

class LastFileNotUploaded: public TestFailed {
  public:
    LastFileNotUploaded(QString arg):
        TestFailed("last specified upload file has not been uploaded:"+pq(arg)) {
    }
};

class EmptyUploadFile: public TestFailed {
  public:
    EmptyUploadFile(): TestFailed("specified upload file is empty string") {}
};

class NoUploadFile: public TestFailed {
  public:
    NoUploadFile(): TestFailed("no upload file specified") {}
};

class SetFileUploadFailed: public TestFailed {
  public:
    SetFileUploadFailed(QString selector, QString filename):
        TestFailed("set file upload failed for:"
                   +p("selector: "+q(selector))
                   +p("file: "+q(filename))) {
    }
};

class AssertionFailed: public TestFailed {
  public:
    AssertionFailed(QString text):
        TestFailed("assertion failed:"+pq(text)) {
    }
};

class DownloadFailed: public TestFailed {
  public:
    DownloadFailed(QString file):
        TestFailed("download failed of file:"+pq(file)) {
    }
};

class WriteFileFailed: public TestFailed {
  public:
    WriteFileFailed(QString file):
        TestFailed("write failed of file:"+pq(QDir(file).absolutePath())) {
    }
};

class ScriptFailed: public TestFailed {
  public:
    ScriptFailed(QString dsc, QString cmd, QStringList args, QString script):
        TestFailed(dsc
                   +p("command: "
                      +pq(cmd+(args.size()?" "+args.join(' '):QString())))
                   +(script.size()?p("script:"+p(script)):QString())) {
    }
};

class CannotStartScript: public ScriptFailed {
  public:
    CannotStartScript(QString command, QStringList args, QString script):
        ScriptFailed("command not found", command, args, script) {
    }
};

class CannotLoadScript: public ScriptFailed {
  public:
    CannotLoadScript(QString command, QStringList args, QString script):
        ScriptFailed("cannot load script data", command, args, script) {
    }
};

class ScriptNotFinished: public ScriptFailed {
  public:
    ScriptNotFinished(QString command, QStringList args, QString script):
        ScriptFailed("command hangs, timeout", command, args, script) {
    }
};

class ScriptExecutionFailed: public ScriptFailed {
  public:
    ScriptExecutionFailed(QString command, QStringList args, QString script,
                          int code, QString sout, QString serr):
        ScriptFailed("failed with exit code "+QString::number(code)
                     +(sout.size()?p("stdout: "+q(sout)):"")
                     +(serr.size()?p("stderr: "+q(serr)):""),
                     command, args, script) {
    }
};

class WrongNumberOfArguments: public TestFailed {
  public:
    WrongNumberOfArguments(QStringList vars, QStringList args):
        TestFailed(QString("function has %1 arguments, but %2 were given")
                   .arg(vars.size()).arg(args.size())) {
    }
};

class FunctionCallFailed: public TestFailed {
  public:
    FunctionCallFailed(QString name, QStringList vars, QStringList args,
                       const std::exception& x):
        TestFailed("function call failed:"+p(name)) {
      for (QStringList::iterator var(vars.begin()), arg(args.begin());
           var<vars.end() && arg<args.end(); ++var, ++arg)
        _what += " "+*var+"="+*arg;
      _what += p("reason:"+p(x.what()));
    }
};

class FunctionNotFound: public TestFailed {
  public:
    FunctionNotFound(QString name): TestFailed("function not found:"+pq(name)) {}
};

class VariableNotFound: public TestFailed {
  public:
    VariableNotFound(QString name):
        TestFailed("variable not found:"+pq(name)) {
    }
};

class CheckFailed: public TestFailed {
  public:
    CheckFailed(QString value1, char cmp, QString value2):
        TestFailed("check failed:"
                   +p(QString("%1 %2 %3")
                      .arg(value1).arg(cmp).arg(value2))) {
    }
};

class OpenIncludeFailed: public TestFailed {
  public:
    OpenIncludeFailed(QString file):
        TestFailed(QString("open include file %1 failed").arg(q(file))) {
    }
};

class ParseIncludeFailed: public TestFailed {
  public:
    ParseIncludeFailed(QString file, QString msg):
        TestFailed(QString("parse include file %1 failed with:%2").arg(q(file)).arg(p(msg))) {
    }
};

class ExecuteIncludeFailed: public TestFailed {
  public:
    ExecuteIncludeFailed(QString file, QString msg):
        TestFailed(QString("error in included file %1:%2").arg(file).arg(p(msg))) {
    }
};

#endif
