// ******************************************
// * $Id$
// *
// *   UserException()
// *   RaiseAnError()
// *   TestAndRaiseAnXMLError()
// *   SafeLoadTextXML()
// *   SafeLoadFileXSL()
// *   XML2HTML()
// *   fetchXMLContent()
// *   mergeXMLAttachments()
// *   Hexify()
// *
// * XML en XSL functies.
// *
// ******************************************
//
function UserException(number, source, message)
{
   this.number = number;
   this.source = source;
   this.description = message;
}

function RaiseAnError(e, p)
{
    throw new UserException(e + 1000, "UserException", p);
}

function TestAndRaiseAnXMLError(p, iXml, xmltext) // xmltext is optional for error logging
{
    if (iXml.parseError.errorCode != 0)
    {
        if (xmltext)
            LogString2File(1, "Failed_XML", xmltext, "xml");
        RaiseAnError( iXml.parseError.errorCode
                    , p + "\n" + iXml.parseError.reason
                        + " regel " + iXml.parseError.line
                              + "(" + iXml.parseError.filepos + ")"
                    );
    }
}

function SafeLoadTextXML(xmlText)
{
    var iXml = new ActiveXObject("Msxml2.DOMDocument.6.0");
    iXml.async = false;
    iXml.loadXML(xmlText);
    TestAndRaiseAnXMLError("Error in xmlData", iXml);

    return iXml;
}

function SafeLoadFileXSL(xslPath)
{
    var iXsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.6.0"); // Moet FreeThreaded voor XSLTemplate
    iXsl.async = false;
    iXsl.setProperty("AllowXsltScript", true);
    iXsl.load(custabspath + "/" + xslPath);
    iXsl.resolveExternals = true; // anders SOAP problemen om de template functie te vinden
    TestAndRaiseAnXMLError("Error loading file " + xslPath, iXsl);

    return iXsl;
}

// Vergelijk shared.inc versie
var protectQS =
{
    create: function (url, params)
    {
        var splitter = url.split("?");
        var pad = splitter[0];
        var qs = splitter.length>1?splitter[1]:"x=x";
        var padsplitter = pad.split("/");
        var file = padsplitter[padsplitter.length-1]; // laatste component
        // Let op dat protectQS.wsc hetzelfde doet voor Facmgt
        var str = file.toUpperCase() + "?" + qs;

        //var hmacced = protectHMAC.create(data, params);
        var ts = String(Math.round((new Date).getTime() / 1000));
        var data = ts + ":" + str
        Log2File(2, "hmacdata: " + data);
        //__Log("sleutel: "+ params.sleutel);
        var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
        var sig = oCrypto.hex_hmac_sha1(params.sleutel, data);
        var hmac = oCrypto.hex2base64(sig, false, true); // no padding, urlsafe
        oCrypto = null; // Caching klinkt leuk maar Oracle sessies blijven langer hangen?
        var hmacced = ts+":"+hmac;

        var newurl = pad + "?" + qs + "&hmac="+encodeURIComponent(hmacced);

        return newurl;

    }
}

