#include "runscriptwidget.h"

#define _CONSOLE_INFO( x ) emit sendConsoleOut(QString() + x , ConsoleOut::Information)
#define _CONSOLE_VERBOSE( x ) emit sendConsoleOut(QString() + x , ConsoleOut::Verbose)
#define _CONSOLE_ERROR( x ) emit sendConsoleOut(QString() + x , ConsoleOut::Error)
#define _CONSOLE_FATAL( x ) emit sendConsoleOut(QString() + x , ConsoleOut::Fatal)

RunScriptWidget::RunScriptWidget(QWidget *parent=0) : QWidget(parent), ui(new Ui::RunScriptWidget) {
    ui->setupUi(this);
    this->argTableModel = new QStandardItemModel(0, 2, this);
    this->returnTableModel = new QStandardItemModel(0, 2, this);
    ui->argTableView->setModel(argTableModel);
    ui->returnTableView->setModel(returnTableModel);

    argMenu = new QMenu("Add Argument from Variables", this);
    ui->argTool->setMenu(argMenu);

    //For returns menu
    connect(returnTableModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(returnsChanged_helper()));

    Py_Initialize();

    //I think these need to be always defined.
    argv = (char**)malloc(sizeof(char*));
    *argv = (char*)malloc(sizeof(char)*128);
}

RunScriptWidget::~RunScriptWidget(){
    Py_Finalize();
    free(argv);  //Am I doing this right??
    free(*argv);
}

void RunScriptWidget::clear(){
    this->argTableModel->clear();
    this->returnTableModel->clear();
}

void RunScriptWidget::returnsChanged_helper(){

    //TODO:  Collect data from returns table and shove....
    _CONSOLE_VERBOSE("RunScriptWidget::returnsChanged_helper..");
    QStringList returnNames, returnValues;
    for (int r = 0; r < returnTableModel->rowCount(QModelIndex()); ++r){
            returnNames.append(returnTableModel->data( returnTableModel->index(r, 0, QModelIndex())  ).toString());
            returnValues.append(returnTableModel->data( returnTableModel->index(r, 1, QModelIndex())  ).toString());
    }
    emit returnsChanged(returnNames, returnValues);
}

void RunScriptWidget::loadScript(QString _scriptName, QString _scriptPath, QString _scriptDescription,
                                 QString _scriptRequire, QString _scriptOptional, QString _scriptReturn){


    this->scriptName = _scriptName;
    this->scriptPath = _scriptPath;
    this->scriptDescription = _scriptDescription;
    this->scriptRequire = _scriptRequire.split(",", QString::SkipEmptyParts);
    this->scriptOptional = _scriptOptional.split(",", QString::SkipEmptyParts);
    this->scriptReturn = _scriptReturn.split(",", QString::SkipEmptyParts);

    ui->scriptNameLabel->setText(scriptName);
    ui->scriptDescriptionLabel->setText(scriptDescription);

    this->argTableModel->clear();
    this->returnTableModel->clear();

    argTableModel->setColumnCount(2);
    argTableModel->setRowCount(scriptRequire.size() + scriptOptional.size());
    returnTableModel->setRowCount(scriptReturn.size());
    returnTableModel->setColumnCount(2);

    for (int r = 0 ;r < scriptRequire.size(); ++r){
        argTableModel->setItem(r,0, new QStandardItem(scriptRequire.at(r))); //TODO: ADD ICONS
        argTableModel->setData(argTableModel->index(r, 0, QModelIndex()),
                               QBrush(Qt::red, Qt::SolidPattern)
                               , Qt::BackgroundRole);
    }

    for (int r = 0; r < scriptOptional.size(); ++r){
        argTableModel->setItem(r+scriptRequire.size(),0,
                     new QStandardItem(scriptOptional.at(r)));   //Add Icons Here
    }

    for (int r = 0; r < scriptReturn.size(); ++r){
        returnTableModel->setItem(r,0, new QStandardItem(scriptReturn.at(r)));
    }


}

