
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// Barcode scanning functions
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//

// Last time barcode input text changed
var BarcodeScanTextAreaId = "";
var LastInputTextTime = new Date();
var BarcodeInputTimerID = 0;
var BarcodeClearTimerID = 0;
var RequiredScanTimerID = 0;
var RequiredScanPromptState = {};



function OnBarcodeContextMenu(event, element) {

    event.preventDefault();
    AjaxGetBarcodeContextMenu(event);
}


/**
 * onkeyup event handler for BarcodeScanTextArea
 * @param {any} event
 * @returns
 */
function OnBarcodeKeyUp(event) {

    //console.log("OnBarcodeKeyUp Event: " + event);
    //console.log("OnBarcodeKeyUp currentTarget: " + event.currentTarget);

    // Call Ajax for scan results if last input was 250ms ago
    if (!event)
        return;
    if (!event.currentTarget)
        return;

    var element = event.currentTarget;
    var BarcodeInputTextObject = $(element);
    if (!BarcodeInputTextObject)
        return;

    //console.log("OnBarcodeKeyUp BarcodeScanTextAreaId: " + BarcodeScanTextAreaId);
    //console.log("OnBarcodeKeyUp BarcodeText: " + BarcodeInputTextObject.val());

    BarcodeScanTextAreaId = BarcodeInputTextObject.attr("id");
    if (!BarcodeScanTextAreaId)
        return;

    var BarcodeText = BarcodeInputTextObject.val();
    if (!BarcodeText)
        return;

    RestartBarcodeInputTimer();
    //console.log("OnBarcodeKeyUp setTimeout: " + BarcodeInputTimerID);
    //console.log("OnBarcodeKeyUp End: " + BarcodeText);
}


function OnBarcodeKeyDown(event) {

    //console.log("OnBarcodeKeyDown Event: " + event);

    if (!event)
        return;
    if (!event.currentTarget)
        return;

    // Special handling for tab key
    // Don't let tab key kill focus and go to next element
    // Instead, include TAB character in the text value
    // This happens because QR codes sometimes include tab characters
    if (event.keyCode == 9/*TAB*/) {

        var element = event.currentTarget;
        var BarcodeInputTextObject = $(element);
        if (!BarcodeInputTextObject)
            return;

        var BarcodeText = BarcodeInputTextObject.val();
        if (!BarcodeText)
            BarcodeText = "";
        BarcodeText += "\t";

        BarcodeInputTextObject.val(BarcodeText);
        if (event.preventDefault)
            event.preventDefault();
    }
}


function SendCrLfBarcodeInput() {

    if (!BarcodeScanTextAreaId)
        return;
    var BarcodeInputTextObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextObject)
        return;

    // Try both jQuery and Html focus functions
    BarcodeInputTextObject.focus();
    BarcodeInputTextObject[0].focus();

    var CrPress = jQuery.Event("keyup");
    CrPress.ctrlKey = false;
    CrPress.which = 13;
    CrPress.currentTarget = BarcodeInputTextObject[0];

    var LfPress = jQuery.Event("keyup");
    LfPress.ctrlKey = false;
    LfPress.currentTarget = BarcodeInputTextObject[0];
    LfPress.which = 10;

    var EnterPress = jQuery.Event("keyup");
    EnterPress.ctrlKey = false;
    EnterPress.which = 13;
    EnterPress.key = "Enter";
    EnterPress.currentTarget = BarcodeInputTextObject[0];

    BarcodeInputTextObject.trigger(CrPress);
    BarcodeInputTextObject.trigger(LfPress);
    BarcodeInputTextObject.trigger(EnterPress);
}

function RestartBarcodeInputTimer() {

    LastInputTextTime = new Date();
    if (BarcodeInputTimerID) {
        clearTimeout(BarcodeInputTimerID);
        BarcodeInputTimerID = 0;
    }
    BarcodeInputTimerID = setTimeout(BarcodeInputTimer, 300);
}

function RestartBarcodeClearTimer() {

    if (BarcodeClearTimerID) {
        clearTimeout(BarcodeClearTimerID);
        BarcodeClearTimerID = 0;
    }

    // Barcode Clear options (See File, Options)
    LoadBarcodeClearValuesFromCookies();
    if (!bBarcodeClearIsChecked)
        return;

    // Default: 60 secs
    if (nBarcodeClearSeconds <= 0)
        nBarcodeClearSeconds = 60;
    var BarcodeClearMs = nBarcodeClearSeconds * 1000;
    BarcodeClearTimerID = setTimeout(BarcodeClearTimer, BarcodeClearMs);
}


function OnRequiredScanKeyUp(event) {

    if (!event)
        return;
    if (!event.currentTarget)
        return;

    var element = event.currentTarget;
    var RequiredScanTextAreaElement = $(element);
    if (!RequiredScanTextAreaElement)
        return;

    var RequiredScanText = RequiredScanTextAreaElement.val();
    if (!RequiredScanText)
        return;

    // Call Ajax for scan results if last input was 250ms ago
    LastInputTextTime = new Date();
    if (RequiredScanTimerID) {
        clearTimeout(RequiredScanTimerID);
        RequiredScanTimerID = 0;
    }
    RequiredScanTimerID = setTimeout(RequiredScanTimer, 300);

    console.log("OnRequiredScanKeyUp Begin: " + RequiredScanText);

    // Press Enter to process required scan input
    // This is often included in barcode scans as well
    if (event.key === "Enter" || event.which == 13) {
        console.log("OnRequiredScanKeyUp Key: " + event.which);

        // Clear this input so we don't come back here
        RequiredScanTextAreaElement.val("");

        ProcessRequiredScan(RequiredScanText);
    }

    //console.log("OnBarcodeKeyUp: " + LastInputTextTime);
    //console.log("OnBarcodeKeyUp: " + BarcodeText);
}




