/*
    $Revision$
    $Id$

    File:           Exchange.js

    Description:    Exchange EWS koppeling
                    Dit bestand maakt voor één ruimte-mailbox Arguments(0) de xml-bestanden
                    met calenderitems aan sinds de laatste sync-actie, Arguments(1)

    Parameters      (0) e-mail adres van de zaal
                    (1) "EXCHFULL" calendarview-import (in de toekomst tot config.fullfuture)
                        alle andere waarden: synchronisatie-import (voorheen werd hier de syncstate in meegegeven)
*/

var fso = new ActiveXObject("Scripting.FileSystemObject");
var zaalemail = WScript.Arguments(0);
var import_app_key = WScript.Arguments(1); // Gebruik EXCHFULL voor alles in bepaalde periode

var inifile = ".\\exchange.config.js";
var f = fso.OpenTextFile(inifile, 1); // ForReading
var config = eval('(' + f.ReadAll() + ')')
f.Close();
config.loglevel = config.loglevel || 0;

WScript.Echo("Connecting to " + config.endpointurl)
// ---
// kunnen we niets mee doExchange(config.endpointurl, "<m:GetRoomLists />", "roomlist.xml");

if (!Date.prototype.toISOString) {
    Date.prototype.toISOString = function() {
        function pad(n) { return n < 10 ? '0' + n : n }
        return this.getUTCFullYear() + '-'
            + pad(this.getUTCMonth() + 1) + '-'
            + pad(this.getUTCDate()) + 'T'
            + pad(this.getUTCHours()) + ':'
            + pad(this.getUTCMinutes()) + ':'
            + pad(this.getUTCSeconds()) + 'Z';
    };
}

var oCrypto = new ActiveXObject("SLNKDWF.Crypto");

if (import_app_key != "EXCHFULL")
{
    var udl = "../Oracle.udl";
    var Oracle = new ActiveXObject("ADODB.Connection");
    Oracle.Open('File Name='+udl);

    var sql = "SELECT res_ruimte_syncstate"
            + "  FROM res_ruimte"
            + " WHERE res_ruimte_verwijder IS NULL"
            + "   AND res_ruimte_extern_id = '" + zaalemail + "'";
    var oRs = Oracle.Execute(sql);
    var zaalsync = oRs("res_ruimte_syncstate").Value||"";
    oRs.Close();
    var sha = oCrypto.hex_sha1(zaalsync); // Gemakkelijker verschillen te zien
    WScript.Echo("\n\n==== Room: " + zaalemail + "\nOld sync hash: " + sha);

    var soapRequest = '<m:SyncFolderItems'
                    + '            xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
                    + '            xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
                    + '   <m:ItemShape>'
                    + '     <t:BaseShape>IdOnly</t:BaseShape>'
                    + '     <t:AdditionalProperties>'
                    + '        <t:FieldURI FieldURI="calendar:CalendarItemType" />'
                    + '        <t:FieldURI FieldURI="calendar:Start" />'
                    + '     </t:AdditionalProperties>'
                    + '   </m:ItemShape>'
                    + '   <m:SyncFolderId>'
                    + '     <t:DistinguishedFolderId Id="calendar">'
                    + '       <t:Mailbox>'
                    + '           <t:EmailAddress>' + zaalemail + '</t:EmailAddress>'
                    + '       </t:Mailbox>'
                    + '     </t:DistinguishedFolderId>'
                    + '   </m:SyncFolderId>'
                    + '   <m:SyncState>' + zaalsync + '</m:SyncState>'
                    + '   <m:MaxChangesReturned>' + config.maxchange + '</m:MaxChangesReturned>'
                    + '   <m:SyncScope>NormalItems</m:SyncScope>'
                    + '</m:SyncFolderItems>';
}
else
{
    var dtFrom = new Date();
    dtFrom.setHours(0, 0, 0, 0);
    var dtTo = new Date(dtFrom);
    dtFrom.setDate(dtFrom.getDate() - config.fullpast);
    dtTo.setDate(dtTo.getDate() + config.fullfuture);
    WScript.Echo("Full syncing from " + dtFrom.toISOString() + " to " + dtTo.toISOString() + " (" + (config.fullfuture + config.fullpast) + " days)");
    var soapRequest = '<m:FindItem Traversal="Shallow"'
                    + '            xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
                    + '            xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
                    + '  <m:ItemShape>'
                    + '    <t:BaseShape>IdOnly</t:BaseShape>'
                    + '    <t:AdditionalProperties>'
                    + '        <t:FieldURI FieldURI="calendar:CalendarItemType" />'
                    + '        <t:FieldURI FieldURI="calendar:Start" />'
                    + '    </t:AdditionalProperties>'
                    + '  </m:ItemShape>'
                    //+ '  <m:CalendarView StartDate="2016-01-01T17:30:24.127Z" EndDate="2016-05-20T17:30:24.127Z" />'
                    + '  <m:CalendarView StartDate="' + dtFrom.toISOString() + '" EndDate="' + dtTo.toISOString() + '" />'
                    + '  <m:ParentFolderIds>'
                    + '     <t:DistinguishedFolderId Id="calendar">'
                    + '       <t:Mailbox>'
                    + '           <t:EmailAddress>' + zaalemail + '</t:EmailAddress>'
                    + '       </t:Mailbox>'
                    + '     </t:DistinguishedFolderId>'
                    + '  </m:ParentFolderIds>'
                    + '</m:FindItem>';
}