void RunScriptWidget::loadVariables(QStringList names, QStringList values){
    //Called whenever variables change.  Creates new menu
    //TODO: could be cleverer, only recreate the bit thats changed??
    //Is this the right way to do it? (memory wise)
    _CONSOLE_VERBOSE("Building Menu (run script widget");

    argMenu->clear();
    argMenuAction.clear();
    for (int i = 0; i < names.size(); ++i){
        argMenuAction.append(new QAction(names.at(i), this));
        argMenuAction.last()->setProperty("name", names.at(i));
        argMenuAction.last()->setProperty("value", values.at(i));
        connect(argMenuAction.last(), SIGNAL(triggered()), this, SLOT(argMenu_clicked()));
        argMenu->addAction(argMenuAction.last());
    }
}

void RunScriptWidget::argMenu_clicked(){
    _CONSOLE_VERBOSE("Clicked!");
    _CONSOLE_VERBOSE(sender()->property("name").toString());
    _CONSOLE_VERBOSE(sender()->property("value").toString());
    //replace current item value with that of the variable clicked on..

    if(ui->argTableView->currentIndex().isValid()){
        argTableModel->setItem(ui->argTableView->currentIndex().row(), 1,
                           new QStandardItem(sender()->property("value").toString()));
    }else{
        _CONSOLE_INFO("Arg Table View index not valid! (Have you selected a variable?)");
    }
}

