#include <iostream>
#include <stdlib.h>
#include <boost/format.hpp>
#include <string>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>

#include <map>
#include <list>
#include <set>
using namespace std;

char* pathTestsOutput = "/tmp/xreate-tests-output-XXXXXX";
const string& pathComponentsGraph = "/private/prg/code/xreate/project/subsystems.graphml";

map<string, bool> tests;
set<string> testsUnused;

void runTests(){
    //mkstemp(pathTestsOutput);
    const string &cmm_export = string("export GTEST_OUTPUT=\"xml:")+ pathTestsOutput +"\"";
    const string &cmm_chdir = "cd /private/prg/code/xreate/";
    const string &cmm_runtest = "exec build/xreate";

    const string& comm = boost::str(boost::format("(%1% && %2% && %3%)") % cmm_export % cmm_chdir % cmm_runtest);
    system(comm.c_str());
}

void parseTestsOutput(){
    const string& queryTestCases = "//testcase[@status='run']";

    xmlDocPtr doc = xmlParseFile(pathTestsOutput);
    xmlXPathContextPtr context = xmlXPathNewContext(doc);
    xmlXPathObjectPtr testcases = xmlXPathEvalExpression((xmlChar*) queryTestCases.c_str(), context);

    for (int caseId = 0; caseId < testcases->nodesetval->nodeNr; ++caseId) {
        xmlNodePtr testcase = testcases->nodesetval->nodeTab[caseId];

        string nameTestSuite((const char *) xmlGetProp(testcase->parent, (const xmlChar*)"name"));
        string nameTestCase((const char *) xmlGetProp(testcase, (const xmlChar*)"name"));
        bool flagSuccess = (testcase->children)? false: true;

        string testName = nameTestSuite+":"+nameTestCase;
        tests.emplace(testName, flagSuccess);
        testsUnused.insert(testName);
    }
}

string getTestsResultForComponent(const string& signature){
    list<string> componentParts;
    boost::split(componentParts, signature, [](const char& a){return a==',';});

    int resNeg = 0; int resPos = 0; bool flagTestsFound = false;
    for(auto test: tests){
        for(const string& signature: componentParts){
            if (boost::icontains(test.first, signature)){
                if (test.second){
                    resPos ++;
                } else {
                    resNeg--;
                }

                flagTestsFound = true;
                testsUnused.erase(test.first);
                break;
            }
        }
    }

    if (!flagTestsFound){
        cout << "Warning: No tests for "<<signature << endl;
    }

    return (resNeg!=0)? to_string(resNeg) : to_string(resPos);
}

void parseComponentsGraph(){
    const string& queryNode = "//a:node[a:data[@key=\"d4\"]]";
    const string& queryTestResults = "a:data[@key='d5']";
    const string& queryTestSignature = "a:data[@key='d4']";

    xmlDocPtr doc = xmlParseFile(pathComponentsGraph.c_str());
    xmlNodePtr root = xmlDocGetRootElement(doc);

    xmlXPathContextPtr context = xmlXPathNewContext(doc);
    xmlXPathRegisterNs(context, (xmlChar*) "a", (xmlChar*) "http://graphml.graphdrawing.org/xmlns");
    xmlXPathObjectPtr setComponents = xmlXPathEvalExpression((xmlChar*) queryNode.c_str(), context);

    for (int nodeId=0; nodeId < setComponents->nodesetval->nodeNr; ++nodeId){
        xmlNodePtr nodeComponent = setComponents->nodesetval->nodeTab[nodeId];
        //xmlXPathSetContextNode(node, context);
        context->node = nodeComponent;
        xmlXPathObjectPtr setNodeTestSignature = xmlXPathEvalExpression((xmlChar*) queryTestSignature.c_str(), context);
        assert(setNodeTestSignature->nodesetval->nodeNr==1);
        char* componentTestsSignature = (char*) xmlNodeGetContent(setNodeTestSignature->nodesetval->nodeTab[0]);

        string componentTestsResult = getTestsResultForComponent(componentTestsSignature);

        xmlXPathObjectPtr setNodeTestResult = xmlXPathEvalExpression((xmlChar*) queryTestResults.c_str(), context);

        if (0 == setNodeTestResult->nodesetval->nodeNr){
             xmlNodePtr dataTestsResult = xmlNewChild(nodeComponent, NULL, BAD_CAST "data",
                                                      BAD_CAST componentTestsResult.c_str());

             xmlNewProp(dataTestsResult, BAD_CAST "key", BAD_CAST "d5");
             continue;
        }
        assert(setNodeTestResult->nodesetval->nodeNr == 1);
        xmlNodePtr dataTestsResult = setNodeTestResult->nodesetval->nodeTab[0];
        xmlNodeSetContent(dataTestsResult, BAD_CAST componentTestsResult.c_str());
    }

    xmlSaveFile(pathComponentsGraph.c_str(), doc);
}

void checkUnusedTests(){
    for (string test: testsUnused){
        cout << "Warning: no components use test "<<test << endl;
    }
}

int main()
{
    runTests();
    parseTestsOutput();
    parseComponentsGraph();
    checkUnusedTests();
    return 0;
}

