// some HTTP status constants
const HTTP_CODE_OK                  = 200;
const HTTP_CODE_FORBIDDEN           = 403;
const HTTP_CODE_NOT_FOUND           = 404;
const HTTP_CODE_GONE                = 410;
const HTTP_CODE_SERVICE_UNAVAILABLE = 503;

// XMLHttpRequest.readyState constants
const READY_STATE_UNSENT            = 0;
const READY_STATE_OPENED            = 1;
const READY_STATE_HEADERS_RECEIVED  = 2;
const READY_STATE_LOADING           = 3;
const READY_STATE_DONE              = 4;


// true if the async QR bills request has signaled that it has finished (or an error occurred)
var statusFinished = false;


// Sends a POST request to the given URL.
//
// url:       URL
// action:    action sent to URL
// readyFn:   called if ready state is 1 (READY_STATE_OPENED)
// successFn: called if ready state is 4 (READY_STATE_DONE) and http status is 200 (HTTP_CODE_OK)
// errorFn:   called if ready state is 4 (READY_STATE_DONE) and http status is NOT 200 (HTTP_CODE_OK)
function sendRequest(url, action, readyFn, successFn, errorFn)
{
    var xhttp = new XMLHttpRequest();
    
    xhttp.onreadystatechange = function()
    {
        addDebug("[readyState:" + this.readyState + "/status:" + this.status + "/action:" + action + "]");
    
        if (this.readyState == READY_STATE_OPENED)  // 1: opened
        {
            readyFn(this);
        }
        else
        if (this.readyState == READY_STATE_DONE)    // 4: done
        {
            if (this.status == HTTP_CODE_OK)        // 200: ok
            {
                successFn(this);
            }
            else
            {
                errorFn(this);
            }
        }
    }
    
    xhttp.open("POST", url, true);
    xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhttp.send(action);
}

// Adds the given text to the HTML element "status".
//
// text: string to add to the field
function addStatus(text)
{
    var el = document.getElementById("status");
    el.innerHTML += text;
    el.scrollIntoView({block:"end", inline:"nearest", behavior:"smooth"});
}

// Adds the given text to the HTML element "debug".
//
// text: string to add to the field
function addDebug(text)
{
// remove comment for the following line if you want to see the debug output
//     document.getElementById("debug").innerHTML += "<br />" + text;
}


//////////////////////////////////////////////////////////////////////////////////////////
// Async request

// Processes async QR bills request, starts polling for the status (getProcessStatus).
//
// action: action sent to URL "QrBillsProcessAsync.php"
function processAsync(action)
{
    // clear status
    document.getElementById("status").innerHTML = "";
    
    sendRequest("QrBillsProcessAsync.php",             // URL
                action,                                // action
                
                function(xhttp)                        // readyFn
                {
                    // start status poll in 200 milli seconds, calls getProcessStatus()
                    setTimeout(getProcessStatus, POLL_INTERVAL);
                },
                
                function(xhttp)                        // successFn
                {
                    addDebug("processAsync - successFn: SUCCESS");
                }, 
                
                function(xhttp)                        // errorFn
                {
                    // signal finished to getProcessStatus() poll
                    statusFinished = true;

                    if (xhttp.status == HTTP_CODE_FORBIDDEN)  // 403: forbidden, means locked by another user
                    {
                        addDebug("processAsync - errorFn: Status 403 -> locked -> stop");
                        addStatus("ERROR 403, forbidden: This profile is locked by another user."); 
                    }
                    else
                    if (xhttp.status == HTTP_CODE_SERVICE_UNAVAILABLE)  // 503: service unavailable, known exception
                    {
                        addDebug("processAsync - errorFn: Status 503 -> error -> stop");
                        addStatus("ERROR 503, service unavailable: The connection to the server is broken or processing has crashed."); 
                    }
                    else
                    {
                        // some unforeseen error occurred before the status file was written
                        addDebug("processAsync - errorFn: Status " + xhttp.status + " -> error -> stop");
                        addStatus("FATAL ERROR: " + xhttp.status); 
                    }
                }
    );
}

// Starts async dryRun.
function dryRun()
{
    statusFinished = false;
    processAsync("dryRun");
}