void RunScriptWidget::runScript(){
    QStringList dictReturnKeys;
    QStringList dictReturnValues;
    QStringList dictCallKeys;
    QStringList dictCallValues;

    //Need to add optionals!
    dictCallKeys = scriptRequire;
    for(int i =0; i < scriptRequire.size(); ++i){
        dictCallValues << argTableModel->data( argTableModel->index(i, 1, QModelIndex()) ).toString();
    }

    QString a;
    for(int i = 0; i < scriptOptional.size();++i){
        a = argTableModel->data( argTableModel->index(i+scriptRequire.size(), 1,
                                 QModelIndex()), Qt::DisplayRole).toString();
        if(!a.isEmpty()){
            dictCallKeys << scriptOptional.at(i);
            dictCallValues << a;
        }
    }


    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;

    /*Create Dictionary to give to script*/
    PyObject *pCallDict = PyDict_New();
    PyObject *p;

    for(int i = 0; i < dictCallKeys.size(); ++i){
        p = PyString_FromString(dictCallValues.at(i).toAscii());
        if(PyDict_SetItemString(pCallDict, dictCallKeys.at(i).toAscii(), p)==-1){
            _CONSOLE_ERROR("PyDict_SetItemString fails..");
            Py_DECREF(p);
            Py_DECREF(pCallDict);
            return;
        }
        Py_DECREF(p);
    }

    //Tell python to look in scripts directory, this is quite a brute force method,
    //  literally just sets the argv stuff that is passed to the script..
    strcpy(*argv, (QString("/home/simon/URSS/scripts/")+scriptName+".py").toAscii());
    PySys_SetArgv(1, argv);


    pName = PyUnicode_FromString(scriptName.toAscii());  //python file, I think..
    if(!PyUnicode_Check(pName)){
        _CONSOLE_FATAL("weird: pName is not unicode....");
        Py_DECREF(pCallDict);
        Py_Finalize();
        return;
    }


    /* Error checking of pName left out */

    PyObject *pFuncname;
    pFuncname = PyUnicode_FromString(scriptName.toAscii());


    pModule = PyImport_Import(pName);
    Py_XDECREF(pName);   //used to be just D

    if (pModule != NULL) {

        PyObject *pSonifyException;
        if(PyObject_HasAttrString(pModule, "SonifyException")){
            pSonifyException = PyObject_GetAttrString(pModule, "SonifyException");
            if(pSonifyException==NULL){
                _CONSOLE_FATAL("call python: SonifyException not set, error checking will fail.");
            }
        }else{
            pSonifyException = NULL;
        }

        pFunc = PyObject_GetAttr(pModule, pFuncname);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(1);   //Arg list must be in a tuple
            PyTuple_SetItem(pArgs, 0, pCallDict);
            pValue = PyObject_CallObject(pFunc, pArgs);
            _CONSOLE_VERBOSE("Func Callable.");
            if (pValue != NULL) {   //pValue will be null if an uncaught exception is raised..
                PyObject *returnIterator = PyObject_GetIter(pValue);
                if(returnIterator!=NULL)
                {
                    PyObject *key, *value;
                    char *keyStr, *valueStr;

                    while ((key = PyIter_Next(returnIterator))) {
                        //I think item is a python string corresponding to key in dict
                        value = PyObject_GetItem(pValue, key);
                        if(value==NULL){_CONSOLE_ERROR("Value NULL");}

                        //Points to internal buffer, not modifiable
                        keyStr = PyString_AsString(key);
                        valueStr = PyString_AsString(value);
                        if(keyStr==NULL ){
                            _CONSOLE_ERROR("Key String NULL");
                        }else{
                            if(valueStr==NULL){
                                _CONSOLE_VERBOSE("Key: " + keyStr);
                                _CONSOLE_ERROR(QString::fromStdString("Value String NULL"));
                            }else{
                                _CONSOLE_VERBOSE("Key: " + keyStr);
                                _CONSOLE_VERBOSE("Value: "+ valueStr);
                                dictReturnKeys << keyStr;
                                dictReturnValues << valueStr;
                            }
                        }
                        Py_DECREF(key);
                        Py_DECREF(value);
                    }

                    Py_DECREF(returnIterator);
                }else{
                    _CONSOLE_FATAL("call python: Script didn't return an iterable object.");
                }
                Py_DECREF(pValue);
                Py_DECREF(pArgs);

            }
            else {
                PyObject *excType, *excValue, *excTrace;
                PyErr_Fetch(&excType, &excValue, &excTrace);
                if(excType != NULL){
                    if(pSonifyException){
                        if(PyErr_GivenExceptionMatches(excType, pSonifyException)){
                            _CONSOLE_FATAL("Script Threw Known Error (SonifyException)");
                        }else{
                            _CONSOLE_FATAL("Script Threw Unknown Error");
                        }
                    }
                    if(excValue!=NULL){
                        PyObject *excString = PyObject_Str(excValue);
                        _CONSOLE_FATAL("Exception Value: " + PyString_AsString(excString));
                        Py_DECREF(excString);
                    }else{
                        _CONSOLE_FATAL("No Exception Value");
                    }
                    Py_DECREF(excType);

                    if(excTrace!=NULL){
                        PyObject *excString = PyObject_Str(excTrace);
                        _CONSOLE_FATAL("Trace Value: " + PyString_AsString(excString));
                        Py_DECREF(excString);
                        Py_DECREF(excTrace);
                    }
                }

                if(excValue != NULL){Py_DECREF(excValue);}
                if(excType != NULL){Py_DECREF(excType);}

                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                Py_DECREF(pArgs);
                return;
            }
        }
        else {
            _CONSOLE_FATAL("call_python: Cannot find function");
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);

        if(pSonifyException!=NULL){Py_DECREF(pSonifyException);}
    }
    else {
        PyErr_Print();
        _CONSOLE_FATAL("call_python: Failed to load Module, syntax error in script?");
        return;
    }

    Py_DECREF(pCallDict);

    for (int i = 0; i < dictReturnKeys.size(); ++i){
        int pos = scriptReturn.indexOf(dictReturnKeys.at(i));
        if(pos == -1){
            _CONSOLE_ERROR("Key not specified in header: " + dictReturnKeys.at(i)
                           + ":" + dictReturnValues.at(i));
        }else{
            returnTableModel->setItem(pos, 1, new QStandardItem(dictReturnValues.at(i)));
        }
    }
}

void RunScriptWidget::on_runScriptButton_clicked()
{//need to check script loaded...
    if(scriptName.isEmpty()){
        _CONSOLE_ERROR("No Script Selected");
        return;
    }
    for (int i = 0; i < scriptRequire.size(); ++i){
        if(argTableModel->data(argTableModel->index(i, 1, QModelIndex()), Qt::DisplayRole).toString().isEmpty()){
            _CONSOLE_ERROR("Some Required Argument not given.");
            return;
        }
    }
    this->runScript();
}