function XML2HTML( xmlDoc
                 , xslPath
                 , srtnotificatie
                 , mode
                 , p_bedrijfadres
                 )
{
    Log2File(2, "*> XML2HTML");
    // Transform body=xml according to xslPath=xslfilenaam with optionel srtnotification parameter (e.g. RESBEV)
    // Output is supposed to be a HTML string but really depends on the XSL content
    var xslt   = new ActiveXObject("Msxml2.XSLTemplate.6.0");
    var xslProc;

    var xslDoc = SafeLoadFileXSL(xslPath);
    var result = "";

    xslDoc.resolveExternals = true; // anders op de volgende regel al:
                                    // A reference to variable or parameter 'srtnotificatiecode' cannot be resolved
    xslt.stylesheet = xslDoc;

    var xslProc = xslt.createProcessor();
    xslProc.input = xmlDoc;
    xslProc.addParameter("srtnotificatiecode", srtnotificatie);
    xslProc.addParameter("mode", mode);
    if (p_bedrijfadres)
    {
        xslProc.addParameter("bedrijfadres_key", p_bedrijfadres.bedrijfadres_key);
    }
    /*
        plugindata:
        {
            "xlstransformparams": {
                                    "opzichter_variant": "1",
                                    "parameter2": "waarde2"
                                  }
        }
     */
    if (p_bedrijfadres && p_bedrijfadres.plugindata && "xlstransformparams" in p_bedrijfadres.plugindata)
    {
        for (paramname in p_bedrijfadres.plugindata.xlstransformparams)
        {
            var paramval = p_bedrijfadres.plugindata.xlstransformparams[paramname];
            if (paramval && paramval.substr(0, 1) == '@')
            {
                var selector = paramval.substring(1);
                var v_node = xmlDoc.selectSingleNode(selector);
                if (v_node) 
                {
                    paramval = v_node.text;
                }
                else
                {
                    paramval = "";
                }
                Log2File(2, "Fetching parameter " + selector + " gave: " + paramval);
            }
            xslProc.addParameter(paramname, paramval);
        }
    }

    xslProc.transform();
    result = xslProc.output;

    // Let op: dezelfde code in xml_converter.inc
    // eerst lockeduser(xmlnode,key,bedrijf_key) vervangen
    // lockeduser(opdracht,12345,910) met 12345 opdracht_key en 910 bedrijf_key
    var hmacs = result && result.match(/(lockeduser\([^\)]*\))/g); // heeft nu array van lockeduser(opdracht,12345,910)
    for (var i =0; hmacs && i < hmacs.length; i++)
    {
        var params = hmacs[i].match(/\(([^,]+)\,(\d+),(\d+)\)/);
        if (!params)
            throw { description: "lockeduser is used in xsl but empty opdracht_key or bedrijf_key" };
        if (params.length == 4)
        {
            var xmlnode = params[1]; // We ondersteunen alleen nog maar 'opdracht'
            var key = parseInt(params[2], 10);
            var bedrijf_key = parseInt(params[3], 10);
            // Let op: we pakken altijd het eerste bedrijfadres
            var sql = "SELECT prs_bedrijfadres_key,"
                    + "       prs_bedrijfadres_lockuser_key,"
                    + "       prs_bedrijfadres_locksecret"
                    + "  FROM prs_bedrijfadres"
                    + " WHERE prs_bedrijf_key = " + bedrijf_key
                    + "   AND prs_bedrijfadres_startdatum <= SYSDATE"
                    + "   AND prs_bedrijfadres_lockuser_key IS NOT NULL"
                    + " ORDER BY prs_bedrijfadres_startdatum DESC";
            var oRs = Oracle.Execute(sql);
            if (oRs.Eof)
            {   // Ergens in de bon staat
                //     <xsl:attribute name="href">https://aqqa.facilitor.nl/lockeduser(opdracht,<xsl:value-of select="key"/>,
                //                <xsl:value-of select="uitvoerende/bedrijf/key"/>)</xsl:attribute>
                // maar bij de leverancier is geen 'Vaste gebruiker' en 'Gedeeld geheim' ingevuld
                throw { description: "lockeduser is used in xsl but missing prs_bedrijfadres_locksecret/prs_bedrijfadres_lockuser_key" };
            }
            var bdradr_key = oRs("prs_bedrijfadres_key").Value;
            var lockuser_key = oRs("prs_bedrijfadres_lockuser_key").Value;
            var locksecret = oRs("prs_bedrijfadres_locksecret").Value;
            oRs.Close();
            if (lockuser_key && !locksecret) {
                var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
                locksecret = oCrypto.hex_random(32);
                var sql = "UPDATE prs_bedrijfadres"
                        + "   SET prs_bedrijfadres_locksecret = " + safe.quoted_sql(locksecret)
                        + " WHERE prs_bedrijfadres_key = " + bdradr_key;
                Oracle.Execute(sql);
            }
            var url = "?u={0}&k={1}&lbdr={2}".format(xmlnode, key, bdradr_key);
            var newurl = protectQS.create(url, { sleutel: locksecret, no_user_key: true });

            result = result.replace(hmacs[i], newurl);
        }
    }

    // Let op: dezelfde code in xml_converter.inc
    // nu fclttoegang(xmlnode,key) vervangen
    // xmlnode mag zijn 'reservering', 'afspraak' of 'opdracht' zonder de quotes
    // fclttoegang(reservering,12345) geeft toegang tot rsv_ruimte_key 12345 (wat niet reservering 12345/1 hoeft te zijn)
    var hmacs = result && result.match(/(fclttoegang\([^\)]*\))/g); // heeft nu array van fclttoegang(reservering,12345)
    for (var i =0; hmacs && i < hmacs.length; i++)
    {
        var params = hmacs[i].match(/\(([^,]+)\,(\d+)\)/);
        if (!params)
            throw { description: "fclttoegang is used in xsl but empty xmlnode or key" };
        if (params.length == 3)
        {
            var xmlnode = params[1]; // We ondersteunen alleen nog maar 'opdracht'
            var key = parseInt(params[2], 10);
            // Let op: zoek de laagste fac_tracking_key als (onderdeel van) de 'sleutel'
            var sql = "SELECT MIN(fac_tracking_key) fac_tracking_key,"
                    + "       MIN(fac_tracking_datum) fac_tracking_datum"
                    + "  FROM fac_tracking ft, fac_srtnotificatie fsn"
                    + " WHERE ft.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key"
                    + "   AND fac_srtnotificatie_xmlnode = " + safe.quoted_sql(xmlnode)
                    + "   AND fac_tracking_refkey = " + key;
            var oRs = Oracle.Execute(sql);
            if (oRs.Eof)
            {   // Ergens in de bon staat zo'n fclttoegang maar geen tracking gevonden?
                throw { description: "fclttoegang is used in xsl but no tracking found for " + xmlnode + "/" + key};
            }
            var locksecret = String(oRs("fac_tracking_key").Value) + "|" + toDateTimeString(new Date(oRs("fac_tracking_datum").Value));
            oRs.Close();
            var url = "?u=toegang&k={1}&xmlnode={0}".format(xmlnode, key);
            var newurl = protectQS.create(url, { sleutel: locksecret, no_user_key: true });

            result = result.replace(hmacs[i], newurl);
        }
    }
    Log2File(2, "*< XML2HTML");

    return result;
}


