//  Copyright (2010-2013) Cédric Coussinet (cedric.coussinet@nomoseed.net)
//
//  This program 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, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program 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 more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see <http://www.gnu.org/licenses/>

#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebElement>
#include <QtGui/QColor>
#include <QtWebKit/QWebHistory>
#include <QDir>
#include <QProcess>
#include <QDirIterator>
#include <QLibrary>

#include <libxml/xinclude.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/HTMLparser.h>

#include <libexslt/exslt.h>

#include "mainwindow.h"
#include "editor.h"

#include <QDateTime>

nomoProjectType toProjectType(const QString extension){
    if (extension == "prg" || extension == ".prg")
        return prg;
    else if (extension == "mod" || extension == ".mod")
        return mod;
    else if (extension == "map" || extension == ".map")
        return map;
    else if (extension == "pts" || extension == ".pts")
        return pts;
    else if (extension == "eng" || extension == ".eng")
        return eng;
    else if (extension == "bas" || extension == ".bas")
        return bas;
    else if (extension == "uni" || extension == ".uni")
        return uni;
    else if (extension == "lnk" || extension == ".lnk")
        return lnk;
    else if (extension == "cod" || extension == ".cod")
        return cod;
    else if (extension == "fr" || extension == ".fr")
        return fr;
    else if (extension == "pr" || extension == ".pr")
        return pr;
    else if (extension == "sql" || extension == ".sql")
        return sql;
    else
        return error;
}

QString toQString(const nomoProjectType projectType){
    QString result(".error");
    switch(projectType)
    {
        case prg:{result = ".prg";}break;
        case mod:{result = ".mod";}break;
        case eng:{result = ".eng";}break;
        case bas:{result = ".bas";}break;
        case uni:{result = ".uni";}break;
        case map:{result = ".map";}break;
        case pts:{result = ".pts";}break;
        case cod:{result = ".cod";}break;
        case lnk:{result = ".lnk";}break;
        case pr:{result = ".pr";}break;
        case fr:{result = ".fr";}break;
        case sql:{result = ".sql";}break;
        default:break;
    }
    return result;
}

QString toName(const nomoProjectType projectType){
    QString result("error");
    switch(projectType)
    {
        case prg:{result = "program";}break;
        case mod:{result = "model";}break;
        case eng:{result = "inference_engine";}break;
        case bas:{result = "knowledge_base";}break;
        case uni:{result = "unit";}break;
        default:break;
    }
    return result;
}

Editor::Editor(QMainWindow * mainWindow, QWebView * webView){
    this->mainWindow = mainWindow;
    this->webView = webView;
    QWebFrame *webFrame = webView->page()->mainFrame();
    webFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
    webFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
    connect(webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(populateJavaScriptWindowObject()));
    QByteArray url(QDir::currentPath().toUtf8());
    url.append("/web/editor/editor.html");
    this->webView->load (QUrl::fromLocalFile(url));
    webView->load(QUrl::fromLocalFile(url));
    navigation.importStylesheet("web/editor/navigation.xsl");
    navigation.setNewParameter("number",0);
    navigation.setNewParameter("modified",0);
    navigation.setNewParameter("plant", tr("plant"));
    css.importStylesheet("web/editor/css.xsl");
    xmlDocPtr categories_colors = xmlReadFile("categories_colors.xml", "UTF-8", 0);
    XSLTProcessor::transform("transformations/categories_colors.xsl", categories_colors, "web/editor/categories.css", NULL);
    xmlFreeDoc (categories_colors);
    XSLTProcessor * workspace = new XSLTProcessor();
    workspace->importStylesheet(QByteArray("web/editor/htmltonomo.xsl"));
    listWorkspaces.append(workspace);
    workspace = new XSLTProcessor();
    workspace->importStylesheet(QByteArray("web/editor/nomotohtml.xsl"));
    listWorkspaces.append(workspace);
    this->webView->history()->setMaximumItemCount(0);
    languageInterface = "";
    extensionInterface = "";
    cache = "local";
    updateGrammarSDK("formalism");
    updateGrammarSDK("macro");
}

Editor::~Editor (){
    for (int i=0;i<listWorkspaces.length();i++)
        delete listWorkspaces[i];
    for (int i=0;i<listElements.length();i++)
        xmlFreeDoc(listElements[i].doc);
}

