//  Copyright (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 Affero 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 Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program. If not, see <http://www.gnu.org/licenses/>

#include <iostream>
#include <cstring>

#include <libxml/parser.h>
#include <libxml/xinclude.h>
#include <libxml/xpathInternals.h>
#include <libxml/xmlschemas.h>
#include <libxml/xmlschemastypes.h>

#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

#include "nomotools.h"

typedef enum {
    XML_VALID          =  1,
    XML_NOT_VALID      =  0,
    XML_ERROR_SCHEMA   = -1,
    XML_ERROR_PARSER   = -2,
    XML_INVALID_SCHEMA = -3,
    XML_ERROR_CONTEXT  = -4
} xsd_result;

typedef enum {
    NOMO_MOD             = 0,
    NOMO_PRG             = 1,
    NOMO_ENG             = 2,
    NOMO_BAS             = 3,
    NOMO_UNI             = 4,
    NOMO_LNK             = 5,
    NOMO_COD             = 6,
    NOMO_FR              = 7,
    NOMO_PR              = 8,
    NOMO_SEED            = 9,
    NOMO_EXTENSION_ERROR = 10
} nomo_extension;

typedef enum {
    NOMOTOOLS_CSV = 0,
    NOMOTOOLS_XML = 1,
    NOMOTOOLS_DB  = 2,
    NOMOTOOLS_UDB = 3
} nomotools_conversion;

static nomo_extension getExtension(const char *filename){
    int len = strlen(filename);
    if(len >= 4) {
        char ext[6] = "-----";
        ext[4] = filename[len-1];
        ext[3] = filename[len-2];
        ext[2] = filename[len-3];
        if(!strcmp(ext, "--.fr"))
            return NOMO_FR;
        else if(!strcmp(ext, "--.pr"))
            return NOMO_PR;
        if(len >= 5) {
            ext[1] = filename[len-4];
            if(!strcmp(ext, "-.mod"))
                return NOMO_MOD;
            else if(!strcmp(ext, "-.prg"))
                return NOMO_PRG;
            else if(!strcmp(ext, "-.eng"))
                return NOMO_ENG;
            else if(!strcmp(ext, "-.bas"))
                return NOMO_BAS;
            else if(!strcmp(ext, "-.uni"))
                return NOMO_UNI;
            else if(!strcmp(ext, "-.lnk"))
                return NOMO_LNK;
            else if(!strcmp(ext, "-.cod"))
                return NOMO_COD;
            if(len > 5) {
                ext[0] = filename[len-5];
                if(!strcmp(ext, ".seed"))
                    return NOMO_SEED;
            }
        }
    }
    return NOMO_EXTENSION_ERROR;
}

