/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//---------------------------------------------------------------------------
#ifdef __BORLANDC__
    #pragma hdrstop
#endif
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include <libxml/tree.h>
#include <libexslt/exslt.h>
#include "Xslt.h"
#include "Core.h"
#include <fstream>
#include <sstream>
#include <string.h>
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
namespace MediaConch {

//***************************************************************************
// Constructor/Destructor
//***************************************************************************

size_t Xslt::instances = 0;

//---------------------------------------------------------------------------
Xslt::Xslt(bool no_https) : Schema(no_https)
{
    xslt_ctx = NULL;
    doc_ctx = NULL;

    CS.Enter();
    if (instances == 0)
        exsltRegisterAll();

    instances++;
    CS.Leave();
}

//---------------------------------------------------------------------------
Xslt::~Xslt()
{
    if (xslt_ctx)
    {
        xsltFreeStylesheet(xslt_ctx);
        xslt_ctx = NULL;
        doc_ctx = NULL;
    }
    if (doc_ctx)
    {
        xmlFreeDoc(doc_ctx);
        doc_ctx = NULL;
    }

    CS.Enter();
    instances--;

    if (instances == 0)
        xsltCleanupGlobals();
    CS.Leave();
}

//---------------------------------------------------------------------------
bool Xslt::register_schema_from_doc(void* data)
{
    xmlDocPtr doc = (xmlDocPtr)data;
    if (doc == NULL)
        return false;

    xsltSetGenericErrorFunc(this, &manage_generic_error);
    xmlLoadExtDtdDefaultValue = 1;

    if (xslt_ctx)
    {
        xsltFreeStylesheet(xslt_ctx);
        xslt_ctx = NULL;
        doc_ctx = NULL;
    }

    if (doc_ctx)
    {
        xmlFreeDoc(doc_ctx);
        doc_ctx = NULL;
    }

    doc_ctx = xmlCopyDoc(doc, 1);
    xslt_ctx = xsltParseStylesheetDoc(doc_ctx);

    xsltSetGenericErrorFunc(NULL, NULL);

    return true;
}

//---------------------------------------------------------------------------
bool Xslt::register_schema_from_memory(const std::string& schem)
{
    xmlLoadExtDtdDefaultValue = 1;
    xmlSetGenericErrorFunc(this, &manage_generic_error);

    int doc_flags = XML_PARSE_COMPACT | XML_PARSE_DTDLOAD;
#ifdef XML_PARSE_BIG_LINES
    doc_flags =| XML_PARSE_BIG_LINES;
#endif // !XML_PARSE_BIG_LINES

    xmlDocPtr doc = NULL;
    if (no_https)
    {
        std::string memory = schem;
        Core::unify_no_https(memory);
        doc = xmlReadMemory(memory.c_str(), memory.length(), NULL, NULL, doc_flags);
    }
    else
        doc = xmlReadMemory(schem.c_str(), schem.length(), NULL, NULL, doc_flags);

    if (doc == NULL)
        return false;

    bool ret = register_schema_from_doc(doc);

    xmlFreeDoc(doc);
    xmlSetGenericErrorFunc(NULL, NULL);
    return ret;
}

//---------------------------------------------------------------------------
int Xslt::validate_xml(const std::string& xml, bool)
{
    report.clear();
    if (!xslt_ctx)
        return -1;

    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;

    int doc_flags = XML_PARSE_COMPACT | XML_PARSE_DTDLOAD;
    xmlSetGenericErrorFunc(this, &manage_generic_error);

#ifdef XML_PARSE_BIG_LINES
    doc_flags =| XML_PARSE_BIG_LINES;
#endif // !XML_PARSE_BIG_LINES

    xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.length(), NULL, NULL, doc_flags);
    if (!doc)
        return -1;

    const char** params = NULL;
    std::vector<std::string> vec;

    if (options.size())
    {
        std::map<std::string, std::string>::iterator it = options.begin();
        for (; it != options.end(); ++it)
        {
            if (!it->second.size())
                continue;
            vec.push_back(std::string(it->first));
            vec.push_back(std::string(it->second));
        }

        params = new const char* [vec.size() + 1];
        size_t i = 0;
        for(; i < vec.size(); ++i)
            params[i] = vec[i].c_str();
        params[i] = NULL;
    }

    xmlDocPtr res = xsltApplyStylesheet(xslt_ctx, doc, params);

    if (params)
        delete [] params;

    if (!res)
    {
        xmlFreeDoc(doc);
        return -1;
    }

    xmlChar *doc_txt_ptr = NULL;
    int doc_txt_len = 0;
    if (xsltSaveResultToString(&doc_txt_ptr, &doc_txt_len, res, xslt_ctx) < 0)
    {
        xmlFreeDoc(doc);
        xmlFreeDoc(res);
        return -1;
    }
    size_t Prefix = 0;
    if (doc_txt_len>3 && doc_txt_ptr[0]=='\n' && doc_txt_ptr[1]==' ' && doc_txt_ptr[2]==' ')
        Prefix=3; // TODO: we see such prefix in output, we need to understand the reason and remove it fro XSL instead of this hack
    report = std::string((const char*)doc_txt_ptr+Prefix, doc_txt_len);
    free(doc_txt_ptr);

    xmlFreeDoc(doc);
    xmlFreeDoc(res);
    xmlSetGenericErrorFunc(NULL, NULL);

    std::stringstream Out;
    if (errors.size())
    {
        for (size_t pos = 0; pos < errors.size(); pos++)
            Out << "\t" << errors[pos].c_str();
        if (!errors.size())
            Out << std::endl;
        report = Out.str();
        return 1;
    }
    return 0;
}

//***************************************************************************
// Callbacks
//***************************************************************************

//---------------------------------------------------------------------------
void Xslt::manage_error(void *userData, xmlErrorPtr err)
{
    Xslt *obj = (Xslt *)userData;
    if (!err || err->code == XML_ERR_OK) {
        return;
    }
    obj->errors.push_back(err->message);
}

//---------------------------------------------------------------------------
void Xslt::manage_generic_error(void *userData, const char* msg, ...)
{
    Xslt *obj = (Xslt *)userData;
    va_list args;
    char buf[4096] = {0};

    va_start(args, msg);
#ifdef _MSC_VER
    int ret = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, msg, args);
#else //_MSC_VER
    int ret = vsnprintf(buf, sizeof(buf), msg, args);
    if (ret < 0)
        ret = 0;
    buf[ret] = '\0';
#endif //_MSC_VER
    obj->errors.push_back(buf);
    va_end(args);
}

}