var room_id = safefilename(zaalemail);
var exch = doExchange(config.endpointurl, soapRequest, null);
if (!exch)
    WScript.Quit(1);

var xmlDoc = exch.doc;
var rescode = exch.response;
if (exch.response != 'NoError')
{
    WScript.Echo("Geen NoError responsecode in response: " + exch.response);
    WScript.Quit(1);
}

if (import_app_key != "EXCHFULL")
{
    var newsyncstate = xmlDoc.selectSingleNode("//m:SyncState");
    var sha = oCrypto.hex_sha1(newsyncstate.text); // Gemakkelijker verschillen te zien
    WScript.Echo("New syncstate: " + newsyncstate.text + "\nHash: " + sha + "\nlength: " + newsyncstate.text.length);
}

var calItems = xmlDoc.selectNodes("//t:CalendarItem");
if (calItems.length == 0)
{
    WScript.Echo("No calitems, no need to update syncstate");
    WScript.Quit(1);
}
// Stukje robuustheid
// Eerst alle *andere* sync_*.xml bestanden verwijderen om te voorkomen dat
// per ongeluk de verkeerde syncstate in onze ruimte terecht komt
try
{
    fso.DeleteFile(config.xmlfolder + "Sync_*.xml");
    // Als hierboven geen files gevonden zijn komen we in de exception
    // en niet in onderstaande echo.
    WScript.Echo("Oude syncfile is verwijderd.");
}
catch(e)
{
    // Neem aan dat gelukkig geen files zijn gevonden
}
__Log2File("Sync_" + room_id + ".xml", xmlDoc.xml); // moet straks gesynced
WScript.Echo("Fetching " + calItems.length + " calendar items");
var curDate = new Date();
for (var i = 0; i < calItems.length; i++)
{
    var calItem = calItems[i];
    var Id = calItem.selectSingleNode("t:ItemId").getAttribute("Id");
    var ChangeKey = calItem.selectSingleNode("t:ItemId").getAttribute("ChangeKey");
    var start = calItem.selectSingleNode("t:Start").text;
    var type = calItem.selectSingleNode("t:CalendarItemType").text;
    //var subject = calItem.selectSingleNode("t:CalendarItemType").text;
    var cal_id = room_id + "_" + i;
    WScript.Echo("  " + cal_id + ": " + start + " type: " + type);

    if (type == "Single" || type == "Occurrence" || type == "Exception")
    {
        var startDate = internal_parsedate(null, start);
        if (startDate.getTime() < curDate.getTime())
        {
            WScript. Echo("    Appointment in the past; skip it.");
            continue;
        }
        var fname = "CalItem_" + cal_id + ".xml";
        getCalenderItem('<t:ItemId Id="' + Id + '" ChangeKey="' + ChangeKey + '" />', fname);
    }
    else if (type == "RecurringMaster") // Haal Occurences en Exceptions op
    {
        getCalenderItem('<t:ItemId Id="' + Id + '" ChangeKey="' + ChangeKey + '" />', "receiveMasterItem_" + cal_id + ".xml");
        // However the compromise with CalendarView is NO restrictions are permitted besides Start and End Date. None.
        // Dat zou nog redelijk zijn als je het tot een specifieke Recurring master kon beperken. Dat kan echter niet.
        // Dan maar op index ophalen
        for (var j = 1; j <= config.maxrecurring; j++)
        {
            var rec_id = cal_id + "_" + j;
            var fname = "CalItem_" + rec_id + ".xml";
            // Wel XML ophalen, maar nog niet wegschrijven; eerst checken of het een toekomstige appointment betreft
            var exch = getCalenderItem('<t:OccurrenceItemId RecurringMasterId="' + Id + '" InstanceIndex="' + j + '" />', null);
            var xmlDoc = exch.doc;
            WScript.Echo("      Child: " + rec_id + " " + exch.response);
            if (exch.response == 'ErrorCalendarOccurrenceIndexIsOutOfRecurrenceRange')
            {
                WScript.Echo("               Tijd om te stoppen");
                break;
            }
            if (exch.response == 'ErrorCalendarOccurrenceIsDeletedFromRecurrence')
            {
                WScript.Echo("               Deleted negeren we");
                continue;
            }
            if (exch.response == 'ErrorAccessDenied')
            {
                WScript.Echo("               Access Denied negeren we");
                continue;
            }
            var start = xmlDoc.selectSingleNode("//t:Start").text;
            var type = xmlDoc.selectSingleNode("//t:CalendarItemType").text;
            var startDate = internal_parsedate(null, start);
            if (startDate.getTime() < curDate.getTime())
            {
                WScript. Echo("        Appointment in the past; skip it.");
                continue;
            }
            // Toekomstig: save
            __Log2File(fname, xmlDoc.xml);
            WScript.Echo("           Start: " + start + " " + type);
        }
    }
}