static xsd_result parse(const char* filename, const nomo_extension ext){
    const char * schemaPath;
    if(ext == NOMO_MOD)
        schemaPath = "grammars/basic_model.xsd";
    else if(ext == NOMO_PRG)
        schemaPath = "grammars/basic_program.xsd";
    else if(ext == NOMO_ENG)
        schemaPath = "grammars/inference_engine.xsd";
    else if(ext == NOMO_BAS)
        schemaPath = "grammars/knowledge_base.xsd";
    else if(ext == NOMO_UNI)
        schemaPath = "grammars/basic_unit.xsd";
    else if(ext == NOMO_COD)
        schemaPath = "grammars/code.xsd";
    else if ((ext != NOMO_LNK))
        return XML_ERROR_CONTEXT;
    xmlDocPtr doc = xmlReadFile(filename, "UTF-8", 0);
    if (ext == NOMO_LNK){
        const xmlNodePtr root = xmlDocGetRootElement (doc);
        if (!strcmp((const char*)root->name, "program"))
            schemaPath = "grammars/program.xsd";
        else if (!strcmp((const char*)root->name, "model"))
            schemaPath = "grammars/model.xsd";
        else if (!strcmp((const char*)root->name, "unit"))
            schemaPath = "grammars/unit.xsd";
        else
            return XML_ERROR_CONTEXT;
    }
    xmlSchemaParserCtxtPtr parserCtxt = xmlSchemaNewParserCtxt(schemaPath);
    xmlSchemaPtr schema = xmlSchemaParse(parserCtxt);
    if(schema == NULL) {
        xmlSchemaFreeParserCtxt(parserCtxt);
        xmlFreeDoc(doc);
        return XML_INVALID_SCHEMA;
    }
    xmlSchemaValidCtxtPtr validCtxt = xmlSchemaNewValidCtxt(schema);
    if(validCtxt == NULL) {
        xmlSchemaFree(schema);
        xmlSchemaFreeParserCtxt(parserCtxt);
        xmlFreeDoc(doc);
        return XML_ERROR_CONTEXT;
    }
    const int result = xmlSchemaValidateDoc(validCtxt, doc);
 /* if(result != 0 && ext == NOMO_COD){
        xmlXPathInit();
        xmlXPathContextPtr ctxt = xmlXPathNewContext(doc);
        xmlXPathRegisterNs(ctxt, (const xmlChar*)"code",(const xmlChar*)"http://www.nomoseed.org/code");
        xmlXPathObjectPtr xpathRes = xmlXPathEvalExpression((const xmlChar*)"//code:time_span_limit[@value='invalid']", ctxt);
        if (xmlXPathCastToBoolean(xpathRes) == 1)
        {
            xmlXPathFreeObject(xpathRes);
            xmlXPathFreeContext(ctxt);
            fprintf(stderr, "nomo code: TIME_SPAN_LIMIT_ERROR\n");
        }
        xmlXPathFreeObject(xpathRes);
        xpathRes = xmlXPathEvalExpression((const xmlChar*)"//code:maximum_of_internal_events[@value='invalid']", ctxt);
        if (xmlXPathCastToBoolean(xpathRes) == 1)
        {
            xmlXPathFreeObject(xpathRes);
            xmlXPathFreeContext(ctxt);
            fprintf(stderr, "nomo code: MAXIMUM_OF_INTERNAL_EVENTS_ERROR\n");
        }
        xmlXPathFreeObject(xpathRes);
        xpathRes = xmlXPathEvalExpression((const xmlChar*)"//code:maximum_of_rules_by_type[@value='invalid']", ctxt);
        if (xmlXPathCastToBoolean(xpathRes) == 1)
        {
            xmlXPathFreeObject(xpathRes);
            xmlXPathFreeContext(ctxt);
            fprintf(stderr, "nomo code: MAXIMUM_OF_RULES_BY_TYPE_ERROR\n");
        }
        xmlXPathFreeObject(xpathRes);
        xpathRes = xmlXPathEvalExpression((const xmlChar*)"//code:maximum_of_premises[@value='invalid']", ctxt);
        if (xmlXPathCastToBoolean(xpathRes) == 1)
        {
            xmlXPathFreeObject(xpathRes);
            xmlXPathFreeContext(ctxt);
            fprintf(stderr, "nomo code: MAXIMUM_OF_PREMISES_ERROR\n");
        }
        xmlXPathFreeObject(xpathRes);
        xmlXPathFreeContext(ctxt);
    }*/
    xmlSchemaFreeParserCtxt(parserCtxt);
    xmlSchemaFree(schema);
    xmlSchemaFreeValidCtxt(validCtxt);
    xmlFreeDoc(doc);
    return result == 0 ? XML_VALID : XML_NOT_VALID;
}

static void usage(void){
    printf("Usage : nomotools [option] [file]\n\n");
    printf("[file] can be:\n");
    printf("\tA unit(.uni)\n");
    printf("\tA model(.mod)\n");
    printf("\tA program(.prg)\n");
    printf("\tA knowledge base(.bas)\n");
    printf("\tA inference engine(.eng)\n");
    printf("\tA unit after linking(.lnk)\n");
    printf("\tA unit after encoding(.cod)\n");
    printf("\tA log file of full rules(.fr)\n");
    printf("\tA log file of partial rules(.pr)\n\n");
    printf("[option] can be:\n");
    printf("\t-h                 Help - display this message\n");
    printf("\t-parse             Valid XML file with basic grammar of the extension\n");
    printf("\t                   (option by default of XML document)\n");
    printf("\t-link              Link a unit file(.uni) or a program file(.prg) or a\n");
    printf("\t                   model file(.mod) \n");
    printf("\t-encode            Encode a unit file after linking (.lnk)\n");
    printf("\t-compile           Compile a unit file after encoding (.cod)\n");
    printf("\t-interface [l] [e] Generate the interface of a unit file after encoding\n");
    printf("\t                   (.cod) in the language [l] and with a extension [e]\n");
    printf("\t-db [path]         Generate the database of unit file after encoding\n");
    printf("\t                   (.cod) or log file (.pr .fr)\n");
    printf("\t-udb [path]        If exist update the database with log file (.pr .fr)\n");
    printf("\t                   else create the database\n");
    printf("\t-csv [list]        Convert log file in CSV format, optional [list] in\n");
    printf("\t                   CSV format (option by default of log files)\n");
    printf("\t-xml               Convert log file in XML format\n");
    printf("\t-seed [seed]       Convert log file of full rule(.fr) in SEED format,\n");
    printf("\t                   [seed] is the origin seed\n");
    printf("\t-critical [-full]  Generate a unit for the maximal computing time of\n");
    printf("\t                   the interpretation process from a encode file (.cod).\n");
    printf("\t                   The option [-full] calculates the maximum number of\n");
    printf("\t                   rules with knowledge base\n");
    printf("\t-time              Compile the file(.cod) from the critical unit generate\n");
    printf("\t                   and evaluate the time process, after remove the seed\n");
    printf("\n");
}