// Resultaat is een xmlDoc object of null bij errors
// één niveau van caching, is efficienter als 13 keer dezelfde MLD2BO
var prev_XMLContent = {};
function fetchXMLContent( node
                        , refkey
                        , xrefkey
                        , where
                        , userlang
                        , params
                        )
{

    params = params || {};
    if (prev_XMLContent.node == node &&
        prev_XMLContent.refkey == refkey &&
        prev_XMLContent.xrefkey == xrefkey &&
        prev_XMLContent.where == where &&
        prev_XMLContent.userlang == userlang &&
        prev_XMLContent.aanmaak == params.aanmaak) // putorders loopt soms heel lang, oppassen met cachen van ORDUPD's
    {
        Log2File(2, "Re-using cached xmlDoc");
        return prev_XMLContent.xmlDoc;
    }
    var xml_content = "";

    try
    {
        // we geven de gewenste taal mee aan de Oracle sessie, die dat verder zal gebruiken
        var sql = "BEGIN lcl.setuserlanguage (" + safe.quoted_sql(userlang) + "); END;"
        Log2File(3, sql);
        Oracle.Execute(sql);
        //
        var sql_xtra = 'NULL';
        // FCLT#81165 tijdelijke fix omdat xtrakey soms onverwacht een fac_tracking_key bevat
        //            wat 95% van de bonnen niet verwacht (die verwachten een note_key van de
        //            notitie die je bij het handmatig mailen hebt ingegeven)
        // Hieronder eigenlijk alle srtnotificatie codes benoemen die een notitie-key verwachten in extra_key
        // Correcte oplossing (alle bonnen aanpassen) is geregistreerd in FCLT#87598
        if (xrefkey > 0
                && (node != 'deel'     || params.srtnotificatie == 'INSMAI')
                && (node != 'melding'  || params.srtnotificatie == 'MLDMAI')
                && (node != 'opdracht' || params.srtnotificatie == 'ORDMAI' || params.srtnotificatie == 'ORDNOL')
                && (node != 'contract' || params.srtnotificatie == 'CNTMAI')
                && (node != 'factuur'  || params.srtnotificatie == 'FINMAI')
           )
        {
            sql_xtra = String(xrefkey);
        }

        var sql_params = safe.quoted_sql(node)
                                  + ", " + refkey
                                  + ", " + safe.quoted_sql(customerId)
                                  + ", '$PutOrdersSession$'"
                                  + ", " + sql_xtra
                                  + ", " + safe.quoted_sql(where);
        if (params.notificatie_key > 0)
        {
            sql_params += ", p_notificatie_key => " + params.notificatie_key;
        }

        var sql = "SELECT xml.make_xml2(" + sql_params + ") xml_blob, sys_context('USERENV','SID') mysid FROM dual";
        Log2File(typeof PUO_LOGPATH == "undefined"?2:1, sql);
        var tmstart = new Date();
        var oRs = Oracle.Execute(sql);
        var tmend = new Date();
        var xml_content = oRs("xml_blob").Value||"";
        var mysid = oRs("mysid").Value||"";
		oRs.Close();
		if (xml_content)
            Log2File(typeof PUO_LOGPATH == "undefined"?2:1, "XML blob is {0} karakters in {1}ms for SID {2}.".format(xml_content.length, tmend.getTime() - tmstart.getTime(), mysid));
        else
            Log2File(1, "XML blob is leeg. Verwijderd record?");
    }
    catch(e)
    {
        Log2File(0, "fetchXMLContent error: " + e.description, "W");
        xml_content = "";
    }
    var xmlDoc = xml_content?SafeLoadTextXML(xml_content):null;
    xml_content = addLanguageNode(xmlDoc, { userlang: userlang });
    LogString2File(3, "make_xml", xml_content, "xml");

    prev_XMLContent.node = node;
    prev_XMLContent.refkey = refkey;
    prev_XMLContent.xrefkey = xrefkey;
    prev_XMLContent.where = where;
    prev_XMLContent.userlang = userlang;
    prev_XMLContent.aanmaak == params.aanmaak;
    prev_XMLContent.xmlDoc = xmlDoc;

    return xmlDoc;
}