function getCalenderItem(filter, fname)
{
    var soapRequest = '<m:GetItem xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
                    + '           xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
                    + '   <m:ItemShape>'
                    + '     <t:BaseShape>AllProperties</t:BaseShape>' // TODO: Dit kan heel traag zijn. Alleen opvragen wat gewenst is
                    + '     <t:AdditionalProperties>'
                    + '        <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="35" PropertyType="Binary" />' // 35=PidLidCleanGlobalObjectId
                    + '        <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="3"  PropertyType="Binary" />' //  3=PidLidGlobalObjectId
                    + '        <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="40" PropertyType="String" />' // 40=PidLidOldLocation
                    + '     </t:AdditionalProperties>'
                    + '  </m:ItemShape>'
                    + '  <m:ItemIds>'
                    +     filter // Dit schijnen er ook meer te mogen zijn
                    + '  </m:ItemIds>'
                    + '</m:GetItem>';

    return exch = doExchange(config.endpointurl, soapRequest, fname);
}

function doExchange(url, body, fname)
{
    var xmlDoc = doSOAP(url, body, fname);
    if (!xmlDoc)
        return null;

    var strQuery = "s:Envelope/s:Body/m:ResponseMessages/m:SyncFolderItemsResponseMessage/m:ResponseCode";
    var strQuery = "//m:ResponseCode";
    var rescode = xmlDoc.selectSingleNode(strQuery);
    if (!rescode)
    {
        WScript.Echo("Geen responsecode in response");
        WScript.Quit(1);
    }
    return { response: rescode.text, doc: xmlDoc };
}

function doSOAP(url, body, fname)
{
    var envsoap = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" '
                + '               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  '
                + '               xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
                + '               xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" '
                + '               xmlns:wsi="http://ws-i.org/schemas/conformanceClaim/">'
                + '  <soap:Header>'
                + '    <t:RequestServerVersion Version=\"Exchange2010\" />'
                + '  </soap:Header>'
                + '  <soap:Body>'
                + body
                + '  </soap:Body>'
                + '</soap:Envelope>';
    var res = doHTTP("POST", url, envsoap);
    if (!res)
        return null;
    if (fname)
        __Log2File(fname, res.responseText);
    var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
    xmlDoc.loadXML(res.responseText);
    xmlDoc.setProperty("SelectionLanguage", "XPath");
    xmlDoc.setProperty("SelectionNamespaces", 'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"');
    return xmlDoc;
}

function doHTTP(method, url, body)
{
    //var SXH_PROXY_SET_PROXY = 2;
    var SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS = 2;
    var SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS = 0x3300;

    var objXMLHTTP = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
//    objXMLHTTP.setProxy(SXH_PROXY_SET_PROXY, "127.0.0.1:8888");

    objXMLHTTP.open(method, url, false, config.username, config.password);
    objXMLHTTP.setRequestHeader("Content-Type", "text/xml; charset=utf-8")

    objXMLHTTP.setOption(SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS, SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS);

    if (config.loglevel > 0)
        __Log2File("request.xml", body);
    objXMLHTTP.send(body);

    if (objXMLHTTP.status >= 200 && objXMLHTTP.status <= 299)
    {
        return objXMLHTTP;
    }
	// else: er is iets fout
    __Log2File("response.xml", objXMLHTTP.responseText);
    WScript.Echo(objXMLHTTP.status);
    WScript.Echo(objXMLHTTP.statusText);
    WScript.Echo(objXMLHTTP.responseText);
    return null;
}

function safefilename (naam) // geen 'lage' karakters en geen (back)slashes, *,%,<,>, '"', ':', ';' '?' and '|' of '+'
{
    return naam.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_"); // " syntax highlight correctie
}
function __Log2File(log_file, data)
{
    var utf8Stream = new ActiveXObject("ADODB.Stream");
    utf8Stream.Open();
    utf8Stream.Type = 2;
    utf8Stream.CharSet = "utf-8";
    utf8Stream.WriteText(data);
    utf8Stream.SaveToFile(config.xmlfolder + safefilename(log_file), 2);
    utf8Stream.Close();
}

// From MyJSON in shared.inc
function internal_parsedate(key, value)
{
    var a;
    if (typeof value === 'string') {
        a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
        if (a) {
            return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
        }
    }
    return value;
}