/**
 * Check for barcode text every 100ms
 * If 250ms has passed since the last input, the call Ajax for scan results
 * */
function BarcodeInputTimer() {

	// See BarcodesPartialView.cshtml
    //console.log("BarcodeInputTimer BarcodeInputTimerID: " + BarcodeInputTimerID);

    // Clear the ID for this timer
    BarcodeInputTimerID = 0;

    //console.log("BarcodeInputTimer BarcodeScanTextAreaId: " + BarcodeScanTextAreaId);
    //console.log("BarcodeInputTimer BarcodeInputTextObject: " + $("#" + BarcodeScanTextAreaId));
    //console.log("BarcodeInputTimer BarcodeText: " + $("#" + BarcodeScanTextAreaId).val());
    //console.log("BarcodeInputTimer LastInputTextTime: " + LastInputTextTime);

    // Check for value
    var BarcodeInputTextObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextObject)
        return;
    var BarcodeText = BarcodeInputTextObject.val();
    if (!BarcodeText)
        return;
    if (!BarcodeText.length)
        return;
    BarcodeText = BarcodeText.trim();
    if (!BarcodeText.length)
        return;

	// 250ms since last key entered?
	var Now = new Date();
    var millisecondsSinceLastInput = Now.getTime() - LastInputTextTime.getTime();
    //if (millisecondsSinceLastInput > 250 && millisecondsSinceLastInput <= 1000) {
    if (millisecondsSinceLastInput > 250) {

        //console.log("BarcodeInputTimer Clear input: " + BarcodeInputTextObject.val());

        // Clear this input so we don't come back here
        BarcodeInputTextObject.val("");
        LastInputTextTime = new Date();

        // Set a new 60-second timer to clear barcode input
        RestartBarcodeClearTimer();

        //console.log("BarcodeInputTimer AjaxProcessBarcodeScanBegin: " + BarcodeText);

        // Clear previous Required Scan values
        ClearRequiredScanStateAndElements();

        // Call Ajax to get scan results
        AjaxProcessBarcodeScanBegin(BarcodeText);
    }
    else {
        //console.log("---------------> LT 250ms or GTE 1000ms: " + Now + ", " + LastInputTextTime + ", " + millisecondsSinceLastInput);
    }
}


/**
 * Check for barcode text every 100ms
 * If 250ms has passed since the last input, the call Ajax for scan results
 * */
function RequiredScanTimer() {

    // See BarcodesPartialView.cshtml

    // Clear the ID for this timer
    RequiredScanTimerID = 0;

    // Check for value
    var RequiredScanTextAreaElement = $("#RequiredScanTextArea");
    if (!RequiredScanTextAreaElement)
        return;
    var RequiredScanText = RequiredScanTextAreaElement.val();
    if (!RequiredScanText)
        return;
    if (!RequiredScanText.length)
        return;
    RequiredScanText = RequiredScanText.trim();
    if (!RequiredScanText.length)
        return;

    // Special scans to end this session (CANCEL, CLEAR, STOP)
    var bCancel = IsCancelled(RequiredScanText);

    // "I will type the text (not scanned)"
    var RequiredScansTypeChecked = $("#RequiredScansTypeCheckbox").is(':checked');
    if (RequiredScansTypeChecked && !bCancel)
        return;

    // 250ms since last key entered?
    var Now = new Date();
    var millisecondsSinceLastInput = Now.getTime() - LastInputTextTime.getTime();
    if (millisecondsSinceLastInput > 250 && millisecondsSinceLastInput <= 1000) {

        // Clear this input so we don't come back here
        RequiredScanTextAreaElement.val("");
        LastInputTextTime = new Date();

        // Set a new timer to clear barcode input
        if (BarcodeClearTimerID) {
            clearTimeout(BarcodeClearTimerID);
            BarcodeClearTimerID = 0;
        }
        BarcodeClearTimerID = setTimeout(BarcodeClearTimer, 1000 * 60);

        //console.log("Greater than 250ms: " + Now + ", " + LastInputTextTime + ", " + millisecondsSinceLastInput);
        console.log("RequiredScanTimer: " + RequiredScanText);

        // Collect this data, then prompt for the next required scan
        ProcessRequiredScan(RequiredScanText);
    }
}


/**
 * This is necessary to prevent scans from coming in while processing an Ajax call
 * Multiple scans produces multiple Ajax calls, which can return in unpredictable order
 * An unpredictable order of Ajax calls confuses the logic
 * One possible result is hiding the Required Scan window immediately after it is displayed
 * This can occur when the second Ajax call comes in after the third call (out of order)
 * @returns
 */
function DisableBarcodeInputTextArea() {

    var BarcodeInputTextAreaObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextAreaObject)
        return;
    if (BarcodeInputTextAreaObject.length == 0)
        return;

    BarcodeInputTextAreaObject.prop('disabled', true);

    // Slowly animate the "processing" status, in case the Ajax calls take a long time
    // Normally, Ajax calls will return within one second, and there is no need to display a jarring "processing" status for short calls
    // But, if calls take more than five seconds, then show something so users know something is happening
    BarcodeInputTextAreaObject.css('background-image', 'url(\'Content/images/ajax-loader-large.gif\')');
    BarcodeInputTextAreaObject.css('background-repeat', 'no-repeat');
    BarcodeInputTextAreaObject.css('background-size', '0px');
    BarcodeInputTextAreaObject.animate({ backgroundColor: 'green', 'background-size': '32px' }, 5000);
}