void Editor::updateGrammarSDK(const QByteArray name){
    QString xmlList= "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?><" + name + "s xmlns=\"http://www.nomoseed.org/nomosdk\">";
    QDirIterator dir(name + "s", QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
    QString temp;
    while(dir.hasNext()){
        temp = dir.next().split('/').last();
        xmlList.append("<"+name+" name=\""+temp.replace(" ","_")+"\"/>");
    }
    xmlList.append("</"+name+"s>");
    const QByteArray out = XSLTProcessor::transform(QByteArray("transformations/sdk-"+name+".xsl"), xmlList, NULL);
    QFile grammar("grammars/sdk-"+name+".xsd");
    grammar.open(QFile::WriteOnly);
    grammar.write(out);
    grammar.close();
}

QString Editor::open (const QString path, const nomoProjectType projectType) {
    const QDir codeDir(path);
    QString result = "";
    element item;
    item.path = path;
    item.type = toQString(projectType)[1];
    item.modified = "";
    item.name = codeDir.dirName().split(".").first();
    for (int i=0;i<listElements.length();i++)
        if(QDir(listElements[i].path) == codeDir)
            result = tr("Document is already open." );
        else if (listElements[i].name == item.name && listElements[i].type == item.type)
            result = tr("Document of same name is already open." );
    if (result != "")
        return result;
    QStringList arguments;
    arguments << "-parse";
    arguments << path;
    QProcess nomotools;
    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
    nomotools.waitForFinished(-1);
    result = nomotools.readAllStandardError ();
    if(result == ""){
        xmlDocPtr doc = xmlReadFile(path.toUtf8().data(),"UTF-8",0);
        xmlNodePtr root = xmlDocGetRootElement(doc);
        xmlChar* nameRoot = xmlGetProp(root,(const xmlChar*) "name");
        if (strcmp((const char *) nameRoot, item.name.toUtf8().data())){
            xmlSetProp(root,(const xmlChar*) "name", (const xmlChar*) item.name.toUtf8().data());
            webView->page()->mainFrame()->evaluateJavaScript("alert('"+tr("The name file must have the name of the project, so it has been modified.")+"')");
        }
        xmlFree(nameRoot);
        xmlXPathInit();
        xmlXPathContextPtr ctxt = xmlXPathNewContext(doc);
        xmlXPathRegisterNs(ctxt, (const xmlChar*)"sdk",(const xmlChar*)"http://www.nomoseed.org/sdk");
        xmlXPathObjectPtr xpathRes = xmlXPathEvalExpression((const xmlChar*)"//@ihm", ctxt);
        const QString ihm = "";// QString((char*) xmlXPathCastToString(xpathRes));
        xmlXPathFreeObject(xpathRes);
        xmlXPathFreeContext(ctxt);
        QString workspacePath;
        if (projectType == prg){
            ctxt = xmlXPathNewContext(doc);
            xmlXPathRegisterNs(ctxt, (const xmlChar*)"sdk",(const xmlChar*)"http://www.nomoseed.org/sdk");
            xpathRes = xmlXPathEvalExpression((const xmlChar*)"name(//sdk:formalism/*)", ctxt);
            const QString formalismName = QString((char*) xmlXPathCastToString(xpathRes));
            xmlXPathFreeObject(xpathRes);
            xmlXPathFreeContext(ctxt);
            if (formalismName.isEmpty())
                if (ihm.isEmpty())
                    workspacePath = "web/editor/nomotohtml.xsl";
                else{
                    workspacePath = "web/editor/"+ ihm +"/nomotohtml.xsl";
                    xmlXIncludeProcess (doc);
                }
            else
                if (ihm.isEmpty())
                    workspacePath = "web/editor/nomotohtml.xsl";
                else{
                    workspacePath = "formalisms/" + formalismName +"/"+ ihm + "/nomotohtml.xsl";
                    xmlXIncludeProcess (doc);
                }
        }
        else
            if (ihm.isEmpty())
                workspacePath = "web/editor/nomotohtml.xsl";
            else{
                workspacePath = "web/editor/"+ ihm +"/nomotohtml.xsl";
                xmlXIncludeProcess (doc);

            }
        bool isPresent = false;
        for(int i=0;i<listWorkspaces.length();i++)
            if (listWorkspaces[i]->isEgal(workspacePath))
                isPresent = true;
        if (!isPresent && QFile::exists(workspacePath)){
            XSLTProcessor * workspace = new XSLTProcessor;
            workspace->importStylesheet(workspacePath.toUtf8());
            listWorkspaces.append(workspace);
            item.toXML = listWorkspaces.last();
            workspace = new XSLTProcessor;
            workspace->importStylesheet(workspacePath.replace("htmltonomo.xsl","nomotohtml.xsl").toUtf8());
            listWorkspaces.append(workspace);
            item.toHTML = listWorkspaces.last();
        }
        else{
            item.toXML = listWorkspaces[0];
            item.toHTML = listWorkspaces[1];
        }
        item.doc = doc;
        if (listElements.length()>0)
            webView->page()->mainFrame()->evaluateJavaScript("if(colorWidget.isActive()){colorWidget.close();}document.getElementById('workspace').setAttribute('id','w"+QString::number(current)+"');");
        bool isFind = false;
        current = 0;
        while (current <listElements.length() && !isFind){
            if (listElements[current].name == ""){
                isFind =true;
                listElements[current] = item;
            }
            else
                current++;
        }
        if (!isFind)
            listElements.append(item);
        navigation.setParameter(0, current);
        navigation.setParameter(1, "");
        xmlChar* outNavigation = navigation.transformToFragment(doc);
        QString resultNavigation((const char*) outNavigation);
        webView->page()->mainFrame()->documentElement().findFirst("#tree").firstChild().appendInside(resultNavigation);
        xmlFree(outNavigation);
        xmlChar * outWorkspace = item.toHTML->transformToFragment(doc);
        QString resultWorkspace = QString::fromUtf8 ((const char*) outWorkspace, (int) strlen((const char*) outWorkspace));
        webView->page()->mainFrame()->documentElement().findFirst("body").appendInside(resultWorkspace.toUtf8());
        xmlFree(outWorkspace);
        webView->page()->mainFrame()->evaluateJavaScript("nomoOpenWorkspace("+QString::number(current)+",'"+item.type+"');");
        updateCSS();
    }
    return result;
}

QString Editor::parse(const QString xml, const QString isRef){
    if (isRef == "false")
        listElements[current].modified = "*";
    else
        listElements[current].modified = "";
    QString path = listElements[current].path;
    QString local = cache+path.mid(path.length()-4,4);
    QFile flocal(local);
    flocal.open(QFile::WriteOnly);
    flocal.write(xml.toUtf8());
    flocal.close();
    QStringList arguments;
    arguments << "-parse";
    arguments << local;
    QProcess nomotools;
    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
    nomotools.waitForFinished(-1);
    QString result = nomotools.readAllStandardError ();
    if (!result.isEmpty()) {
        QDir dir(path);
        return result.replace("^","</p><p>").replace(local,dir.dirName());
    }
    else
        return "*" + tr("Document Valid.");
}

QString Editor::saveAll(){
    QString result;
    const QStringList list = webView->page()->mainFrame()->evaluateJavaScript("nomoGetSelected();").toString().split('-',QString::SkipEmptyParts);
    if (list.isEmpty())
        result = save(current);
    else
        for (int i=0;i<list.length();i++)
            result.append(save(list[i].toInt()));
    return result;
}

QString Editor::link(){
    QString result = saveAll();
    if (!result.isEmpty())
        result.replace("^","</p><p>");
    else {
        if (listElements[current].type == "p"){
            result = link_program();
            updateCSS();
        }
        else if (listElements[current].type == "m"){
            result = link_model();
            updateCSS();
        }
        else if (listElements[current].type == "u")
            result =  link_unit();
        else
            result = tr("Intern error");
    }
    return result;
}

QString Editor::link_unit(){
    QStringList arguments;
    arguments << "-link";
    arguments << listElements[current].path;
    QProcess nomotools;
    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
    nomotools.waitForFinished(-1);
    QString result = nomotools.readAllStandardError ();
    if (!result.isEmpty()) {
        result.replace("^","</p><p>");
    }
    else {
        QString flink = listElements[current].path;
        flink.chop(3);
        flink = flink + "lnk";
        arguments.clear();
        arguments << "-parse";
        arguments << flink;
        nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
        nomotools.waitForFinished(-1);
        result = nomotools.readAllStandardError ();
        if (!result.isEmpty()) {
            result.replace("^","</p><p>");
        }
        else{
            arguments.clear();
            arguments << "-parse";
            arguments << flink;
            nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
            nomotools.waitForFinished(-1);
            result = nomotools.readAllStandardError ();
            if (!result.isEmpty()) {
                result.replace("^","</p><p>");
            }
            else{
                arguments.clear();
                arguments << "-encode";
                arguments << flink;
                nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                nomotools.waitForFinished(-1);
                result = nomotools.readAllStandardError ();
                if (!result.isEmpty()) {
                    result.replace("^","</p><p>");
                }
                else{
                    QFile::remove(flink);
                    flink.chop(3);
                    flink = flink + "cod";
                    arguments.clear();
                    arguments << "-parse";
                    arguments << flink;
                    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                    nomotools.waitForFinished(-1);
                    result = nomotools.readAllStandardError ();
                    if (!result.isEmpty()) {
                        result.replace("^","</p><p>");
                    }
                    else{
                        arguments.clear();
                        arguments << "-parse";
                        arguments << flink;
                        nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                        nomotools.waitForFinished(-1);
                        result = nomotools.readAllStandardError ();
                        if (!result.isEmpty()) {
                            result.replace("^","</p><p>");
                        }
                        else{
                            arguments.clear();
                            arguments << "-compile";
                            arguments << flink;
                            nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                            nomotools.waitForFinished(-1);
                            result = nomotools.readAllStandardError ();
                            if (!result.isEmpty()) {
                                result.replace("^","</p><p>");
                            }
                            else if (!languageInterface.isEmpty()){
                                arguments.clear();
                                arguments << "-interface";
                                arguments << languageInterface;
                                arguments << extensionInterface;
                                arguments << flink;
                                nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                                nomotools.waitForFinished(-1);
                                result = nomotools.readAllStandardError ();
                                if (!result.isEmpty()) {
                                    result.replace("^","</p><p>");
                                }
                                else{
                                    arguments.clear();
                                    arguments << "-db";
                                    arguments << QFileInfo (listElements[current].path).absoluteDir().absolutePath()+QDir::separator();
                                    arguments << flink;
                                    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                                    nomotools.waitForFinished(-1);
                                    result = "*" + tr("Linking, compilation and interfacing successful.");
                                    QFile::remove(flink);
                                }
                            }
                            else{
                                arguments.clear();
                                arguments << "-db";
                                arguments << QFileInfo (listElements[current].path).absoluteDir().absolutePath()+QDir::separator();
                                arguments << flink;
                                nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                                nomotools.waitForFinished(-1);
                                result = "*" + tr("Linking and compilation successful.");
                                QFile::remove(flink);
                            }

                        }
                    }
                }
            }
        }
    }
    if (!result.isEmpty()) {
        result.replace("^","</p><p>");
    }
    return result;
}

QString Editor::link_model(){
    QStringList arguments;
    arguments << "-link";
    arguments << listElements[current].path;
    QProcess nomotools;
    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
    nomotools.waitForFinished(-1);
    QString result = nomotools.readAllStandardError ();
    if (!result.isEmpty()) {
        result.replace("^","</p><p>");
    }
    else {
        QString flink = listElements[current].path;
        flink.chop(3);
        flink = flink + "lnk";
        arguments.clear();
        arguments << "-parse";
        arguments << flink;
        nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
        nomotools.waitForFinished(-1);
        result = nomotools.readAllStandardError ();
        if (!result.isEmpty()) {
            result.replace("^","</p><p>");
        }
        else{
            result = "*" + tr("Linking successful.");
            QFile::remove(flink);
        }
    }
    if (!result.isEmpty()) {
        result.replace("^","</p><p>");
    }
    return result;
}

QString Editor::link_program(){
    xmlDocPtr doc = xmlReadFile(listElements[current].path.toUtf8().data(),"UTF-8",0);
    xmlXPathInit();
    xmlXPathContextPtr ctxt = xmlXPathNewContext(doc);
    xmlXPathRegisterNs(ctxt, (const xmlChar*)"sdk", (const xmlChar*)"http://www.nomoseed.org/sdk");
    xmlXPathObjectPtr xpathResFormalism = xmlXPathEvalExpression((const xmlChar*)"name(/*/*/sdk:formalism/*)", ctxt);
    const QString formalismName = QString((char*) xmlXPathCastToString(xpathResFormalism));
    xmlXPathFreeObject(xpathResFormalism);
    QString result;
    if (!formalismName.isEmpty()) {
        QLibrary libformalism;
        libformalism.setFileName("formalisms/"+formalismName+"/"+formalismName);
        if(libformalism.load()) {
            typedef void (*formalismActualizePrototype)(const char* file);
            formalismActualizePrototype formalismActualize;
            formalismActualize = (formalismActualizePrototype) libformalism.resolve("formalismActualize");
            if (!formalismActualize)
                result = libformalism.errorString().toUtf8().data();
            else{
                formalismActualize(listElements[current].path.toUtf8().data());
                QFile f(listElements[current].path);
                f.open(QFile::ReadWrite);
                QString text(f.readAll().replace("><",">\n<"));
                f.reset();
                f.write(text.toUtf8());
                f.close();
                webView->page()->mainFrame()->documentElement().findFirst("#workspace .code").setInnerXml(text);
                f.setFileName("formalism.log");
                f.open(QFile::ReadOnly);
                result = f.readAll();
                result.replace("\n","</p><p>");
                f.close();
            }
            libformalism.unload();
        }
        else
            result =  libformalism.errorString().toUtf8().data();
        if (result.isEmpty()) {
            QStringList arguments;
            arguments << "-link";
            arguments << listElements[current].path;
            QProcess nomotools;
            nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
            nomotools.waitForFinished(-1);
            result = nomotools.readAllStandardError ();
            if (!result.isEmpty()) {
                result.replace("^","</p><p>");
            }
            else {
                QString flink = listElements[current].path;
                flink.chop(3);
                flink = flink + "lnk";
                arguments.clear();
                arguments << "-parse";
                arguments << flink;
                nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                nomotools.waitForFinished(-1);
                result = nomotools.readAllStandardError ();
                if (!result.isEmpty()) {
                    result.replace("^","</p><p>");
                }
                else{
                    result = "*" + tr("Linking successful.");
                    QFile::remove(flink);
                }
            }
        }
    }
    else {
        QStringList arguments;
        arguments << "-link";
        arguments << listElements[current].path;
        QProcess nomotools;
        nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
        nomotools.waitForFinished(-1);
        result = nomotools.readAllStandardError ();
        if (!result.isEmpty()) {
            result.replace("^","</p><p>");
        }
        else {
            QString flink = listElements[current].path;
            flink.chop(3);
            flink = flink + "lnk";
            arguments.clear();
            arguments << "-parse";
            arguments << flink;
            nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
            nomotools.waitForFinished(-1);
            result = nomotools.readAllStandardError ();
            if (!result.isEmpty()) {
                result.replace("^","</p><p>");
            }
            else{
                QFile::remove(flink);
            }
        }
        if (result.isEmpty()){
            xmlDocPtr xidoc = xmlCopyDoc(doc, 1);
            xmlXIncludeProcess(xidoc);
            const QString local = cache+".prg";
            xmlSaveFileEnc(local.toUtf8().data(), xidoc, "UTF-8");
            xmlFreeDoc (xidoc);
            xmlXPathObjectPtr xpathResMacros = xmlXPathEvalExpression((const xmlChar*)"/*/*/sdk:macro/*[not(local-name()='template') and not(local-name()='csv')]", ctxt);
            if (xmlXPathCastToBoolean(xpathResMacros)){
                QLibrary libmacro;
                typedef void (*macroActualizePrototype)(const char* file);
                macroActualizePrototype macroActualize;
                QStringList macrosName;
                for (int i=0; i < xpathResMacros->nodesetval->nodeNr; i++)
                    macrosName.append(QString((char*) xpathResMacros->nodesetval->nodeTab[i]->name));
                macrosName.removeDuplicates();
                QFile f("macro.log");
                for (int i=0; i < macrosName.length(); i++){
                    if (result.isEmpty()){
                        libmacro.setFileName("macros/"+macrosName[i]+"/"+macrosName[i]);
                        if(libmacro.load()) {
                            macroActualize = (macroActualizePrototype) libmacro.resolve("macroActualize");
                            if (!macroActualize)
                                result = libmacro.errorString().toUtf8().data();
                            else{
                                macroActualize(local.toUtf8().data());
                                f.open(QFile::ReadOnly);
                                result = f.readAll();
                                result.replace("\n","</p><p>");
                                f.close();
                            }
                            libmacro.unload();
                        }
                        else
                            result =  libmacro.errorString().toUtf8().data();
                    }
                }
            }
            xmlXPathFreeObject(xpathResMacros);
            if (result.isEmpty()) {
                xmlDocPtr temp1 = xmlReadFile(local.toUtf8().data(),"UTF-8",0);
                exsltStrRegister ();
                xmlXPathContextPtr ctxtTemp = xmlXPathNewContext(temp1);
                exsltStrXpathCtxtRegister(ctxtTemp, (const xmlChar *) "str");
                xmlXPathRegisterNs(ctxtTemp, (const xmlChar*)"program",(const xmlChar*)"http://www.nomoseed.org/program");
                xmlXPathObjectPtr xpathResSchemeRoot = xmlXPathEvalExpression((const xmlChar*)"/program:program/program:body/program:scheme", ctxtTemp);
                if(!xmlXPathCastToBoolean(xpathResSchemeRoot)){
                    xmlXPathObjectPtr xpathResBody = xmlXPathEvalExpression((const xmlChar*)"/program:program/program:body", ctxtTemp);
                    if(xmlXPathCastToBoolean(xpathResBody)){
                        xmlNsPtr ns = xmlCopyNamespace(xmlSearchNsByHref(doc, xmlDocGetRootElement(doc), (const xmlChar*)"http://www.nomoseed.org/program"));
                        xmlNodePtr scheme = xmlNewNode(ns, (const xmlChar*) "scheme");
                        xmlAddChild(xpathResBody->nodesetval->nodeTab[0],scheme);
                    }
                    xmlXPathFreeObject(xpathResBody);
                }
                xmlXPathFreeObject(xpathResSchemeRoot);
                xsltStylesheetPtr xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/template.xsl");
                xmlDocPtr temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
                xmlXPathFreeContext(ctxtTemp);
                xsltFreeStylesheet(xsl);
                xmlFreeDoc(temp1);
                xmlSaveFileEnc(local.toUtf8().data(), temp2 ,"UTF-8");
                QStringList arguments;
                arguments << "-link";
                arguments << local;
                QProcess nomotools;
                nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                nomotools.waitForFinished(-1);
                result = nomotools.readAllStandardError ();
                if (!result.isEmpty()) {
                    result.replace("^","</p><p>");
                }
                else {
                    arguments.clear();
                    arguments << "-parse";
                    arguments << cache + ".lnk";
                    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
                    nomotools.waitForFinished(-1);
                    result = nomotools.readAllStandardError();
                    if (!result.isEmpty()) {
                        QDir dir(listElements[current].path);
                        result.replace("^","</p><p>").replace(local,dir.dirName());
                    }
                }
                if (!result.isEmpty())
                    result = tr("A macro generates invalid rules with the following errors:") + "</p><p>" + result;
                else{
                    ctxtTemp = xmlXPathNewContext(temp2);
                    xmlXPathRegisterNs(ctxtTemp, (const xmlChar*)"program",(const xmlChar*)"http://www.nomoseed.org/program");
                    xmlXPathObjectPtr xpathResTemp = xmlXPathEvalExpression((const xmlChar*)"/program:program/program:body/program:scheme", ctxtTemp);
                    xmlXPathRegisterNs(ctxt, (const xmlChar*)"program",(const xmlChar*)"http://www.nomoseed.org/program");
                    xmlXPathObjectPtr xpathResSchemeRoot = xmlXPathEvalExpression((const xmlChar*)"/program:program/program:body/program:scheme", ctxt);
                    if(!xmlXPathCastToBoolean(xpathResSchemeRoot)){
                        xmlXPathObjectPtr xpathResBody = xmlXPathEvalExpression((const xmlChar*)"/program:program/program:body", ctxt);
                        if(xmlXPathCastToBoolean(xpathResBody)){
                            xmlNodePtr scheme = xpathResTemp->nodesetval->nodeTab[0];
                            xmlUnlinkNode(scheme);
                            xmlAddChild(xpathResBody->nodesetval->nodeTab[0],scheme);
                        }
                        xmlXPathFreeObject(xpathResBody);
                    }
                    else
                        xmlFreeNode(xmlReplaceNode(xpathResSchemeRoot->nodesetval->nodeTab[0], xmlCopyNode(xpathResTemp->nodesetval->nodeTab[0],1)));
                    xmlSaveFileEnc(listElements[current].path.toUtf8().data(), doc, "UTF-8");
                    xmlXPathFreeObject(xpathResTemp);
                    xmlXPathFreeObject(xpathResSchemeRoot);
                    xmlXPathFreeContext(ctxtTemp);
                    QFile f(listElements[current].path);
                    f.open(QFile::ReadWrite);
                    QString text(f.readAll().replace("><",">\n<"));
                    f.reset();
                    f.write(text.toUtf8());
                    f.close();
                    webView->page()->mainFrame()->documentElement().findFirst("#workspace .code").setInnerXml(text);
                    result = "*" + tr("Linking successful.");
                }
                xmlFreeDoc(temp2);
            }
            QFile::remove (local);
        }
    }
    xmlXPathFreeContext(ctxt);
    xmlFreeDoc(doc);
    return result;
}

xmlNodePtr xmlGetNode (xmlNodePtr parent, const char* name) {
    for (xmlNodePtr n=parent->last;n;n=n->prev)
        if((n->type != XML_CDATA_SECTION_NODE) && (n->type != XML_TEXT_NODE))
            if (!strcmp((const char*)n->name, name))
                return n;
    return NULL;
}

QString Editor::save(const int number){
    QString path = listElements[number].path;
    QString local = cache+path.mid(path.length()-4,4);
    xmlNodePtr root = xmlDocGetRootElement(listElements[number].doc);
    xmlNodePtr header = xmlGetNode(root, "header");
    const QString newContent = QDateTime::currentDateTimeUtc ().toString(Qt::ISODate);
    QString oldContent = "";
    if (header){
        xmlNodePtr datetime = xmlGetNode(header, "datetime");
        if (datetime){
            oldContent = (const char*) xmlNodeGetContent(datetime);
            xmlNodeSetContent(datetime, (xmlChar*) newContent.toUtf8().data());
        }
    }
    xmlSaveFileEnc(local.toUtf8().data(), listElements[number].doc, "UTF-8");
    QStringList arguments;
    arguments << "-parse";
    arguments << local;
    QProcess nomotools;
    nomotools.start(toolsName, arguments, QIODevice::ReadOnly);
    nomotools.waitForFinished(-1);
    QString result(nomotools.readAllStandardError());
    QFile::remove(local);
    if (!result.isEmpty()) {
        QDir dir(path);
        return result.replace("^","</p><p>").replace(local,dir.dirName());
    }
    else {
        xmlSaveFileEnc(path.toUtf8().data(),listElements[number].doc, "UTF-8");
        listElements[number].modified = "";
        webView->page()->mainFrame()->evaluateJavaScript("nomoWorkspaces["+QString::number(number)+"].isSaved('"+newContent+"','"+oldContent+"');");
        navigation.setParameter(0, number);
        navigation.setParameter(1, listElements[number].modified);
        xmlChar* outNavigation = navigation.transformToFragment(listElements[number].doc);
        const QString resultNavigation((const char*) outNavigation);
        QWebElement e = webView->page()->mainFrame()->documentElement().findFirst("#" + listElements[number].type + QString::number(number));
        e.replace(resultNavigation);
        webView->page()->mainFrame()->evaluateJavaScript("nomoNavigation.update();");
        xmlFree(outNavigation);
        return result;
    }
}

bool Editor::updateCSS(){
    xmlDocPtr copy = xmlCopyDoc(listElements[current].doc, 1);
    xmlXIncludeProcess(copy);
    xmlDocPtr result = css.transform(copy);
    if (xmlDocGetRootElement(result)->children){
        QStringList list = QString((const char*) xmlDocGetRootElement(result)->children[0].content).simplified().remove(" ").split("|", QString::SkipEmptyParts);
        webView->page()->mainFrame()->evaluateJavaScript("nomoUpdateCSS('"+list.join("|") + "|" + listElements[current].type + QString::number(current)+"');");
        xmlFreeDoc(result);
        return true;
    }
    xmlFreeDoc(copy);
    return false;
}

void Editor::update(const QString data){
    xmlDocPtr doc = xmlReadMemory(data.toUtf8().data(), data.toUtf8().size(),listElements[current].path.toUtf8().data(),"UTF-8",0);
    navigation.setParameter(0, current);
    navigation.setParameter(1, listElements[current].modified);
    xmlChar* outNavigation = navigation.transformToFragment(doc);
    const QString resultNavigation((const char*) outNavigation);
    QWebElement e = webView->page()->mainFrame()->documentElement().findFirst("#" + listElements[current].type + QString::number(current));
    e.replace(resultNavigation);
    webView->page()->mainFrame()->evaluateJavaScript("nomoNavigation.update();");
    const QString newName = e.findFirst("a").toInnerXml().split("ins>").last().split("<").first();
    listElements[current].path.replace(listElements[current].name + ".", newName + ".");
    listElements[current].name = newName;
    xmlFreeDoc(listElements[current].doc);
    listElements[current].doc = doc;
    xmlFree(outNavigation);
}

void Editor::close(){
    for (int i=0; i<listElements.length(); i++)
        webView->page()->mainFrame()->evaluateJavaScript("nomoClose();");
    for (int i=0; i<listElements.length(); i++)
        if (listElements[i].name != "")
            xmlFreeDoc(listElements[i].doc);
    listElements.clear();
    webView->reload();
}

void Editor::populateJavaScriptWindowObject(){
    webView->page()->mainFrame()->addToJavaScriptWindowObject("nomoSDK", this);
}

QString Editor::toHSL(const int r, const int g, const int b ){
    const QColor hsl = QColor::fromRgb(r,g,b).toHsl();
    return QString::number(hsl.hsvHue())+","+QString::number(hsl.hslSaturation()/2.55)+","+QString::number(hsl.lightness()/2.55);
}

void Editor::activeColor(const QString name){
    QString code = name;
    if (!code.contains(".i_"))
         code.replace(".t_"," @type='").replace(".m_"," @model='").replace(".c_"," @category='");
    else
        code.remove(QRegExp("\\.m_[^\\.]+")).replace(".t_"," @type='").replace(".i_"," @model='").replace(".c_"," @category='");
    QStringList attributes = code.split(' ');
    attributes.removeFirst();
    attributes.removeFirst();
    xmlXPathInit();
    xmlXPathContextPtr ctxt = xmlXPathNewContext(listElements[current].doc);
    xmlXPathRegisterNs(ctxt, (const xmlChar*)"sdk",(const xmlChar*)"http://www.nomoseed.org/sdk");
    xmlXPathObjectPtr xpathRes = xmlXPathEvalExpression((const xmlChar*)QString("//sdk:color["+attributes.join("' and ")+"']").toUtf8().data(), ctxt);
    color = xpathRes->nodesetval->nodeTab[0];
    xmlXPathFreeObject(xpathRes);
    xmlXPathFreeContext(ctxt);
}

void Editor::setColor(const QString hue, const QString saturation){
    xmlSetProp(color,(const xmlChar*)"hue",(const xmlChar*) hue.toUtf8().data());
    xmlSetProp(color,(const xmlChar*)"saturation",(const xmlChar*) saturation.toUtf8().data());
}

void Editor::updateText(){
    xmlChar* xmlOut;
    int len;
    xmlDocDumpMemoryEnc (listElements[current].doc, &xmlOut, &len, "UTF-8");
    webView->page()->mainFrame()->documentElement().findFirst("#workspace .code").setInnerXml(QString ((const char*) xmlOut));
    xmlFree(xmlOut);
}

void Editor::setCurrentWorkspace(const QString number){
    current = number.toInt();
}

QString Editor::getCurrentWorkspace(){
    return QString::number(current);
}

QString Editor::isModified(const QString number){
    if (listElements[number.toInt()].modified == "*")
        return tr("Save the last valid version of file '") + listElements[number.toInt()].path + "' ?";
    else
        return "";
}

QString Editor::closeWorkspace(){
        listElements[current].name = "";
        listElements[current].path = "";
        listElements[current].toHTML = NULL;
        listElements[current].toXML = NULL;
        listElements[current].modified = "";
        listElements[current].type = "";
        xmlFreeDoc(listElements[current].doc);
        while (listElements[current].name == "" && current < listElements.length() - 1){
            current = current + 1;
        }
        if (listElements[current].name == ""){
            while (listElements[current].name == "" && current > 0){
                current = current - 1;
            }
        }
        if (listElements[current].name == ""){
            close();
            dynamic_cast<MainWindow*>(mainWindow)->closeTabEditor();
            return QString::number(-1);
        }
        else
            return QString::number(current);
}

QString Editor::openWorkspace (const QString path, const int id){
    QString target;
    QString result = "";
    if (QDir::isAbsolutePath(path))
        target = path;
    else {
        QString origin = listElements[id].path;
        origin.remove(origin.split(QRegExp("[/\\\\]")).last());
        target = origin + path;
    }
    if (QFile::exists(target)){
        const nomoProjectType projectType = toProjectType(path.split('.').last());
        const QDir codeDir(target);
        int isOpen = -1;
        for (int i=0;i<listElements.length();i++)
            if(QDir(listElements[i].path) == codeDir)
                isOpen = i;
        if(isOpen != -1)
            return "*" + listElements[isOpen].type + QString::number(isOpen);
        else
            if (projectType != error)
                result = open(target, projectType);
            else
                result = tr("File extension error: ") + path;
    }
    else
        result = tr("Could not load: ") + path;
    return result;
}

void Editor::createProject(const nomoProjectType projectType, const QString projectPath, const QString projectName, const QString projectFormalism){
    xmlDocPtr project = xmlNewDoc((xmlChar*)"1.0");
    xmlNsPtr ns = xmlNewNs(NULL,(xmlChar*) "http://www.nomoseed.org/sdk", NULL);
    xmlNodePtr root = xmlNewNode(ns,(xmlChar*)"project");
    xmlSetProp(root, (xmlChar*) "type", (xmlChar*) toName(projectType).toUtf8().data());
    xmlSetProp(root, (xmlChar*) "context", (xmlChar*) tr("What is the context?").toUtf8().data());
    xmlSetProp(root, (xmlChar*) "subject", (xmlChar*) tr("What is the subject?").toUtf8().data());
    xmlSetProp(root, (xmlChar*) "language", (xmlChar*) dynamic_cast<MainWindow*>(mainWindow)->getLocale().name().split('_')[0].toUtf8().data());
    xmlSetProp(root, (xmlChar*) "name", (xmlChar*) projectName.toUtf8().data());
    xmlSetProp(root, (xmlChar*) "datetime", (xmlChar*) QDateTime::currentDateTimeUtc ().toString(Qt::ISODate).toUtf8().data());
    if (!projectFormalism.isEmpty() && projectFormalism != "none")
        xmlSetProp(root, (xmlChar*) "formalism", (xmlChar*) projectFormalism.toUtf8().data());
    xmlDocSetRootElement(project, root);
    XSLTProcessor::transform("transformations/create.xsl", project, projectPath.toUtf8().data(), NULL);
    xmlFreeDoc(project);
}

QString Editor::getDefaultProject (){
    const QString ext = listElements[current].path.split('.').last();
    createProject(toProjectType(ext), cache + "."+ext, listElements[current].name, "");
    QFile code(cache + "."+ext);
    code.open(QFile::ReadOnly);
    return code.readAll();
}

void Editor::setInterface(const QString language, const QString extension){
    languageInterface = language;
    extensionInterface = extension;
}