function addLanguageNode(p_xml, params)
{
    var lang_path = custabspath + "/xsl/language_" + params.userlang + ".xml";
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    if (p_xml && fso.FileExists(lang_path))
    {
        var xmlIns = new ActiveXObject("Msxml2.DOMDocument.6.0");
        xmlIns.async = false;
        xmlIns.load(lang_path);
        TestAndRaiseAnXMLError("Error in "+lang_path, xmlIns);

        var langNode = xmlIns.selectSingleNode("/*");  // de root-node uit het zojuist geladen taal-bestand
        var clone = p_xml.importNode(langNode, true);
        var nodeParent = p_xml.selectSingleNode("//facilitor/lcl"); // In deze node moet het worden toegevoegd.
        nodeParent.appendChild(clone);
        Log2File(1, lang_path + " added to XML");
    }
    return (p_xml?p_xml.documentElement.xml:"");
}

function mergeXMLAttachments(iXml, p_xslPath)
{
    Log2File(2, ">>mergeXMLAttachments");
    mergeFlexAttachments(iXml, p_xslPath, "//FCLTmeldingattachment", "meldingkey", "M", "MLD");
    mergeFlexAttachments(iXml, p_xslPath, "//FCLTnotitieattachment", "notitiekey", "N", "MLDN");

    mergeFlexAttachments(iXml, p_xslPath, "//FCLTopdrachtattachment", "opdrachtkey", "O", "MLD");
    mergeFlexAttachments(iXml, p_xslPath, "//FCLTnotitieattachment", "notitiekey", "N", "ORDN");

    mergeFixedAttachment(iXml, p_xslPath, "//FCLTfixedattachment", "BDRADR");
    Log2File(2, "<<mergeXMLAttachments");
}

