/* Copyright (c) 2015  Gerald Knizia
 * 
 * This file is part of the IboView program (see: http://www.iboview.org)
 * 
 * IboView is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 * 
 * IboView is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
 * 
 * Please see IboView documentation in README.txt for:
 * -- A list of included external software and their licenses. The included
 *    external software's copyright is not touched by this agreement.
 * -- Notes on re-distribution and contributions to/further development of
 *    the IboView software
 */

#include "Iv.h"
// #include <iostream>
// #include <fstream>
// #include <string>
// #include <sstream>

#include <QScriptEngine>
#include <QTextStream>
#include <QFile>

// #include <chaiscript/chaiscript.hpp>
// #include <chaiscript/dispatchkit/function_call.hpp>
// #include <boost/format.hpp>
// #include <boost/shared_ptr.hpp>
// using boost::format;

// boost::shared_ptr<chaiscript::ChaiScript> create_chaiscript_object();

#include "IvScript.h"
#include "CxColor.h"
// #include "cppformat/format.h"
// #include "CtIo.h"

IApplication::~IApplication()
{}

IView3d::~IView3d()
{}

// void IView3d::set_option(QString const &OptionName, bool f) {
//    if (f)
//       set_option(OptionName, int(1));
//    else
//       set_option(OptionName, int(-1));
// }
// void IView3d::set_option(QString const &OptionName, int i) {
//    set_option(OptionName, double(i));
// }


// // will load an entire file into the stringstream str. Returns false if failed.
// bool LoadFileIntoMemory( std::string &sFileContent,
//         std::string const &FileName, unsigned *pFileLength )
// {
//     // read the entire file into an stringstream object.
//     std::ifstream
//         File( FileName.c_str() );
//     std::size_t
//         FileLength;
//     if ( false == File.good() )
//         return false;
//     File.seekg( 0, std::ios::end );
//     FileLength = File.tellg();
//     if ( 0 != pFileLength )
//         *pFileLength = FileLength;
//     sFileContent.clear();
//     sFileContent.resize(FileLength, 0);
//     File.seekg( 0, std::ios::beg );
//     File.read( &sFileContent[0], FileLength );
// //     pFileContent.resize(2 + FileLength);
// //     memset( &pFileContent[0], 0, 2 + FileLength );
// //     File.seekg( 0, std::ios::beg );
// //     File.read( &pFileContent[0], FileLength );
//     return true;
// };

uint32_t hsv_uint(float h, float s, float v) {
   return 0xff000000 | (uint32_t)ct::Hsv(h,s,v).uint32();
//    return 0xff000000 + (uint32_t)ct::FColor(h,s,v);
//    return 0xfffffffe;
//    return (uint32_t)ct::Hsv(h,s,v);
}

uint32_t ColFromAlpha(float a);

uint32_t hsva_uint(float h, float s, float v, float a) {
   return ColFromAlpha(a) | (uint32_t)ct::Hsv(h,s,v).uint32();
//    return 0xff000000 + (uint32_t)ct::FColor(h,s,v);
//    return 0xfffffffe;
//    return (uint32_t)ct::Hsv(h,s,v);
}

// static std::string s_format_i(std::string const &Format, int val) {
//    std::stringstream str;
//    str << boost::format(Format) % val;
//    return str.str();
// }
//
// static std::string s_format_f(std::string const &Format, double val) {
//    std::stringstream str;
//    str << boost::format(Format) % val;
//    return str.str();
// }
//
// static std::string s_format_s(std::string const &Format, std::string const &val) {
//    std::stringstream str;
//    str << boost::format(Format) % val;
//    return str.str();
// }

static QString s_format_i(QString const &Format, int val) {
   QString r;
   r.sprintf(Format.toLatin1().constData(), val);
   return r;
}

static QString s_format_f(QString const &Format, double val) {
   QString r;
   r.sprintf(Format.toLatin1().constData(), val);
   return r;
}

static QString s_format_s(QString const &Format, QString const &val) {
   QString r;
   r.sprintf(Format.toLatin1().constData(), val.toUtf8().constData());
   return r;
}



