#include "qgpythonsandbox.h"

#include <QLibrary>
#include <QDir>
#include <QSettings>

#include <QMessageBox>
#include <QDebug>
#include <QCoreApplication>

#include "qgpythoninterface.h"

//typedef QWidget* (*qgp_create_console_t)(QWidget*);

typedef QgPythonInterface* (*qgp_instance_t)(void);

class QDialog;

class StubPython: public QgPythonInterface
{
public:
    class StubWidgets: public QgPythonWidgets
    {
    public:
        virtual QWidget* createConsole(QWidget*) override
        {
            return 0;
        }
        virtual QDialog* createEditDialog(QWidget*) override
        {
            return 0;
        }

    };

    virtual QgPythonWidgets* pythonWidgets()
    {
        static StubWidgets stub;
        return &stub;
    }

    virtual PythonQtObjectPtr* mainModule() override
    {
        return 0;
    }

    virtual PythonQtObjectPtr* createModule(const QString& /*module_name*/) override
    {
        return 0;
    }

    virtual PythonQtObjectPtr* createModuleFromScript(const QString& , const QString& ) override
    {
        return 0;
    }

    virtual PythonQtObjectPtr* createModuleFromFile(const QString& , const QString& ) override
    {
        return 0;
    }

    virtual bool addObject(const QString& , QObject*, PythonQtObjectPtr* /*module*/) override
    {
        return 0;
    }

    virtual bool evalScript(const QString& , PythonQtObjectPtr* ) override
    {
        return 0;
    }

    virtual bool evalFile(const QString& , PythonQtObjectPtr* ) override
    {
        return 0;
    }

};

class SandboxContext
{
public:
    StubPython stubs;

    bool enable;
    QWidget* consoleWidget;
    QDialog* editDialog;

    QgPythonInterface* pyif;
    PythonQtObjectPtr* appModule;
    qgp_instance_t create_instance;

    QString qglib_name;
    QString qglib_version;
    QString qglib_dbg;

    QString pyhome;
    QString pylib;

    QStringList pSymb;

    SandboxContext():
        enable(true),
        consoleWidget(0),
        editDialog(0),
        pyif(&stubs),
        appModule(0),
        qglib_name("QgPython"),
        qglib_version("36"),
#if !defined(QT_NO_DEBUG)
        qglib_dbg("_d")
#else
        qglib_dbg("")
#endif

    {
    }

    void py_sethome(const QString s)
    {
        pyhome=QDir::toNativeSeparators(s);
        qputenv("PYTHONHOME", s.toLatin1().constData());
        qDebug()<<"PYENV:"<<"PYTHONHOME"<<"="<<s;
        qDebug()<<"LIBPATH:"<<QCoreApplication::libraryPaths();
    }

    void py_setenv(const char* vname, const QString s)
    {
        if (s.isEmpty())
        {
            return;
        }

        static const QString pattern("${pythonhome}");

        QString value=s;

        QStringList tmp=s.split(';');
        if (tmp.size()>1)
        {
            value=tmp.at(0);
            value.replace(pattern, pyhome, Qt::CaseInsensitive);
            value=QDir::toNativeSeparators(value);

            for(int i=1; i<tmp.size(); ++i)
            {
                QString ns=tmp.at(i);
                ns.replace(pattern, pyhome, Qt::CaseInsensitive);
                value+=';';
                value+=QDir::toNativeSeparators(ns);
            }
        }
        else
        {
            value.replace(pattern, pyhome, Qt::CaseInsensitive);
            value=QDir::toNativeSeparators(value);
        }
        qputenv(vname, value.toLatin1().constData());
        qDebug()<<"PYENV:"<<vname<<"="<<value;
    }
};

static SandboxContext sbcx;

typedef const char* (*qgp_create_t)(unsigned int flags);

class SandboxInitializer
{
public:
    bool success;

    SandboxInitializer():
        success(false)
    {
        success=init();
    }

    bool init()
    {
       if (!sbcx.enable)
           return 0;

       unsigned int flags=0;
       QString pydll=sbcx.qglib_name;
       pydll=pydll+sbcx.qglib_version+sbcx.qglib_dbg;
       QLibrary pyl(pydll);
       bool ok=pyl.load();
       if (!ok)
       {
           QMessageBox::warning(0, "QgPython", QString("Load FAILED: %1").arg(pyl.errorString()));
           qWarning()<<"QgPython:"<<"Load FAILED -"<<pyl.errorString();
           sbcx.enable=false;
           return false;
       }

       qDebug()<<"QgPython: loaded"<<pyl.fileName();

       QgPythonLib_Initialize_function_t qgp_create=(qgp_create_t)pyl.resolve(QGPYTHON_LIB_INITIALIZE_FUNCTION_NAME); //"qgp_create");
       if (!qgp_create)
       {
           QMessageBox::warning(0, "QgPython", QString("FAIL: Cannot resolve: %1").arg(pyl.errorString()));
           qWarning()<<"QgPython:"<<"FAIL: Cannot resolve -"<<pyl.errorString();
           sbcx.enable=false;
           return false;
       }
       const char* msg=qgp_create(flags);
       if (msg)
       {
           QMessageBox::warning(0, "QgPython", QString("FAIL: %1").arg(pyl.errorString()));
           qWarning()<<"QgPython:"<<"FAIL:"<<pyl.errorString();
           sbcx.enable=false;
           return false;
       }
       sbcx.create_instance=(QgPythonLib_Instance_function_t)pyl.resolve(QGPYHON_LIB_INSTANCE_FUNCTION_NAME); //"qgp_instance");
       if (!sbcx.create_instance)
       {
           QMessageBox::warning(0, "QgPython", QString("FAIL: Cannot resolve: %1").arg(pyl.errorString()));
           qWarning()<<"QgPython:"<<"FAIL: Cannot resolve -"<<pyl.errorString();
           sbcx.enable=false;
           return false;
       }
       sbcx.pyif=sbcx.create_instance();

       sbcx.appModule=sbcx.pyif->mainModule();

       return true;
    }
};