function mergeFlexAttachments(iXml, p_xslPath, xmltag, keynaam, pNiveau, pModule)
{
    // Doorzoek iXml op <!FCLTmeldingattachment>345</FCLTmeldingattachment>
    // en vervang die door alle flexkenmerken er onder
    // <Iattachements>   <-- nodename = Iattachements
    //      <attachment>...hexdata...</attachment>
    //      <name>naam</name>
    //      <size>1235</size>
    // </Iattachments>
    var NodeList = iXml.documentElement.selectNodes(xmltag);
    if (NodeList.length > 0)
    {
        Log2File(2, "*>>mergeFlexAttachments:" + pModule + " =" + NodeList.length);

        for (var index=0; index < NodeList.length; index++)
        {
            var node = NodeList[index];
            var meldingopdrachtkey = parseInt(trimall(node.attributes.getNamedItem(keynaam).text));
            var bijlagemod = node.attributes.getNamedItem("bijlagemod")?(trimall(node.attributes.getNamedItem("bijlagemod").text)):"";
            var v_module = (bijlagemod?bijlagemod:pModule);
            if (v_module == "MLDN" || v_module == "ORDN")
            {
                var kenmerkkey = -1; // hardcoded
            }
            else
            {
                var kenmerkkey = parseInt(trimall(node.attributes.getNamedItem("kenmerkkey").text), 10) || 0;
            }
            var bijlagekey = node.attributes.getNamedItem("bijlagekey")?parseInt(trimall(node.attributes.getNamedItem("bijlagekey").text)):"";
            Log2File(1, "Searching attachments {0}{1} kenmerk {2}".format(pNiveau, meldingopdrachtkey, kenmerkkey));
            var att_list = [];
            var sql = "SELECT r.fac_bijlagen_disk_directory"
                    + "     , r.fac_bijlagen_filename"
                    + "     , r.fac_bijlagen_file_size"
                    + "  FROM fac_bijlagen r"
                    + "     , fac_bijlagen b"
                    + " WHERE b.fac_bijlagen_root_key = r.fac_bijlagen_key"
                    + "   AND b.fac_bijlagen_verwijder IS NULL"
                    + "   AND r.fac_bijlagen_verwijder IS NULL"
                    + "   AND b.fac_bijlagen_module = " + safe.quoted_sql(v_module)
                    + "   AND b.fac_bijlagen_refkey = " + meldingopdrachtkey
                    + (kenmerkkey ? " AND b.fac_bijlagen_kenmerk_key = " + kenmerkkey : "")
                    + (bijlagekey ? " AND b.fac_bijlagen_key = " + bijlagekey : "");
            Log2File(3, sql);
            var oRs = Oracle.Execute(sql);
            while (!oRs.eof)
            {
                att_list.push( { f_disk: oRs("fac_bijlagen_disk_directory").Value
                               , f_name: oRs("fac_bijlagen_filename").Value
                               , f_size: oRs("fac_bijlagen_file_size").Value
                               , f_path: S("flexfilespath") + "/" + oRs("fac_bijlagen_disk_directory").Value + "/" + oRs("fac_bijlagen_filename").Value
                               }
                             );
                oRs.MoveNext();
            }
            oRs.Close();

            mergeAttachments(iXml, node, p_xslPath, att_list)
        }
        Log2File(2, "*<<mergeFlexAttachments");
    }
    return iXml;
}

