/* Globals */

var DEZE; // context voor alle globale functies vanuit ASP
var Oracle;
var custabspath;

var config;
var token;

/*////////////////////////////////
//                              //
//       Helper functions       //
//                              //
////////////////////////////////*/
function initialize(params)
{
    /* Vul de globals */
    DEZE = params.DEZE;

    __Log   = DEZE.__Log;
    __DoLog = DEZE.__DoLog;
    _AiAi   = DEZE._AiAi;
    safe    = DEZE.safe;

    custabspath = params.custabspath;
    Oracle = DEZE.Oracle;
    customerId = DEZE.customerId;

    config = loadconfig(custabspath + "\\Exchange\\exchange.config");
    if (config === null) {
        return null;
    }
    token = requestToken(config);

    return token;
}

/*////////////////////////////////
//                              //
//        Main functions        //
//                              //
////////////////////////////////*/
function _getSql(CRUD) {
    return "SELECT rrr.res_rsv_ruimte_key,"
         + "       rr.res_ruimte_key,"
         + "       rr.res_ruimte_extern_id,"
         + "       rrr.res_rsv_ruimte_omschrijving,"
         + "       rrr.res_rsv_ruimte_opmerking,"
         + (CRUD == "U" || CRUD == "D"
             ? "   rrr.res_rsv_ruimte_externnr,"         /* ID van het event in de kalender v/d ruimte-resource */
               + " rrr.res_rsv_ruimte_externnr2,"        /* ID van het event in de kalender v/d organisator */
             : "")
         + "       rrr.res_rsv_ruimte_extern_meeting,"
         + "       p_host.prs_perslid_email host_mail,"
         + "       (SELECT prs_perslid_naam_friendly FROM prs_v_perslid_fullnames_all p WHERE p.prs_perslid_key = p_host.prs_perslid_key) host_name,"
         + "       TO_CHAR(rrr.res_rsv_ruimte_van, 'YYYY-MM-DD\"T\"HH24:MI:SS') res_rsv_ruimte_van,"
         + "       TO_CHAR(rrr.res_rsv_ruimte_tot, 'YYYY-MM-DD\"T\"HH24:MI:SS') res_rsv_ruimte_tot,"
         + "       rrr.res_rsv_ruimte_visibility"
         + "  FROM res_rsv_ruimte rrr, res_ruimte_opstelling ro, res_ruimte rr, prs_perslid p_host"
         + " WHERE     rrr.res_ruimte_opstel_key = ro.res_ruimte_opstel_key"
         + "       AND ro.res_ruimte_key = rr.res_ruimte_key"
         + "       AND rrr.res_rsv_ruimte_host_key = p_host.prs_perslid_key"
         + "       AND rrr.res_rsv_ruimte_tot >= TRUNC(SYSDATE) - " + config.fullpast
         + "       AND rrr.res_rsv_ruimte_van <= TRUNC(SYSDATE) + " + config.fullfuture
         + "       AND (   rr.res_ruimte_startdatum IS NULL"
         + "            OR rr.res_ruimte_startdatum <= rrr.res_rsv_ruimte_van)"
         + "       AND (   rr.res_ruimte_vervaldatum IS NULL"
         + "            OR rr.res_ruimte_vervaldatum > rrr.res_rsv_ruimte_tot)"
         + (CRUD == "D"
             ? "   AND rrr.res_rsv_ruimte_verwijder IS NOT NULL"
             : "   AND rrr.res_rsv_ruimte_verwijder IS NULL");
}

function subscription(userPrincipalName, res_ruimte_key, CRUD) {
    if (CRUD == "C") {
        var result = false;
        var notificationUrl = getNotificationUrl(DEZE.customerId);
        if (notificationUrl) {
            result = createSubscription(userPrincipalName, res_ruimte_key, notificationUrl);
        } else {
            __DoLog("Error: Failed to craft notificationUrl for customer [" + DEZE.customerId + "]", "#FF0000");
        }
        return result;
    } else if (CRUD == "R") {
        // Unsupported
    } else if (CRUD == "U") {
        // We updaten liever niet, in plaats daarvan verwijderen we de subscription en creeeren we een nieuwe
    } else if (CRUD == "D") {
        return deleteSubscription(userPrincipalName);
    }
}