static int convertLog(const char* filename, const nomo_extension ext, const nomotools_conversion format, const char* dbPath){
    int result = 1;
    nomotoolsinit();
    if(ext == NOMO_FR)
        if (format == NOMOTOOLS_CSV)
            fr_to_csv(filename, strlen(filename));
        else if (format == NOMOTOOLS_XML)
            fr_to_xml(filename, strlen(filename));
        else if (format == NOMOTOOLS_DB)
            fr_to_db(filename, strlen(filename), dbPath, strlen(dbPath), 1);
        else
            fr_to_db(filename, strlen(filename), dbPath, strlen(dbPath), 0);
    else if(ext == NOMO_PR)
        if (format == NOMOTOOLS_CSV)
            pr_to_csv(filename, strlen(filename));
        else if (format == NOMOTOOLS_XML)
            pr_to_xml(filename, strlen(filename));
        else if (format == NOMOTOOLS_DB)
            pr_to_db(filename, strlen(filename), dbPath, strlen(dbPath), 1);
        else
            pr_to_db(filename, strlen(filename), dbPath, strlen(dbPath), 0);
    else
        result = 0;
    nomotoolsfinal();
    return result;
}

static void convertLogSeed(const char* logname, const char* seedname){
    nomotoolsinit();
    fr_to_seed(logname, strlen(logname), seedname, strlen(seedname), 0);
    nomotoolsfinal();
}

static void compileCode(const char* filename){
    nomotoolsinit();
    compile(filename,strlen(filename));
    nomotoolsfinal();
}

static void link(char* filename){
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp1, temp2;
    xsltStylesheetPtr xsl;
    temp1 = xmlReadFile(filename, NULL, XML_PARSE_XINCLUDE);
    xmlXIncludeProcess(temp1);
    xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/link_0.xsl");
    temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
    xsltFreeStylesheet(xsl);
    xmlFreeDoc(temp1);
    xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/link_1.xsl");
    temp1 = xsltApplyStylesheet(xsl, temp2, NULL);
    xsltFreeStylesheet(xsl);
    xmlFreeDoc(temp2);
    xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/link_2.xsl");
    temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
    xsltFreeStylesheet(xsl);
    xmlFreeDoc(temp1);
    const int len = strlen(filename);
    filename[len-3] = 'l';
    filename[len-2] = 'n';
    filename[len-1] = 'k';
    xmlSaveFileEnc(filename, temp2, "UTF-8");
    xmlFreeDoc(temp2);
    xsltCleanupGlobals();
    xmlCleanupParser();
}

static void encode(char* filename){
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp1, temp2;
    xsltStylesheetPtr xsl;
    temp1 = xmlReadFile(filename, NULL, 0);
    if (strcmp((const char*)xmlDocGetRootElement (temp1)->name, "unit"))
        usage();
    else {
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/encoding_phase_1.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp1);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/encoding_phase_2.xsl");
        temp1 = xsltApplyStylesheet(xsl, temp2, NULL);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/encoding_phase_3.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp1);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/encoding_phase_4.xsl");
        temp1 = xsltApplyStylesheet(xsl, temp2, NULL);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        const int len = strlen(filename);
        filename[len-3] = 'c';
        filename[len-2] = 'o';
        filename[len-1] = 'd';
        xmlSaveFileEnc(filename, temp1, "UTF-8");
        xmlFreeDoc(temp1);
        xsltCleanupGlobals();
        xmlCleanupParser();
    }
}