function mergeFixedAttachment(iXml, p_xslPath, xmltag)
{
    Log2File(2, "*>>mergeFixedAttachments");
    var NodeList = iXml.documentElement.selectNodes(xmltag);
    if (NodeList.length > 0)
    {
        var att_list = [];
        var node = NodeList[0]; // Er is er maar 1
        var bdradr_path = (S("bdradrfiles_path") ? S("bdradrfiles_path") : custabspath + "/"+ "bdradrfiles")
                        + "/"+ trimall(node.text);

        var fso = new ActiveXObject("Scripting.FileSystemObject");
        if (fso.FileExists(bdradr_path))
        {
            var f = fso.GetFile(bdradr_path);
            att_list.push( { f_disk: "bdradrfilesx/"
                           , f_name: trimall(node.text)
                           , f_size: f.Size
                           , f_path: bdradr_path
                           }
                         );
            mergeAttachments(iXml, node, p_xslPath, att_list)
        }
    }
    Log2File(2, "*<<mergeFixedAttachments");
    return iXml;
}

function mergeAttachments(iXml, p_node, p_xslPath, att_list)
{
    var nodename = trimall(p_node.attributes.getNamedItem("nodename").text);
    var node_encoding = p_node.attributes.getNamedItem("encoding"); // als die NIET is gevuld doen we default "hex"
    var encoding = "hex";
    if (node_encoding){
        encoding = trimall(node_encoding.text);
    }
    var node_templatenodename = p_node.attributes.getNamedItem("templatenodename"); // als die is gevuld doen we een post-parse met de stylesheet
    var templatenodename = '';
    if (node_templatenodename){
        templatenodename = trimall(node_templatenodename.text);
    }

    for (a=0; a< att_list.length; a++)
    {
        Log2File(1, "Embedding attachment: " + att_list[a].f_path + " (" + att_list[a].f_size + " bytes)");
        // en hier de rest voor het invoegen van de bijlagen.
        var elemAttachments = iXml.createElement(nodename);
        var elemAtt = iXml.createElement("attachment");
        switch (encoding)
        {
            case "hex":    elemAtt.text = Hexify(att_list[a].f_path);
                           break;
            case "base64": elemAtt.text = Base64fy(att_list[a].f_path);
                           break;
            case "localpath": elemAtt.text = att_list[a].f_path;
                            break;
        }
        Log2File(1, "{0} encoded it becomes {1} bytes".format(encoding, elemAtt.text.length));
        elemAttachments.appendChild(elemAtt);
        elemAtt = iXml.createElement("encoding");
        elemAtt.text = encoding;
        elemAttachments.appendChild(elemAtt);
        elemAtt = iXml.createElement("name");
        elemAtt.text = att_list[a].f_name;
        elemAttachments.appendChild(elemAtt);
        elemAtt = iXml.createElement("size");
        elemAtt.text = att_list[a].f_size;
        elemAttachments.appendChild(elemAtt);

        if (templatenodename != '') // post processing met de stylesheet
        {
            var elemWrapper = iXml.createElement(templatenodename);
            elemWrapper.appendChild(elemAttachments);
            var tmp_xmlDoc = SafeLoadTextXML(elemWrapper.xml);
            LogString2File(3, "PostProces", tmp_xmlDoc.xml, "xml");
            var tmp_xmlResult = XML2HTML( tmp_xmlDoc
                                        , p_xslPath
                                        , ""
                                        , "processattachments"
                                        );
            var tmp_xmlDoc = SafeLoadTextXML(tmp_xmlResult);
            LogString2File(3, "PostProcesAttach", tmp_xmlResult, "xml");

            var NodeList2 = tmp_xmlDoc.documentElement.childNodes;
            for (var i=0; i < NodeList2.length; i++)
            {
                var tmp_node = NodeList2[i];
                //Let op: niet appendChild gebruikt, want die voegt achteraan toe aan de parentnode, waardoor de volgorde wordt aangepast.
                //Door gebruikt van insertBefore blijft de volgorde intact, die wezenlijk voor Validatie tegen XSD (van externe partijen) kan zijn!
                p_node.parentNode.insertBefore(tmp_node, p_node);
            }
        }
        else
        {
            p_node.parentNode.appendChild(elemAttachments);
        }
    }
    p_node.parentNode.removeChild(p_node); // dummy fcltattachments weg
}