static QScriptValue s_Invoke_format(QScriptContext *context, QScriptEngine * /*engine*/)
{
   int
      iArg0,
      nArgs = context->argumentCount();
   QString
      s;
//    if (context->thisObject().isUndefined()) {
   if (!context->thisObject().isString()) {
      // called as stand-alone function. Take first argument as base string.
      // (the 'thisObject' is still defined: it is returned as "[object global]".)
      s = qscriptvalue_cast<QString>(context->argument(0));
//       IvEmit("!! s_Invoke_format: Taking free-standing version. s = '%1'", s);
      iArg0 = 1;
      nArgs -= 1;
   } else {
      // take "this" as base string and rest as arguments.
      s = qscriptvalue_cast<QString>(context->thisObject());
//       IvEmit("!! s_Invoke_format: Taking object version. s = '%1'", s);
      iArg0 = 0;
   }
//    for (int i = iArg0; i < nArgs; ++ i)
//       s = s.arg(qscriptvalue_cast<QString>(context->argument(i)));
#if 1
   // can only do basic replacements.
   for (int i = 0; i < nArgs; ++ i) {
      s.replace(QString("{%1}").arg(i), qscriptvalue_cast<QString>(context->argument(iArg0+i)));
   }
#else
   fmt::Writer w;
   fmt::BasicFormatter<char> const &cstr = w.Format(s.toStdString());
   fmt::BasicFormatter<char> &str = const_cast<fmt::BasicFormatter<char>&>(cstr);

   for (int i = 0; i < nArgs; ++ i) {
      QScriptValue arg = context->argument(iArg0+i);
      if (arg.isNumber()) {
         qsreal q = arg.toNumber();
         // ECMA script does not distinguish between floats and integers...
         if (q == long(q))
            str << long(q);
         else
            str << q;
      } else {
         str << arg.toString().toStdString();
      }
      s = QString::fromStdString(w.str());
   }
#endif
   return QScriptValue(s);
}


#define FARG(FType,iArg) qscriptvalue_cast<FType>(context->argument((iArg)))

//     QScriptValue callee = context->callee();
//      if (context->argumentCount() == 1) // writing?
//          callee.setProperty("value", context->argument(0));
//      return callee.property("value");
//    return QScriptValue(hsva_uint(context->argument(0).toUInt32()));
static QScriptValue s_Invoke_hsva(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(hsva_uint(FARG(float, 0), FARG(float, 1), FARG(float,2), FARG(float,3)));
}

// static QScriptValue s_Invoke_fmti(QScriptContext *context, QScriptEngine * /*engine*/) {
//    return QScriptValue(QString::fromStdString(s_format_i(FARG(QString,0).toStdString(), FARG(int,1))));
// }
// static QScriptValue s_Invoke_fmtf(QScriptContext *context, QScriptEngine * /*engine*/) {
//    return QScriptValue(QString::fromStdString(s_format_f(FARG(QString,0).toStdString(), FARG(double,1))));
// }
//
// static QScriptValue s_Invoke_fmts(QScriptContext *context, QScriptEngine * /*engine*/) {
//    return QScriptValue(QString::fromStdString(s_format_s(FARG(QString,0).toStdString(), FARG(QString,1).toStdString())));
// }
//
// ^- note: we can probably do this much better now with QtScript...

static QScriptValue s_Invoke_fmti(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(s_format_i(FARG(QString,0), FARG(int,1)));
}

static QScriptValue s_Invoke_fmtf(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(s_format_f(FARG(QString,0), FARG(double,1)));
}

static QScriptValue s_Invoke_fmts(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(s_format_s(FARG(QString,0), FARG(QString,1)));
}

static QScriptValue s_Invoke_irgb(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(ct::irgb(FARG(quint32,0)));
}

static QScriptValue s_Invoke_replace_ext(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(ReplaceExt(FARG(QString,0), FARG(QString,1)));
}

static QScriptValue s_Invoke_remove_path(QScriptContext *context, QScriptEngine * /*engine*/) {
   return QScriptValue(RemovePath(FARG(QString,0)));
}


#undef FARG