static void interface(char* filename, char* language, char* extension){
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp1, temp2;
    xsltStylesheetPtr xsl;
    temp1 = xmlReadFile(filename, NULL, 0);
    if (strcmp((const char*)xmlDocGetRootElement (temp1)->name, "code"))
        usage();
    else {
        char xslInterfacePath [256] = "transformations/interface_";
        strcat(xslInterfacePath, language);
        strcat(xslInterfacePath, "_");
        strcat(xslInterfacePath, extension);
        strcat(xslInterfacePath, ".xsl");
        xsl = xsltParseStylesheetFile((const xmlChar *) xslInterfacePath);
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        xmlFreeDoc(temp1);
        const int len = strlen(filename);
        filename[len-3] = 0;
        strcat(filename, extension);
        xsltSaveResultToFilename(filename, temp2, xsl, 0);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xsltCleanupGlobals();
        xmlCleanupParser();
    }
}

static void db(const char* filename, const char* path){
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp1, temp2;
    xsltStylesheetPtr xsl;
    char filepath[256];
    temp1 = xmlReadFile(filename, NULL, 0);
    if (strcmp((const char*)xmlDocGetRootElement (temp1)->name, "code"))
        usage();
    else {
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/rule.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        strcpy(filepath, path);
        strcat(filepath, "rule.csv");
        xsltSaveResultToFilename(filepath, temp2, xsl, 0);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/type.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        strcpy(filepath, path);
        strcat(filepath, "type.csv");
        xsltSaveResultToFilename(filepath, temp2, xsl, 0);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/item.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        strcpy(filepath, path);
        strcat(filepath, "item.csv");
        xsltSaveResultToFilename(filepath, temp2, xsl, 0);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/component.xsl");
        temp2 = xsltApplyStylesheet(xsl, temp1, NULL);
        strcpy(filepath, path);
        strcat(filepath, "component.csv");
        xsltSaveResultToFilename(filepath, temp2, xsl, 0);
        xsltFreeStylesheet(xsl);
        xmlFreeDoc(temp2);
        xmlFreeDoc(temp1);
        xsltCleanupGlobals();
        xmlCleanupParser();
    }
}

void critical(const char* filename, const char* option)
{
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp1, temp2;
    xsltStylesheetPtr xsl;
    temp1 = xmlReadFile(filename, NULL, XML_PARSE_XINCLUDE);
    xmlXIncludeProcess(temp1);
    xmlXPathInit();
    xmlXPathContextPtr ctxt = xmlXPathNewContext(temp1);
    xmlXPathRegisterNs(ctxt, (const xmlChar*)"code",(const xmlChar*)"http://www.nomoseed.org/code");
    xmlXPathObjectPtr xpathRes = xmlXPathEvalExpression((const xmlChar *)"/code:code/@unit", ctxt);
    char name[50] = "critical_";
    strcpy(name,(const char*)xmlXPathCastToString(xpathRes));
    xmlXPathFreeObject(xpathRes);
    xmlXPathFreeContext(ctxt);
    strcat(name, "_critical");
    if (!strcmp(option, "full"))
    {
        strcat(name, "_full");
    }
    char param_name [40];
    strcpy(param_name, "'");
    strcat(param_name, name);
    strcat(param_name, "'");
    char param_option [6];
    strcpy(param_option, "'");
    strcat(param_option, option);
    strcat(param_option, "'");
    const char * params [5] = {"name", param_name, "option", param_option, NULL};
    xsl = xsltParseStylesheetFile((const xmlChar *) "transformations/critical.xsl");
    temp2 = xsltApplyStylesheet(xsl, temp1, params);
    xmlFreeDoc(temp1);
    strcat(name, ".uni");
    xsltSaveResultToFilename(name, temp2, xsl, 0);
    xsltFreeStylesheet(xsl);
    xmlFreeDoc(temp2);
    xsltCleanupGlobals();
    xmlCleanupParser();
}