QgPythonSandbox::QgPythonSandbox(QObject *parent) : QObject(parent)
{
  initialize();
}

void QgPythonSandbox::loadSettings(const QString &file)
{
    QString iniFile=file.isEmpty() ? "./qgpy.ini" : file;

    QSettings s(iniFile, QSettings::IniFormat);

    s.beginGroup("qgpy");

    if (s.value("enable").isValid())
    {
        bool enable=s.value("enable").toBool();
        if (!enable)
        {
            sbcx.enable=false;
            qDebug()<<"QgPy Setting: Python disabled";
        }
    }

    qDebug()<<"QgPy Settings:"<<s.fileName();

    QString tmp=s.value("pythonhome").toString(); //env "PYTHONHOME"
    if (!tmp.isEmpty())
        setPythonHome(tmp);

    tmp=s.value("qgpydll").toString();
    if (!tmp.isEmpty())
        sbcx.qglib_name=tmp;

    tmp=s.value("qgpyver").toString();
    if (!tmp.isEmpty())
        sbcx.qglib_version=tmp;

    tmp=s.value("pythonlib").toString();  //env "PYTHONLIB"
    if (!tmp.isEmpty())
        setPythonLib(tmp);

    tmp=s.value("path").toString(); //append to env "PATH"
    if (!tmp.isEmpty())
        addPythonPath(tmp);

    tmp=s.value("pythonpath").toString(); //append to env "PATH"
    if (!tmp.isEmpty())
    {
        sbcx.py_setenv("PYTHONPATH", tmp); //SandboxContext::setEnvPath("PYTHONPATH", tmp, false);
    }
    s.endGroup();
}

void QgPythonSandbox::setPythonVersion(const QString& v)
{
    sbcx.qglib_version=v;
}

void QgPythonSandbox::setPythonDll(const QString &name)
{
    sbcx.qglib_name=name;
}

void QgPythonSandbox::setPythonHome(const QString& home, bool precedence)
{
    //SandboxContext::setEnvPath("PYTHONHOME", home, precedence);
    //sbcx.pyhome=home;
    sbcx.py_sethome(home);
}

void QgPythonSandbox::setPythonLib(const QString& home, bool precedence)
{
   //SandboxContext::setEnvPath("PYTHONLIB", sbcx.expandPath(home), precedence);
    sbcx.py_setenv("PYTHONLIB", home);
}

void QgPythonSandbox::addPythonPath(const QString& path)
{
    if (path.isEmpty())
        return;
    auto e=qgetenv("PATH");
    QString syspath=QString::fromLatin1(e.data());
    syspath=path+";"+syspath;
    sbcx.py_setenv("PATH", syspath);
#if 0
    QString nativePath=QDir::toNativeSeparators(sbcx.expandPath(path));
    syspath=nativePath+";"+syspath;
    qputenv("PATH", syspath.toLatin1().constData());
    qDebug()<<"PATH:"<<syspath;
#endif
}

void QgPythonSandbox::enable(bool ena)
{
   sbcx.enable=ena;
}

bool QgPythonSandbox::isEnabled()
{
   return sbcx.enable;
}

bool QgPythonSandbox::initialize()
{
    static SandboxInitializer sbinit;
    return sbinit.success;
}

QgPythonInterface* QgPythonSandbox::pythonInterface()
{
    return sbcx.pyif;
}

QgPythonWidgets* QgPythonSandbox::pyhtonStdWidgets()
{
    return sbcx.pyif->pythonWidgets();
}

QWidget* QgPythonSandbox::createConsoleWidget(QWidget* parent)
{
    if (sbcx.consoleWidget)
        return sbcx.consoleWidget;
    if (!initialize())
        return 0;
    sbcx.consoleWidget=sbcx.pyif->pythonWidgets()->createConsole(parent); //create_console(parent);
    return sbcx.consoleWidget;
}

QDialog* QgPythonSandbox::createEditDialog(QWidget* parent)
{
    if (sbcx.editDialog)
        return sbcx.editDialog;
    if (!initialize())
        return 0;
    sbcx.editDialog=sbcx.pyif->pythonWidgets()->createEditDialog(parent); //create_console(parent);
    return sbcx.editDialog;
}


void QgPythonSandbox::addObject(const QString& name, QObject* obj)
{
    sbcx.pyif->addObject(name, obj, sbcx.appModule);
}

void QgPythonSandbox::addAppObject(const QString& name, QObject* obj)
{
    sbcx.pyif->addObject(name, obj, sbcx.appModule);
}

//PythonQtObjectPtr QgPython::createMainApplicationModule(const QString& module_name)
bool QgPythonSandbox::createModule(const QString& module_name, const QString& file)
{
    sbcx.pyif->createModuleFromFile(module_name, file);
    return true;
}

bool QgPythonSandbox::createAppModule(const QString& module_name, const QString& file)
{
    sbcx.appModule=sbcx.pyif->createModuleFromFile(module_name, file);
    return true;
}

void QgPythonSandbox::evalScript(const QString& script)
{
    sbcx.pyif->evalScript(script, 0);
}

void QgPythonSandbox::evalFile(const QString& file)
{
    sbcx.pyif->evalFile(file, 0);
}