static void AddScriptObject(QScriptEngine &ScriptEngine, QObject *pObject, QString const &Name)
{
   QScriptValue
//       ObjectValue = ScriptEngine.newQObject(pObject, QScriptEngine::QtOwnership, QScriptEngine::ExcludeChildObjects | QScriptEngine::ExcludeSuperClassContents);
//       ObjectValue = ScriptEngine.newQObject(pObject, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents);
      ObjectValue = ScriptEngine.newQObject(pObject, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassMethods);
   ScriptEngine.globalObject().setProperty(Name, ObjectValue);
}

static void AddScriptFunction(QScriptEngine &ScriptEngine, QScriptEngine::FunctionSignature pFn, QString const &Name)
{
   QScriptValue
      FunctionValue = ScriptEngine.newFunction(pFn);
   ScriptEngine.globalObject().setProperty(Name, FunctionValue);
}

void ExecScript(IApplication *app, IView3d *view, QString const &ScriptText, QString const &ScriptName) {
//    std::cout << QString("--- exec script '%1'").arg(ScriptName).toStdString() << std::endl;
   IvEmit("--- exec script '%1'", ScriptName);
   QScriptEngine
      ScriptEngine;

   AddScriptObject(ScriptEngine, app, "app");
   // FIXME: added IApplication as both "app" and "doc" until things get sorted out.
   AddScriptObject(ScriptEngine, app, "doc");
   AddScriptObject(ScriptEngine, view, "view");
   AddScriptFunction(ScriptEngine, s_Invoke_hsva, "hsva");
   AddScriptFunction(ScriptEngine, s_Invoke_irgb, "irgb");
   AddScriptFunction(ScriptEngine, s_Invoke_fmti, "fmti");
   AddScriptFunction(ScriptEngine, s_Invoke_fmtf, "fmtf");
   AddScriptFunction(ScriptEngine, s_Invoke_fmts, "fmts");
   AddScriptFunction(ScriptEngine, s_Invoke_format, "format");
   AddScriptFunction(ScriptEngine, s_Invoke_replace_ext, "replace_ext"); // replace a file extension by another file extension
   AddScriptFunction(ScriptEngine, s_Invoke_remove_path, "remove_path"); // strip off path from a file name

   // add 'format' also to prototype of standard class 'String'.
   // This should then allow for "blabla {0} blabla {1}".format("wheee","whooo")-type calls.
   QScriptValue
      FormatFunction = ScriptEngine.newFunction(s_Invoke_format);
   QScriptValue
      StringProto = ScriptEngine.globalObject().property("String").property("prototype");
   StringProto.setProperty("format", FormatFunction);

//    std::string sInputScript;
//    LoadFileIntoMemory(sInputScript, FileName.toStdString(), 0);

   QScriptValue
//       res = ScriptEngine.evaluate(QString::fromStdString(sInputScript));
      res = ScriptEngine.evaluate(ScriptText);
   if (ScriptEngine.hasUncaughtException()) {
      int iErrorLine = ScriptEngine.uncaughtExceptionLineNumber();
//       std::cerr << QString("\n!ERROR during execution of script:\n%1:%2: %3\n").arg(FileName, QString::number(iErrorLine), res.toString()).toStdString() << std::endl;
      IvNotify(NOTIFY_Error, "Error during script execution:\n" + QString("%1:%2: %3\n").arg(ScriptName, QString::number(iErrorLine), res.toString()));
   }
   IvEmit("--- script terminated.");
}

QString LoadTextFileViaQt(QString const &FileName)
{
   QFile
      File(FileName);
   if (!File.open(QIODevice::ReadOnly | QIODevice::Text)) {
//       std::cerr << "!ERROR: Failed to open script file '%s'" % FileName << std::endl;
      IvNotify(NOTIFY_Error, QString("Failed to open script file '%1'").arg(FileName));
      return "";
   }
   QTextStream
      Stream(&File);
   Stream.setAutoDetectUnicode(true);
   // ^- deals with UTF-16 and UTF-32. I guess it will default to UTF8?
   //    I didn't quite get this in the docs.
   QString
      Result = Stream.readAll();
   File.close();
   return Result;
}


// void ExecScript(FMainWindow *ui, std::string FileName) {
void ExecScript(IApplication *app, IView3d *view, QString const &FileName)
{
//    IvEmit("--- exec script '%1'", FileName);
   QString
      ScriptText = LoadTextFileViaQt(FileName);
   ExecScript(app, view, ScriptText, FileName);
}