function EnableBarcodeInputTextArea() {

    var BarcodeInputTextAreaObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextAreaObject)
        return;
    if (BarcodeInputTextAreaObject.length == 0)
        return;

    BarcodeInputTextAreaObject.css('background-color', 'white');
    BarcodeInputTextAreaObject.prop('disabled', false);
    BarcodeInputTextAreaObject.css('background-image', 'none');
}






/**
 * This is necessary to prevent scans from coming in while processing an Ajax call
 * Multiple scans produces multiple Ajax calls, which can return in unpredictable order
 * An unpredictable order of Ajax calls confuses the logic
 * One possible result is hiding the Required Scan window immediately after it is displayed
 * This can occur when the second Ajax call comes in after the third call (out of order)
 * @returns
 */
function DisableRequiredScanInputTextArea() {

    var RequiredScanTextAreaObject = $("#RequiredScanTextArea");
    if (!RequiredScanTextAreaObject)
        return;
    if (RequiredScanTextAreaObject.length == 0)
        return;

    RequiredScanTextAreaObject.prop('disabled', true);

    // Slowly animate the "processing" status, in case the Ajax calls take a long time
    // Normally, Ajax calls will return within one second, and there is no need to display a jarring "processing" status for short calls
    // But, if calls take more than five seconds, then show something so users know something is happening
    RequiredScanTextAreaObject.css('background-image', 'url(\'Content/images/ajax-loader-large.gif\')');
    RequiredScanTextAreaObject.css('background-repeat', 'no-repeat');
    RequiredScanTextAreaObject.css('background-size', '0px');
    RequiredScanTextAreaObject.animate({ backgroundColor: 'green', 'background-size': '32px' }, 5000);
}

function EnableRequiredScanTextArea() {

    var RequiredScanTextAreaObject = $("#RequiredScanTextArea");
    if (!RequiredScanTextAreaObject)
        return;
    if (RequiredScanTextAreaObject.length == 0)
        return;

    RequiredScanTextAreaObject.prop('disabled', false);
    RequiredScanTextAreaObject.css('background-image', 'none');
    RequiredScanTextAreaObject.stop(true, true);
    RequiredScanTextAreaObject.css('backgroundColor', 'lightyellow');

    //var color = RequiredScanTextAreaObject.css('background-color');
    //console.log("EnableRequiredScanTextArea: " + color);
}






/**
 * Call Ajax to start scan results for this text
 * Examples:
 *     "Ray"
 *     "10200"
 *     "Packaging"
 *     "STOP"
 * @param {any} BarcodeText
 */