void time(const char* filename)
{
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    xmlDocPtr temp;
    temp = xmlParseFile(filename);
    if (temp)
    {
        xmlXPathInit();
        xmlXPathContextPtr ctxt = xmlXPathNewContext(temp);
        xmlXPathRegisterNs(ctxt, (const xmlChar*)"code",(const xmlChar*)"http://www.nomoseed.org/code");
        xmlXPathObjectPtr xpathRes = xmlXPathEvalExpression((const xmlChar *)"count(//code:type[@category='input'])", ctxt);
        char cmd[20];
        strcpy(cmd,"nomotime ");
        strcat(cmd,(const char*)xmlXPathCastToString(xpathRes));
        xmlXPathFreeObject(xpathRes);
        xpathRes = xmlXPathEvalExpression((const xmlChar *)"count(//code:type[@category='command' and @name='time'])=1", ctxt);
        if (xmlXPathCastToBoolean(xpathRes))
        {
            compileCode(filename);
            system(cmd);
            char seed[50];
            const int len = strlen(filename);
            strcpy(seed,filename);
            seed[len-3] = 's';
            seed[len-2] = 'e';
            seed[len-1] = 'e';
            strcat(seed,"d");
            remove (seed);
        }
        else
            usage();
        xmlXPathFreeObject(xpathRes);
        xmlXPathFreeContext(ctxt);
    }
    else
        usage();
}

int main(int argc, char **argv){
   if(argc == 1)
        usage();
   else if(argc == 2)
        if(!strcmp(argv[1], "-h"))
                usage();
        else {
            const nomo_extension ext = getExtension(argv[1]);
            if(ext == NOMO_PR || ext == NOMO_FR)
                convertLog(argv[1], ext, NOMOTOOLS_CSV, NULL);
            else if(ext <= NOMO_COD)
                parse(argv[1], ext);
            else
                usage();
        }
     else if(argc == 3) {
        const nomo_extension ext = getExtension(argv[2]);
        if(ext != NOMO_EXTENSION_ERROR){
            if(!strcmp(argv[1], "-parse") && ext <= NOMO_COD)
                parse(argv[2], ext);
            else if(!strcmp(argv[1], "-link") && (ext == NOMO_UNI || ext == NOMO_PRG || ext == NOMO_MOD))
                link(argv[2]);
            else if(!strcmp(argv[1], "-encode") && ext == NOMO_LNK)
                encode(argv[2]);
            else if(!strcmp(argv[1], "-compile") && ext == NOMO_COD)
                compileCode(argv[2]);
            else if(!strcmp(argv[1], "-db") && ext == NOMO_COD)
                db(argv[2], "");
            else if(!strcmp(argv[1], "-csv") && (ext == NOMO_FR || ext == NOMO_PR))
                convertLog(argv[2], ext, NOMOTOOLS_CSV, NULL);
            else if(!strcmp(argv[1], "-db") && (ext == NOMO_FR || ext == NOMO_PR))
                convertLog(argv[2], ext, NOMOTOOLS_DB, "");
            else if(!strcmp(argv[1], "-udb") && (ext == NOMO_FR || ext == NOMO_PR))
                convertLog(argv[2], ext, NOMOTOOLS_UDB, "");
            else if(!strcmp(argv[1], "-xml") && ext == (ext == NOMO_FR || ext == NOMO_PR))
                convertLog(argv[2], ext, NOMOTOOLS_XML, NULL);
            else if(!strcmp(argv[1], "-time") && ext == NOMO_COD)
                time(argv[2]);
            else if(!strcmp(argv[1], "-critical") && ext == NOMO_COD)
                critical(argv[2], "");
            else
                usage();
        }
    }
    else if(argc == 4){
       const nomo_extension ext = getExtension(argv[3]);
        if(!strcmp(argv[1], "-seed") && getExtension(argv[2]) == NOMO_SEED && ext == NOMO_FR)
            convertLogSeed(argv[3], argv[2]);
        else if(!strcmp(argv[1], "-db") && ext == NOMO_COD)
            db(argv[3], argv[2]);
        else if(!strcmp(argv[1], "-db") && (ext == NOMO_FR || ext == NOMO_PR))
            convertLog(argv[3], ext, NOMOTOOLS_DB, argv[2]);
        else if(!strcmp(argv[1], "-udb") && (ext == NOMO_FR || ext == NOMO_PR))
            convertLog(argv[3], ext, NOMOTOOLS_UDB, argv[2]);
        else if(!strcmp(argv[1], "-critical") && !strcmp(argv[2], "-full")  && ext == NOMO_COD)
            critical(argv[3], "full");
        else
            usage();
   }
    else if(argc == 5)
        if(!strcmp(argv[1], "-interface") && getExtension(argv[4]) == NOMO_COD)
            interface(argv[4], argv[2], argv[3]);
        else
            usage();
    else
        usage();
    return 0;
}