function batchToGraph(CRUD, params) { // [C]reate, [R]ead, [U]pdate & [D]elete
    params = params || {};
    var MAX_BATCH = 20; // TODO (effectief) async maken en throttling testen/afhandelen
    var MAX_TIME = 900000; // 15m, de time-out van 20m willen we handmatig voor zijn zodat we niet halvewege een batch eindigen in een AiAi

    /* Handel een hele batch met requests af */
    function _processBatch(batch, httpResponse) {
        var requests = JSON.parse(batch.body).requests; // Dit kan wel veilig want we hebben dit object zojuist zelf stringified
        if (!httpResponse.responses) {
            __DoLog("Error: Geen responses ontvangen van MS Graph", "#FF0000"); // Wat dan wel?;
            __DoLog(httpResponse);
            return false;
        }
        for (var i = 0; i < httpResponse.responses.length; i++) {
            var response = httpResponse.responses[i];
            var request = requests[response.id - 1];
            request.params.callback = resToGraphCallback;

            // Returned true als resToGraphCallback klaar is, of false als het niet goed is gegaan
            if (_handleMsGraphResponse(request, response)) {
                result.done++;
            } else {
                result.failed++;
            }
        }
        return true;
    }

    var sql = _getSql(CRUD);
    if (CRUD == "C") {
        sql += "       AND rrr.res_rsv_ruimte_externsyncdate IS NULL"
             + "       AND rrr.res_rsv_ruimte_externnr IS NULL"
             + "       AND rrr.res_rsv_ruimte_externnr2 IS NULL";
    }
    if (params.res_ruimte_key) {
        sql += "       AND rr.res_ruimte_key = " + params.res_ruimte_key;
    } else if (params.res_rsv_ruimte_key_arr && params.res_rsv_ruimte_key_arr.length) {
        sql += "       AND rrr.res_rsv_ruimte_key IN (" + params.res_rsv_ruimte_key_arr.join(",") + ")";
    } else { // Er moet minstens 1 beperkende voorwaarde zijn (vooralsnog)
        return { "success": false, "warning": "Missing scope" };
    }
    var oRs = Oracle.Execute(sql);
    var result = {
        "success":  true,
        "total":    0,
        "done":     0,
        "failed":   0
    };
    if (oRs.EOF) {
        if (params.res_ruimte_key) {
            __Log("Error: res_ruimte-record met res_ruimte_key = " + params.res_ruimte_key + " niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
        } else /* if (params.res_rsv_ruimte_key_arr && params.res_rsv_ruimte_key_arr.length) */ {
            __Log("Error: rsv_ruimte-record met rsv_ruimte_key IN (" + params.res_rsv_ruimte_key_arr.join(",") + ") niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
        }
        oRs.Close();
        return { "success": false, "warning": "Not found" };
    }
    var start = new Date().getTime();
    var extern_id = oRs("res_ruimte_extern_id").Value;
    while (!oRs.EoF && (new Date().getTime() - start < MAX_TIME)) {
        // Creeer batch request
        var batch = [];
        while (!oRs.EoF && batch.length < MAX_BATCH) {
            var batch_params = { "batch": true };

            // Als we gaan [U]pdaten moeten we wel weten in welke ruimte we zaten
            if ("old_extern_ids" in params && extern_id != params.old_extern_ids[oRs("res_rsv_ruimte_key").Value]) {
                batch_params.old_extern_id = params.old_extern_ids[oRs("res_rsv_ruimte_key").Value];
            }

            var req = _resToGraph(oRs, CRUD, batch_params);
            if (!req || !req.length) {
                oRs.moveNext();
                continue;
            }

            for (var i = 0; i < req.length; i++) {
                if (batch.length) {
                    req[i].dependsOn = [ batch.length ]; // <- sequentieel, zo gebruiken we 1 mailbox kanaal per batch-request
                }
                req[i].id = batch.length + 1;
                batch.push(req[i]);
            }

            result.total += req.length;
            oRs.moveNext();
        }

        if (batch.length) {
            var batch_request = {
                "method": "POST",
                "url": "https://graph.microsoft.com/v1.0/$batch",
                "body": JSON.stringify({ "requests": batch }),
                "headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
                "params": { "async": true, "callback": _processBatch }
            }
            if (!doHTTP(batch_request)) { // Voert de callback uit indien succesvol
                result.failed += batch.length;
            }
        }
    }

    if (!oRs.EoF) {
        __DoLog("Error: Het synchroniseren is voortijdig afgebroken, mogelijk is de maximale tijd van " + MAX_TIME + "ms overschreden", "#FF0000");
        result.warning = "Timed out";
        while (!oRs.EoF) {
            result.failed++;
            oRs.moveNext();
        }
    }
    oRs.Close();
    var summary = result.done + "/" + result.total + " reserveringen gesynchroniseerd naar outlook (" + result.failed + " mislukt)";
    if (result.warning) {
        result.warning += "\n" + summary;
    } else {
        result.toaster = summary
    }
    return result;
}

function resToGraph(rsv_ruimte_key, CRUD, params) // [C]reate, [R]ead, [U]pdate & [D]elete
{
    __Log("Now in res_to_graph.wsc for rsv_ruimte_key (" + CRUD + ") = " + rsv_ruimte_key);
    params = params || {};

    if ((getMSGraphSyncLevel() & 6) == 0) {
        __DoLog("Error: resToGraph aangeroepen zonder schrijf-intenties volgens S(msgraph_sync_level)", "#FF0000");
        return false;
    }

    /* Eerst wat informatie vergaren */
    var sql = _getSql(CRUD)
            + "       AND res_rsv_ruimte_key = " + rsv_ruimte_key;
    var oRs = Oracle.Execute(sql);
    if (oRs.EOF) {
        __Log("Error: rsv_ruimte-record met rsv_ruimte_key = " + rsv_ruimte_key + " niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
        return false;
    }
    var result = _resToGraph(oRs, CRUD, params);
    oRs.Close();
    return result;
}

function _resToGraph(oRs, CRUD, params)
{
    params                      = params || {};
    var roomChanged;
    var rsv_ruimte_key          = oRs("res_rsv_ruimte_key").Value;
    var res_ruimte_extern_id    = oRs("res_ruimte_extern_id").Value; // 'Zaal email'
    var res_subject             = oRs("res_rsv_ruimte_omschrijving").Value || "";
    var res_body                = oRs("res_rsv_ruimte_opmerking").Value || "";
    if (typeof DEZE !== "undefined") {
        res_body = DEZE.shared.stripbbcodes(res_body);
    }
    var res_start               = oRs("res_rsv_ruimte_van").Value;
    var res_end                 = oRs("res_rsv_ruimte_tot").Value;

    var host_name               = oRs("host_name").Value;
    var host_mail               = oRs("host_mail").Value;
    if (CRUD == "U" || CRUD == "D") {
        var roomEventID         = (oRs("res_rsv_ruimte_externnr").Value  || "");
        roomEventID             = roomEventID.split("|")[1] || roomEventID.split("|")[0]; // <- COALESCE(occurrenceId, eventId)
        var organizerEventID    = oRs("res_rsv_ruimte_externnr2").Value || "";
        roomChanged             = ("old_extern_id" in params);
    }
    var extern_meeting          = oRs("res_rsv_ruimte_extern_meeting").Value; // Online meeting provider
    var visibility              = oRs("res_rsv_ruimte_visibility").Value;

    if ((CRUD == "U" || CRUD == "D") && (getMSGraphSyncLevel() & 4)) {
        if (organizerEventID == "##PENDING##") { // Ooit door Facilitor opgestuurd maar nooit goed gekoppeld (FOUT)
            var thisEvent = getCalendarEventByRsv(host_mail, rsv_ruimte_key); // Is deze afspraak al wel bekend in Exchange?
            if (thisEvent === null) { /* Niet gevonden */
                if (CRUD == "U") {
                    return _resToGraph(oRs, "C", params); // Dan maken we hem bij deze alsnog aan
                } else {
                    return false; // Niet gevonden [D]eletes hoeft niets mee gedaan te worden.
                }
            } else { /* We hebben hem toch gevonden! */
                organizerEventID = thisEvent.id;
                updateFcltRecord(rsv_ruimte_key, thisEvent, thisEvent.id, { "iCalUId": true });
                /* En doorrr */
            }
        } else if (organizerEventID == "##PENDING_OLD##") {
            __DoLog("FATAL ERROR: Deze (oudere) reservering is niet goed gekoppeld met Exchange", "#FF0000");
            return false;
        }
    }

    if (roomChanged) { // De ruimte is aangepast, deze moeten net even anders
        if (CRUD == "D") {                                  // [D]elete de afspraak van de [oude] ruimte
            res_ruimte_extern_id = params.old_extern_id;    //  -> Verwijder de afspraak gekoppeld aan de [oude] ruimte
        } else if (CRUD == "U") {
            if (res_ruimte_extern_id == null) {  // [Nieuwe] ruimte is niet aan Outlook gekoppeld (De oude wel, anders komen we hier niet)
                if (roomEventID === "" && organizerEventID === "") { // De oude ruimte was wel gekoppeld, maar de reservering zelf niet
                    __Log("Oude reservering was niet gekoppeld -> Geen actie");
                    return false;
                } else {
                    return _resToGraph(oRs, "D", params);   //  -> Verwijder de hele afspraak in Outlook
                }
            } else if (params.old_extern_id == null) {  // [Oude] ruimte was niet aan Outlook gekoppeld
                return _resToGraph(oRs, "C", params);       //  -> Maak een [nieuwe] afspraak in Outlook
            } else {                                    // [Oude] ruimte was ook aan Outlook gekoppeld
                if ((getMSGraphSyncLevel() & 6) == 2) { /* We boeken op naam vd ruimte */
                    var res_D = _resToGraph(oRs, "D", params);  //  -> Verwijder de afspraak van de [oude] ruimte
                    var res_C = _resToGraph(oRs, "C", params);  //  -> Maak een nieuwe afspraak aan voor de [nieuwe] ruimte
                    return (params.batch ? res_D.concat(res_C) : res_C);
                } else if (organizerEventID == "") { // Van deze Outlook reservering moeten we eerst nog (bij de oude ruimte) het organizerEventID opvragen
                    organizerEventID = updateExternnr2(params.old_extern_id, roomEventID, host_mail);
                }
            }
        }
    }

    if (res_ruimte_extern_id == null) {
        __DoLog("Error: resToGraph is aangeroepen voor een reservering van een ruimte zonder Extern ID", "#FF0000");
        return false;
    }

    var userPrincipalName = res_ruimte_extern_id;
    if (getMSGraphSyncLevel() & 4) {
        userPrincipalName = host_mail;
    }

    if (userPrincipalName === undefined || userPrincipalName === null) {
        __DoLog("Error: Kan geen gebruiker vinden namens wie we de reservering kunnen maken", "#FF0000");
        return false;
    }

    if ((CRUD == "U" || CRUD == "D") && organizerEventID == "") {
        if (roomEventID == "") {
            __Log("Deze reservering is niet gekoppeld -> Geen actie");
            return false
        } else if (getMSGraphSyncLevel() & 4) {
            organizerEventID = updateExternnr2(res_ruimte_extern_id, roomEventID, host_mail);
        } else if ((getMSGraphSyncLevel() & 6) == 2) {
            organizerEventID = roomEventID;
        }
    }

    if ((CRUD == "U" || CRUD == "D") && organizerEventID == "") {
        __DoLog("Error: deze reservering is niet muteerbaar (of vindbaar) in Exchange", "#FF0000");
        return false;
    }

    // Event object volgens https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0
    // Wij muteren alleen deze velden
    var data = null;
    if (CRUD != "D")
    {
        var tz = "Europe/Amsterdam";
        if (typeof DEZE !== "undefined") { // Alleen vanuit de browser
            tz = DEZE.Session("time_zone") || DEZE.S("fac_server_timezone");
        }
        data = {
            "start": {
                "dateTime": res_start,
                "timeZone": tz
            },
            "end": {
                "dateTime": res_end,
                "timeZone": tz
            }
        };

        if (CRUD == "C" || visibility) {
            data.subject = res_subject;
        }

        if (extern_meeting == 1) {
            data.isOnlineMeeting = true;
            data.onlineMeetingProvider = "teamsForBusiness";
        } else if (CRUD == "C") { // Van een Teams meeting maken we nooit achteraf meer een niet-teams meeting in Outlook
            data.isOnlineMeeting = false;
        }

        // Voor deze velden ondersteunen we (nog) geen bi-directionele sync, dus alleen initieel meegeven
        if (CRUD == "C") {
            data.body = {
                "contentType": "text", // Simpele keuze = geen html
                "content": res_body
            };
            data.singleValueExtendedProperties = [{
                "id": FCLT_KEY_PROP_ID_NAME,
                "value": String(rsv_ruimte_key)
            }];
         // data.sensitivity = visibility ? "normal" : "private"; (Nog) niet vanuit Facilitor muteerbaar
        }

        /* In deze modus nodigen we de gastheer/vrouw toe als deelnemer */
        var inviteHost = (getMSGraphSyncLevel() & 2) && // We boeken op naam vd Ruimte (anders is de host al de organisator)
                         data.isOnlineMeeting &&        // Dit wordt een online meeting
                         (extern_meeting == 0           // Dit was nog geen online meeting; *Dit werkt niet, voor bestaande rsv kun je dit dus nooit meer veranderen
                            || CRUD == "C");            // Dit was nog geen meeting :)
        if (CRUD == "C" || inviteHost || roomChanged) {
            /* Voeg de ruimte toe
               dit willen we zo min mogelijk doen want als we dit meegeven worden eventuele verrijkingen in Outlook overschreven */
            data.attendees = [];
            if (roomChanged && CRUD == "U") { // Bij [C]reate & [D]elete is dit nvt
                // location leegmaken, wordt vanzelf gevuld met de juiste ruimte
                data.location = {};
                data.locations = [];
                if (getMSGraphSyncLevel() & 4) {
                    data.attendees = getAttendeeList(userPrincipalName, organizerEventID, params.old_extern_id);
                }
            }
            data.attendees.push(
                {
                    "type": "resource",
                    "emailAddress": {
                        "name": res_ruimte_extern_id.split("@")[0],
                        "address": res_ruimte_extern_id
                    }
                }
            );
        }

        if (inviteHost) {
            data.attendees.push(
                {
                    "type": "required",
                    "emailAddress": {
                        "name": host_name,
                        "address": host_mail
                    }
                }
            );
        }
    }

    var url = (params.batch ? "" : "https://graph.microsoft.com/v1.0") + "/users/" + encodeURIComponent(userPrincipalName) + "/events";
    if (CRUD == "U" || CRUD == "D")
        url += "/" + organizerEventID;

    var method;
    switch (CRUD) { // [R]ead is in dit bestand nvt
        case "C": method = "POST";   break;
        case "U": method = "PATCH";  break;
        case "D": method = "DELETE"; break;
        default: _AiAi("INTERNAL ERROR: invalid CRUD-action specified in resToGraph(): CRUD = " + CRUD + "");
    }

    var request = {
        "method": method,
        "url": url,
        "body": data,
        "headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
        "res_rsv_ruimte_key": rsv_ruimte_key,
        "params": {
            "roomChanged": (roomChanged ? "1" : "0")
        }
    }

    /* Maak 'pending' zodat we binnenkomende notificaties kunnen matchen op deze reservering als we nog niet in de callback zijn gekomen */
    if (CRUD == "C" && (getMSGraphSyncLevel() & 4)) {
        var sql = "UPDATE res_rsv_ruimte"
                + "   SET res_rsv_ruimte_externnr = NULL"
                + "     , res_rsv_ruimte_externnr2 = '##PENDING##'"
                + "     , res_rsv_ruimte_externsyncdate = SYSDATE"
                + " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key;
        Oracle.Execute(sql);
    }

    if (params.batch) { // Deze bundelen we eerst in een $batch-request en versturen hem dan pas
        request.batch = true;
        return [request];
    } else {
        request.params.callback = resToGraphCallback;
        __Log("(" + method + ")" + " to: " + url);
        if (data) {
            __Log(data);
            request.body = JSON.stringify(request.body);
        }
    }

    return doHTTP(request);
}

/* Deze query wordt uitgevoerd als we een (succesvol) response van MS Graph ontvangen hebben */
var resToGraphCallback = function _resToGraphCallback(request, msGraphResponse) {
    // We hebben hier nog niet de informatie van het ruimte-resource event, die krijgen we straks via de webhook binnen (indien S(msgraph_sync_level) &4)
    // Vul voor nu alleen het organisator-event-id in bij res_rsv_ruimte_externnr2 zodat we deze res_rsv herkennen in de process_webhook waar we hem straks zullen aanvullen
    var thisEvent, externnr2;
    var params = {
        "onlyPending": (getMSGraphSyncLevel() & 4)
    };
    if (getMSGraphSyncLevel() & 4) {
        if (request.params.roomChanged === "1") {
            thisEvent = msGraphResponse;
            params.iCalUId = true;
            params.onlyPending = false; // Forceer een update van het externnr naar iCalUId
        }
        externnr2 = msGraphResponse.id;
    } else {
        thisEvent = msGraphResponse;
    }
    return updateFcltRecord(request.res_rsv_ruimte_key, thisEvent, externnr2, params);
}

function checkExpiringAuth() {
    return doCheckExpiringAuth();
}

function getCalendarEvent(method, userPrincipalName, id) {
    if (method == "id") {
        return getCalendarEventByID(userPrincipalName, id);
    } else if (method == "iCalUId") {
        return getCalendarEventByICalUId(userPrincipalName, id);
    } else if (method == "res_rsv_ruimte_key") {
        return getCalendarEventByRsv(userPrincipalName, id);
    } else if (method == "date") {
        var date_from = id.split("##")[0];
        var date_to = id.split("##")[1];
        return getCalendarEventsByDate(userPrincipalName, date_from, date_to);
    } else {
        return false;
    }
}