function AjaxProcessBarcodeScanBegin(BarcodeText) {

    //console.log("AjaxGetBarcodeScanResults: " + BarcodeText);

    if (!BarcodeText)
        return;
    if (!BarcodeText.length)
        return;

    // Disable scans while processing Ajax
    // This ensures that no new Ajax calls are made during scan processing
    // Multiple Ajax calls could return in the wrong order
    DisableBarcodeInputTextArea();

    var params = {
        "BarcodeText": BarcodeText
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxProcessBarcodeScanBegin",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {

            if (response.Success) {

                if (response.BcDataJsonString) {
                    LastInputTextTime = new Date();
                    var BcData = JSON.parse(response.BcDataJsonString);
                    ProcessBarcodeScanEnd(BcData);
                }
                SetBarcodeInputFocus();
            }
            else {
                ToastError("Scan error", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {

            EnableBarcodeInputTextArea();

            var errormsg = "Error " + xhr.status + ", " + xhr.statusText + "\r\nText: " + BarcodeText;
            ToastError("AjaxProcessBarcodeScanBegin", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}



/**
 * Call Ajax to finish scan results for this text
 * Examples:
 *     "Ray"
 *     "10200"
 *     "Packaging"
 *     "STOP"
 * @param {any} BarcodeText
 */
function AjaxProcessBarcodeScanEnd(BcData) {

    //console.log("AjaxProcessBarcodeScanEnd: " + BarcodeText);

    if (!BcData)
        return;

    //console.log("AjaxProcessBarcodeScanEnd Begin");

    // Convert parameters to a JSON string because MVC binding is not reliable
    var params = {
        "BcData": BcData,
    };
    var ParamsJsonString = JSON.stringify(params);
    params = {
        "BcDataJsonString": ParamsJsonString,
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxProcessBarcodeScanEnd",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {

            // Allow scans again
            // Scanning has been disabled while Ajax calls were processed
            // This was done to prevent multiple Ajax calls that could return out-of-sequence
            EnableBarcodeInputTextArea();
            SetBarcodeInputFocus();

            if (response.Success) {

                if (response.BcDataJsonString) {
                    LastInputTextTime = new Date();

                    var BcData = JSON.parse(response.BcDataJsonString);

                    //console.log("AjaxProcessBarcodeScanEnd: " + BcData.StatusLabel_Text);

                    // Accepted scan
                    // Show the scan for user confirmation
                    if (!BcData.bUnrecognizedScan)
                         ToastMessage("Scan", BcData.sProcessText);
                    else ToastError("Unrecognized", BcData.sProcessText);

                    // Special messages the user should be aware of
                    if (BcData.bTimerStarted || BcData.bTimerStopped || BcData.bScanError) {
                        if (BcData.bScanError)
                             ToastError("Timer", BcData.StatusLabel_Text);
                        else ToastMessage("Timer", BcData.StatusLabel_Text);
                    }

                    // Flash the screen for visual feedback
                    if (BcData.bTimerStarted || BcData.bTimerStopped)
                        StartFlashBarcodeBackgroundTimer(BcData.bTimerStarted);

                    // Play a sound to signify acceptance
                    if (BcData.sAcceptedScanSoundBase64)
                        PlayBase64Sound(BcData.sAcceptedScanSoundBase64);

                    // Pop up messages
                    if (BcData.MessageBoxAlerts && BcData.MessageBoxAlerts.length > 0)
                        OpenMessageBoxAlerts(BcData.MessageBoxAlerts);

                    // See the results of this scan
                    RefreshSelectedContent();
                    SetBarcodeInputFocus();
                }
            }
            else {
                ToastError("AjaxProcessBarcodeScanEnd", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {

            EnableBarcodeInputTextArea();

            var errormsg = "Error " + xhr.status + ", " + xhr.statusText + "\r\nText: " + BarcodeText;
            ToastError("AjaxProcessBarcodeScanEnd", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}





function AjaxCheckBarcodeRequiredScanRules(RequiredScanPrompt) {

    //console.log("AjaxCheckBarcodeRequiredScanRules: " + BarcodeText);

    if (!RequiredScanPrompt)
        return;

    console.log("AjaxCheckBarcodeRequiredScanRules Begin: " + RequiredScanPrompt.sTitle, ", " + RequiredScanPrompt.sField);

    // Disable this so no more input can be entered while inside this Ajax call
    // This prevents multiple Ajax calls, which could return in unpredictable order
    DisableRequiredScanInputTextArea();

    var sPromptJsonString = JSON.stringify(RequiredScanPrompt);
    // Convert parameters to a JSON string because MVC binding is not reliable
    var params = {
        "PromptJsonString": sPromptJsonString,
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxCheckBarcodeRequiredScanRules",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {

            EnableRequiredScanTextArea();

            if (response.Success) {

                // Got a problem?
                if (response.PromptTitle && response.PromptMessage)
                    PopupErrorMessage(response.PromptTitle, response.PromptMessage);

                if (response.Proceed) {
                    console.log("AjaxCheckBarcodeRequiredScanRules Proceed: " + RequiredScanPrompt.sTitle, ", " + RequiredScanPrompt.sField, ", " + RequiredScanPrompt.sValue);

                    // Pop up the next required scan prompt
                    ShowNextRequiredScanPrompt();
                    return;
                }
            }
            else {
                ToastError("AjaxCheckBarcodeRequiredScanRules", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText + "\r\nText: " + BarcodeText;
            ToastError("AjaxCheckBarcodeRequiredScanRules", errormsg);
            EnableRequiredScanTextArea();
            console.log('AJAX: ' + errormsg);
        }
    });
}




function AjaxCheckBarcodeRequiredScanConditions(RequiredScanPrompt) {

    //console.log("AjaxCheckBarcodeRequiredScanConditions: " + BarcodeText);

    if (!RequiredScanPrompt)
        return;

    console.log("AjaxCheckBarcodeRequiredScanConditions Begin: " + RequiredScanPrompt.sTitle, ", " + RequiredScanPrompt.sField);

    // Disable this so no more input can be entered while inside this Ajax call
    // This prevents multiple Ajax calls, which could return in unpredictable order
    DisableRequiredScanInputTextArea();

    var sPromptJsonString = JSON.stringify(RequiredScanPrompt);
    // Convert parameters to a JSON string because MVC binding is not reliable
    var params = {
        "PromptJsonString": sPromptJsonString,
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxCheckBarcodeRequiredScanConditions",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {

            EnableRequiredScanTextArea();

            if (response.Success) {

                // Got a problem?
                if (response.PromptTitle && response.PromptMessage)
                    PopupErrorMessage(response.PromptTitle, response.PromptMessage);


                // Display the prompt?
                if (response.DisplayPrompt) {
                    console.log("AjaxCheckBarcodeRequiredScanConditions Display: " + RequiredScanPrompt.sTitle, ", " + RequiredScanPrompt.sField);

                    // Make sure the input is enabled
                    EnableRequiredScanTextArea();

                    // Name and prompt
                    $("#RequiredScansNameDiv").html(RequiredScanPrompt.sTitle);
                    $("#RequiredScansPromptDiv").html(RequiredScanPrompt.sPrompt);
                    $("#RequiredScanTextArea").val("");
                    if (RequiredScanPrompt.sInitialValue)
                        $("#RequiredScanTextArea").val(RequiredScanPrompt.sInitialValue);
                    $("#RequiredScansTypeCheckbox").prop('checked', !RequiredScanPrompt.bScanValue);

                    // Take input in a special textarea for require scans only
                    ShowRequiredScanInput(true);
                }
                else {
                    console.log("AjaxCheckBarcodeRequiredScanConditions NO Display: " + RequiredScanPrompt.sTitle, ", " + RequiredScanPrompt.sField);

                    // Prompt not displayed, try the next one
                    // Try the next required scan prompt
                    ShowNextRequiredScanPrompt();
                    return;
                }
            }
            else {
                ToastError("AjaxCheckBarcodeRequiredScanConditions", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText + "\r\nText: " + BarcodeText;
            ToastError("AjaxCheckBarcodeRequiredScanConditions", errormsg);
            EnableRequiredScanTextArea();
            console.log('AJAX: ' + errormsg);
        }
    });
}





/**
 * Clear the barcode scan inputs and values
 */
function AjaxBarcodeClear() {

    //console.log("AjaxBarcodeClear: " + BarcodeText);

    var params = {
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxBarcodeClear",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
                ShowRequiredScanInput(false);
                EnableBarcodeInputTextArea();
                RefreshSelectedContent();
                SetBarcodeInputFocus();
            }
            else {
                ToastError("AjaxBarcodeClear", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText;
            ToastError("AjaxBarcodeClear", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}




function AjaxBarcodeCancel() {

    //console.log("AjaxBarcodeCancel: " + BarcodeText);

    var params = {
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxBarcodeCancel",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
                ShowRequiredScanInput(false);
                RefreshSelectedContent();
                SetBarcodeInputFocus();
            }
            else {
                ToastError("AjaxBarcodeCancel", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText;
            ToastError("AjaxBarcodeCancel", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}




function AjaxGetBarcodeContextMenu(event) {

    //console.log("AjaxGetBarcodeContextMenu: " + BarcodeText);

    var params = {
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxGetBarcodeContextMenu",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
                if (response.ContextMenuItems) {
                    var menuitems = JSON.parse(response.ContextMenuItems);
                    var menu = new ContextMenu(menuitems);
                    menu.display(event);
                }
            }
            else {
                ToastError("AjaxGetBarcodeContextMenu", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText;
            ToastError("AjaxGetBarcodeContextMenu", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}





/**
 * Pop up any required scans or messages, then complete the scan
 * @param {any} BcData
 */
function ProcessBarcodeScanEnd(BcData) {

    if (!BcData) {
        ToastError("Scan error", "No BcData to process");
        return;
    }

    // Error results
    //StatusLabel_Text = sError;
    //Errors.Add(sError);
    //StatusLabel_ForeColor = Color.Red;
    //bScanError = true;
    if (BcData.Errors && BcData.Errors.length > 0) {
        PopupBarcodeErrors(BcData);
        return
    }

    // Prompt for values, then end the scan once all input is taken
    if (BcData.Prompts && BcData.Prompts.length > 0 && !BcData.bUnrecognizedScan) {
        PopupBarcodePrompts(BcData);
        return;
    }

    // End the scan
    AjaxProcessBarcodeScanEnd(BcData);
    SetBarcodeInputFocus();
    ShowRequiredScanInput(false);

    //RefreshSelectedContent();
    //console.log("ProcessBarcodeScanEnd: " + BcDataValues.EmployeeLabel_Text);
}



/**
 * Popup barcode prompts, then complete the scan
 * @param {any} BcDataValues
 */
function PopupBarcodePrompts(BcData) {

    if (!BcData)
        return;

    // Show the special "Required Scans" block to take input for prompts
    ShowRequiredScanInput(true);

    // Set up a state variable that will prompt for required scans
    RequiredScanPromptState = {};
    RequiredScanPromptState.BcData = BcData;
    RequiredScanPromptState.idxPrompt = -1;
    ShowNextRequiredScanPrompt();
}

function ClearRequiredScanStateAndElements() {

    RequiredScanPromptState = {};

    // Clear
    $("#RequiredScansNameDiv").html("");
    $("#RequiredScansPromptDiv").html("");
    $("#RequiredScanTextArea").val("");

    // Hide
    $("#RequiredScansDiv").hide();
}

function PopupBarcodeErrors(BcData) {

    if (!BcData)
        return;
    if (!BcData.Errors)
        return;
    if (BcData.Errors.length == 0)
        return;

    // Status label html object
    BarcodeStatusLabelObject = $("#BarcodeStatusLabel");
    if (!BarcodeStatusLabelObject)
        return;
    if (BarcodeStatusLabelObject.length == 0)
        return;

    // Error results from BcData.cs
    //StatusLabel_Text = sError;
    //Errors.Add(sError);
    //StatusLabel_ForeColor = Color.Red;
    //bScanError = true;

    var sError = BcData.Errors[0];
    BarcodeStatusLabelObject.html(sError);
    BarcodeStatusLabelObject.css("color", "red");
}


/**
 * Step through the required scan prompts
 * */
function ShowNextRequiredScanPrompt() {

    // All done?
    if (!RequiredScanPromptState)
        return;
    if (!RequiredScanPromptState.BcData)
        return;
    if (!RequiredScanPromptState.BcData.Prompts)
        return;

    console.log("ShowNextRequiredScanPrompt Begin: " + RequiredScanPromptState.idxPrompt);

    // Next prompt
    RequiredScanPromptState.idxPrompt++;

    // End of prompts
    if (RequiredScanPromptState.idxPrompt >= RequiredScanPromptState.BcData.Prompts.length) {

        console.log("ShowNextRequiredScanPrompt Finished: " + RequiredScanPromptState.idxPrompt);

        // End the barcode scan
        AjaxProcessBarcodeScanEnd(RequiredScanPromptState.BcData);
        return;
    }

    // Set up the prompt
    var NextPrompt = RequiredScanPromptState.BcData.Prompts[RequiredScanPromptState.idxPrompt];
    if (!NextPrompt)
        return;


    AjaxCheckBarcodeRequiredScanConditions(NextPrompt);

// OLD
// Now, must first check the prompt conditions to decide if the prompt should be shown
// This is a hack for "ENDTIMER" to only show prompts that match the conditions for the ENDTIMER project
//    // Make sure the input is enabled
//    EnableRequiredScanTextArea();

//    // Name and prompt
//    $("#RequiredScansNameDiv").html(NextPrompt.sTitle);
//    $("#RequiredScansPromptDiv").html(NextPrompt.sPrompt);
//    $("#RequiredScanTextArea").val("");
//    if (NextPrompt.sInitialValue)
//        $("#RequiredScanTextArea").val(NextPrompt.sInitialValue);
//    $("#RequiredScansTypeCheckbox").prop('checked', !NextPrompt.bScanValue);

//    // Take input in a special textarea for require scans only
//    ShowRequiredScanInput(true);
}



/**
 * Clear the barcode page every 60 seconds
 * */
function BarcodeClearTimer() {

//    var Now = new Date();
//    console.log("BarcodeClearTimer: " + Now);

    // Clear the ID for this timer
    BarcodeClearTimerID = 0;

    AjaxBarcodeClear();
    SetBarcodeInputFocus();
}


var LastSetBarcodeInputFocus = new Date();
var FirstSetBarcodeInputFocus = true;
function SetBarcodeInputFocus() {

    if (!BarcodeScanTextAreaId)
        return;
    var BarcodeInputTextObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextObject || !BarcodeInputTextObject.length)
        return;

    // Prevent infinite recursion
    // This occurs because of OnEndActivePaneChanged
    //var Now = new Date();
    //var millisecondsSinceLastSetFocus = Now.getTime() - LastSetBarcodeInputFocus.getTime();
    //if (millisecondsSinceLastSetFocus < 500)
    //    return;

     // Prevent infinite recursion
    // This occurs because of OnEndActivePaneChanged
    // When a dialog is open and you try to set focus to a control, you trigger a new ActivePane event
    // This function will be called repeatedly while the dialog box is open
    // The results are flickering of controls
    if (bMessageBoxDialogIsOpen || bPromptForValueDialogIsOpen || bWarnDeleteDialogIsOpen)
        return;

    //console.log("SetBarcodeInputFocus: " + LastSetBarcodeInputFocus + (new Error()).stack);

    LastSetBarcodeInputFocus = new Date();
    BarcodeInputTextObject.focus();
    BarcodeInputTextObject[0].focus();

    // Try a second time with a delay
    setTimeout(function () { BarcodeInputTextObject[0].focus(); }, 500);
    setTimeout(function () { BarcodeInputTextObject[0].focus(); }, 1000);

    // Send the barcode defaults to the server for use during scanning
    if (FirstSetBarcodeInputFocus) {
        FirstSetBarcodeInputFocus = false;
        setTimeout(BarcodeClearTimer, 100);
        setTimeout(AjaxSetBarcodeDefaults, 200);
    }
}

function SetBarcodeInputFocusById(elementid) {

    //console.log("SetBarcodeInputFocusById: " + elementid);
    //ToastError("elementid", elementid);

    BarcodeScanTextAreaId = elementid;
    SetBarcodeInputFocus();
}

function SetBarcodeInputFocusByDataValue(element) {

    if (!element)
        return;

    // Special value atttribute stored in the element
    // In this case, used as an ID for the input textarea
    var DataValue = $(element).attr("data-value");
    if (!DataValue)
        return;

    SetBarcodeInputFocusById(DataValue);
}



function SetRequiredScanInputFocus() {

    var RequiredScanTextElement = $("#RequiredScanTextArea");
    if (!RequiredScanTextElement)
        return;
    RequiredScanTextElement.focus();
}




/**
 * Show a special div for taking prompt input for "Required Scans"
 * * Example:
 *          Scan or type the number of items produced:
 *          "34"
 @param {any} bShow
 */
function ShowRequiredScanInput(bShow) {

    if (bShow) {
        // Show a special div for require scan prompts
        $("#RequiredScansDiv").show();
        $("#BarcodeScanTextAreaDiv").hide();
        $("#BarcodeMicButton").hide();
        SetRequiredScanInputFocus();
    }
    else {
        // Show normal barcode input div
        $("#RequiredScansDiv").hide();
        $("#BarcodeScanTextAreaDiv").show();
        $("#BarcodeMicButton").show();
        RequiredScanPromptState = {};
        SetBarcodeInputFocus();
    }
}


/**
 * Process a single "Required Scan" text input
 * Example:
 *          Scan or type the number of items produced:
 *          "34"
 * Check for another required scan prompt, or end the scan sequence
 * @param {any} RequiredScanText
 */
function ProcessRequiredScan(RequiredScanText) {

    //console.log("ProcessRequiredScan: " + RequiredScanText);

    if (!RequiredScanText)
        return;

    console.log("ProcessRequiredScan Begin: " + RequiredScanText);

    // Special scans to end this session (CANCEL, CLEAR, STOP)
    var bCancel = IsCancelled(RequiredScanText);
    if (bCancel) {
        OnRequiredScansCancel();
        return;
    }

    if (RequiredScanPromptState.idxPrompt < 0)
        return;
    if (RequiredScanPromptState.idxPrompt >= RequiredScanPromptState.BcData.Prompts.length)
        return;

    var ThisPrompt = RequiredScanPromptState.BcData.Prompts[RequiredScanPromptState.idxPrompt];
    if (!ThisPrompt)
        return;

    // Save the value
    ThisPrompt.sValue = RequiredScanText;

    // Check the results using Ajax call to server
    AjaxCheckBarcodeRequiredScanRules(ThisPrompt);

    // Pop up the next required scan prompt
    // Moved to Ajax function
    //ShowNextRequiredScanPrompt();
}


function IsCancelled(ScanText) {

    if (!ScanText)
        return false;

    ScanText = ScanText.trim();
    if (!ScanText)
        return false;

    // Special scans to end this session
    var ScanText = ScanText.toLowerCase();
    var bCancel = (ScanText === "cancel" || ScanText === "clear" || ScanText === "stop");
    return bCancel;
}



/**
 * User clicked a button to "Accept" the current required scan prompt
 * */
function OnRequiredScansOk() {

    // Get the value from the textarea input
    var RequiredScanTextAreaElement = $("#RequiredScanTextArea");
    if (!RequiredScanTextAreaElement)
        return;
    var RequiredScanText = RequiredScanTextAreaElement.val();
    if (!RequiredScanText)
        return;
    if (!RequiredScanText.length)
        return;
    RequiredScanText = RequiredScanText.trim();
    if (!RequiredScanText.length)
        return;

    console.log("OnRequiredScansOk Begin: " + RequiredScanText);

    // Collect this data, then prompt for the next required scan
    ProcessRequiredScan(RequiredScanText);
}


/**
 * User cancelled the current required scan prompt
 * */
function OnRequiredScansCancel() {

    AjaxBarcodeCancel();
    AjaxBarcodeClear();

    // Hide "Required Scans" div
    ShowRequiredScanInput(false);
}


function OnRequiredScansTypeCheck(checkbox) {

    // Make the user type more to trigger the automatic timer for scanning
    LastInputTextTime = new Date();

    // Put the focus back on this input
    setTimeout(function () { SetRequiredScanInputFocus(); }, 500);
}


function LargerText() {

    // TODO: P6 Connect this to Ctrl+Plus and Ctrl+Minus so any page can use this functionality
    // TODO: P9 Save font "ups" to cookie, then restore when initialized
    var SelectedContentPartialViewDivElement = GetSelectedContentPartialViewDivElement();
    ChildrenFontSizeUp(SelectedContentPartialViewDivElement, true/*bUp*/);
}

function SmallerText() {

    // TODO: P9 Save font "downs" to cookie, then restore when initialized
    var SelectedContentPartialViewDivElement = GetSelectedContentPartialViewDivElement();
    ChildrenFontSizeUp(SelectedContentPartialViewDivElement, false/*bUp*/);
}

function BarcodeDefaults() {

    // Show "barcode defaults" properties
    // These properties allow users to set defaults that are used for every scan
    // This reduces the number of scans required
    // Example:
    //      User: Buzz
    RefreshBarcodeDefaultsPropertyGridView();
}



function AjaxSetBarcodeDefaults() {

    //console.log("AjaxSetBarcodeDefaults: " + BarcodeText);

    var params = {
        "sDefaultUserId": getCookie("ST-BarcodeDefaults-User_Property"),
        "sDefaultProjectId": getCookie("ST-BarcodeDefaults-Project_Property"),
        "sDefaultTaskId": getCookie("ST-BarcodeDefaults-ProjectTask_Property"),
        "sDefaultTaskName": getCookie("ST-BarcodeDefaults-ProjectTaskName_Property"),
        "sDefaultCategoryId": getCookie("ST-BarcodeDefaults-Category_Property"),
        "sDefaultText1": getCookie("ST-BarcodeDefaults-Text1_Property"),
        "sDefaultText2": getCookie("ST-BarcodeDefaults-Text2_Property"),
        "sDefaultText3": getCookie("ST-BarcodeDefaults-Text3_Property"),
        "sDefaultLocation": getCookie("ST-BarcodeDefaults-Location_Property"),
        "sDefaultDevice": getCookie("ST-BarcodeDefaults-Device_Property"),
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxSetBarcodeDefaults",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
            }
            else {
                ToastError("AjaxSetBarcodeDefaults", response.ErrorResults);
            }
            SetBarcodeInputFocus();
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText + "\r\nText: " + BarcodeText;
            ToastError("AjaxSetBarcodeDefaults", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


var nFlashBackgroundCyles = 0;
var bBarcodeScanTimerStarted = false;
var BarcodeContentDivBkColor = null;
var BarcodeContentBtnImageSrc = null;
var BarcodeContentDivFlashColor = null;
var BarcodeBackgroundTimerInterval = null;
var BarcodeHelpButtonStyle = null;

function StartFlashBarcodeBackgroundTimer(bTimerStarted) {

    var BarcodeContentDiv = $("#BarcodeScanContentDiv");
    var BarcodeHelpButton = $("#BarcodeHelpButton");
    if (!BarcodeContentDiv)
        return;
    if (BarcodeContentDiv.length == 0)
        return;

    bBarcodeScanTimerStarted = bTimerStarted;
    nFlashBackgroundCyles = 0;

    // Stop previous timer, if it is running
    if (BarcodeBackgroundTimerInterval && BarcodeBackgroundTimerInterval != null) {

        clearInterval(BarcodeBackgroundTimerInterval);
        BarcodeBackgroundTimerInterval = null;
        if (BarcodeContentDivBkColor && BarcodeContentDivBkColor != null)
            BarcodeContentDiv[0].style.backgroundColor = BarcodeContentDivBkColor;
    }

    if (BarcodeContentDivBkColor == null)
        BarcodeContentDivBkColor = BarcodeContentDiv[0].style.backgroundColor;
    BarcodeContentDivFlashColor = bBarcodeScanTimerStarted ? "LightSeaGreen" : "OrangeRed";

    // Remember this style so it can be restored when the interval timer ends
    BarcodeHelpButtonStyle = BarcodeHelpButton.attr('style');

    // Set the "Action" icon to "started" or "stopped" to alert the user to the timer status
    BarcodeContentBtnImageSrc = (bBarcodeScanTimerStarted ? $("#BarcodeScanTimerStartImg").attr('src') : $("#BarcodeScanTimerStopImg").attr('src'));
    BarcodeHelpButton.attr('style', 'background: url(' + BarcodeContentBtnImageSrc + ') top center no-repeat');

    // Start interval timer
    BarcodeBackgroundTimerInterval = setInterval(FlashBackgroundIntervalTimer, 200/*ms*/);
}

/**
 * Called for each background flash (every 200ms)
 * */
function FlashBackgroundIntervalTimer() {

    var BarcodeContentDiv = $("#BarcodeScanContentDiv");
    var BarcodeHelpButton = $("#BarcodeHelpButton");

    // Stop flashing at 6 cycles
    // Clear screen at 10 cycles
    var NewBkgColor = BarcodeContentDivBkColor;
    if ((nFlashBackgroundCyles < 6) && ((nFlashBackgroundCyles % 2) == 0))
        NewBkgColor = BarcodeContentDivFlashColor;
    BarcodeContentDiv[0].style.backgroundColor = NewBkgColor;

    // Change button image
    BarcodeHelpButton.attr('style', 'background: url(' + BarcodeContentBtnImageSrc + ') top center no-repeat');

    // End flashing?
    nFlashBackgroundCyles++;
    if (nFlashBackgroundCyles > 10) {
        clearInterval(BarcodeBackgroundTimerInterval);
        BarcodeBackgroundTimerInterval = null;
        nFlashBackgroundCyles = 0;
        BarcodeContentDiv[0].style.backgroundColor = BarcodeContentDivBkColor;

        // Restore help button image
        BarcodeHelpButton.attr('style', BarcodeHelpButtonStyle);
    }
}




//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//

var bCameraVideoDialogIsOpen = false;

function OpenCameraVideoDialog() {

    // Open the dialog
    // igniteui.com/help/igdialog-property-reference
    var nDlgWidth = 500;
    var nDlgHeight = 700;
    var nWindowWidth = $(window).width();
    var nWindowHeight = $(window).height();
    if (nWindowWidth < nDlgWidth)
        nDlgWidth = nWindowWidth * 0.95;
    if (nWindowHeight < nDlgHeight)
        nDlgHeight = nWindowHeight * 0.95;

    // Open the dialog
    // igniteui.com/help/igdialog-property-reference
    $("#igDialogCameraVideo").igDialog({ modal: true, headerText: "Camera", width: nDlgWidth, height: nDlgHeight, resizable: false, closeOnEscape: true, openAnimation: "", closeAnimation: "fade", stateChanged: function (event, ui) { OnCameraVideoStateChanged(event, ui); }/*, openAnimation: "fade"*/ });
    $("#igDialogCameraVideo").igDialog("open");

    CenterDialog("igDialogCameraVideo");

    // Set up controls
    bCameraVideoDialogIsOpen = true;
}

function CloseCameraVideoDialog() {
    $("#igDialogCameraVideo").igDialog("close");
    bCameraVideoDialogIsOpen = false;
}

function OnCameraVideoStateChanged(event, ui) {

    if (!ui)
        return;
    if (!ui.action)
        return;

    // Close event
    if (ui.action == 'close') {

            var bCloseButton = (ui.button == 'close');
            if (bCloseButton) {
                CancelBarcodeDetectorIntervalTimer();
            }

        // No longer open
        bCameraVideoDialogIsOpen = false;
    }
}

/**
 * Timer used to capture barcode scans from camera
 */
function CancelBarcodeDetectorIntervalTimer() {

    if (nBarcodeDetectorIntervalId && nBarcodeDetectorIntervalId != null)
        clearInterval(nBarcodeDetectorIntervalId);
    nBarcodeDetectorIntervalId = null;
}








var nBarcodeDetectorIntervalId = null;
var bAnnounceBarcodeScan = true;


/**
 * Scan barcodes with built-in camera
 * @returns
 */
function OpenCameraBarcodeScan() {

    // check compatibility
    if (!("BarcodeDetector" in globalThis)) {
        ToastError("Barcode Detector is not supported by this browser.");
        return;
    }

    if (!BarcodeScanTextAreaId)
        return;
    var BarcodeInputTextObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextObject)
        return;

    OpenCameraVideoDialog();

    // Define video as the video element. You can pass the element to the barcode detector.
    // Get a stream for the rear camera, else the front (or side?) camera.
    const video = document.querySelector('video');

    /* Setting up the constraint */
    var facingMode = "environment"; // Can be 'user' or 'environment' to access back or front camera (NEAT!)
    var constraints = {
        audio: false,
        video: {
            facingMode: facingMode,
            //width: { ideal: 4096 },
            //height: { ideal: 2160 },
            width: { ideal: 3000 },
            //height: { ideal: 2160 },
        }
    };

    /* Stream it to video element */
    navigator.mediaDevices.getUserMedia(constraints).then(function success(stream) {
        video.srcObject = stream;
    });

    console.log("Barcode supported formats:");
    BarcodeDetector.getSupportedFormats().then((supportedFormats) => {
        supportedFormats.forEach((format) => console.log(format));
    });

    // create new detector
    const barcodeDetector = new BarcodeDetector({
        formats: ["code_39", "code_128", "codabar", "ean_13", "qr_code"],
    });

    //ToastMessage("OpenCameraBarcodeScan detect 4");

    navigator.virtualKeyboard.hide();
    navigator.vibrate(200);

    if (bAnnounceBarcodeScan)
        ToastMessage("Scan barcode now", "");
    bAnnounceBarcodeScan = false;

    CancelBarcodeDetectorIntervalTimer();

    // Check for barcode scan every 1000ms
    nBarcodeDetectorIntervalId = setInterval(function () {

        barcodeDetector
            .detect(video)
            .then((barcodes) => {
                barcodes.forEach((barcode) => OnCameraScannedValue(barcode.rawValue));
            })
            .catch((err) => {
                ToastError("Barcode Detector Error", err);
            });

    }, 1000/*ms*/);
}

function OnCameraScannedValue(sScannedValue) {

    if (!sScannedValue || sScannedValue == null) {
        ToastError("Scan failed with no value");
        return;
    }

    ToastMessage("Camera Results", sScannedValue);

    CancelBarcodeDetectorIntervalTimer();
    CloseCameraVideoDialog();

    if (!BarcodeScanTextAreaId)
        return;
    var BarcodeInputTextObject = $("#" + BarcodeScanTextAreaId);
    if (!BarcodeInputTextObject)
        return;

    // Set text and CRLF
    BarcodeInputTextObject.val(sScannedValue);
    SendCrLfBarcodeInput();

    // Tell user that we got the scan
    navigator.vibrate(200);
}