// Starts async testRun with parameter "testEmailAddress".
// Checks if "testEmailAddress" is not empty; if empty then it displays the error message 
// "Need e-mail address" in this field.
function testRun()
{
    var addr = document.getElementById("testEmailAddress").value;
    
    if (addr == "")
    {
        document.getElementById("testEmailAddress").value = "Need e-mail address";
        return;
    }
    
    statusFinished = false;
    processAsync("testRun&testEmailAddress=" + encodeURIComponent(addr));
}

// Starts async processRun.
function processRun()
{
    statusFinished = false;
    processAsync("processRun");
}


//////////////////////////////////////////////////////////////////////////////////////////
// get status

// poll constants
const POLL_INTERVAL   =  200;
const DEFAULT_TIMEOUT = 2000;

// Calls the given conditionFn in the given interval until it returns true or the timeout 
// is over.
//
// conditionFn: checks whether terminating condition is met
// callbackFn:  called if conditionFn returns true
// errorFn:     called if timeout is exceeded
// timeout:     optional timeout in milliseconds, defaults to 2000 (2 seconds)
// interval:    optional poll interval, defaults to 200 (0.2 seconds)
function poll(conditionFn, callbackFn, errorFn, timeout, interval)
{
    var endTime = Number(new Date()) + (timeout || DEFAULT_TIMEOUT);
    interval = interval || POLL_INTERVAL;

    (function p()
    {
        if (conditionFn())
        {
            // If the condition is met, we're done! 
            callbackFn();
        }
        else if (Number(new Date()) < endTime)
        {
            // If the condition isn't met but the timeout hasn't elapsed, go again
            setTimeout(p, interval);
        }
        else
        {
            // Didn't match and too much time, reject!
            errorFn('timed out for ' + conditionFn + ': ' + arguments);
        }
    })();
}

// maximum count for consecutive 404
const MAX_404_Retries = 10;
// count for 404
var countForNotFound = 0;

// Sends a status request to URL "QrBillsProcessStatus.php" with action "getProcessStatus".
function getProcessStatus()
{
    sendRequest(
        "QrBillsProcessStatus.php",     // URL
        "getProcessStatus",             // action
        
        function(xhttp)                 // readyFn
        {
            // polls until statusFinished
            poll(
                function()              // conditionFn: loop as long as not finished
                {
                    return (!statusFinished);
                },
                function()              // callbackFn: done
                {
                    addDebug("getProcessStatus - readyFn: SUCCESS, Status " + xhttp.status + ", text=" + xhttp.responseText);
                },
                function(errorText)     // errorFn: error
                {
                    addDebug("getProcessStatus - readyFn: ERROR, errorText=" + errorText);
                }
                //, timeout (default 2000ms), interval (default 100ms)
            );
        },
        
        function(xhttp)                 // successFn
        {
            if (statusFinished) return;
            
            addDebug("getProcessStatus - successFn: Status 200 -> poll again, text=" + xhttp.responseText);
            addStatus(xhttp.responseText); 
            countForNotFound = 0;
            setTimeout(getProcessStatus, POLL_INTERVAL);    // poll again
        },
        
        function(xhttp)                 // errorFn
        {
            if (statusFinished) return;
            
            if (xhttp.status == HTTP_CODE_NOT_FOUND)            // 404: not found, means status file does not yet exist, so poll again
            {
                countForNotFound++;
                
                if (countForNotFound > MAX_404_Retries) // 10 times (* POLL_INTERVAL ms) no status file found => looks like server crashed
                {
                    statusFinished = true;
                    addDebug("getProcesStatus - errorFn: Status 404 -> 10 retries exceeded -> stop");
                    addStatus("ERROR: Server not responding"); 
                }
                else
                {
                    addDebug("getProcessStatus - errorFn: Status 404 -> poll again");
                    setTimeout(getProcessStatus, POLL_INTERVAL);    // poll again
                }
            }
            else
            if (xhttp.status == HTTP_CODE_GONE)                 // 410: gone, means EOF read
            {
                statusFinished = true;
                addDebug("getProcessStatus - errorFn: Status 410 -> EOF -> stop");
            }
            else
            {
                statusFinished = true;
                addDebug("getProcessStatus - errorFn: Status " + xhttp.status + " -> error -> stop");
                addStatus("ERROR: Server error"); 
            }
        }
    );
}
