//################################################################################################
/** @file Enumerations mixin for ish.js
 * @mixin ish.type.enum
 * @author Nick Campbell
 * @license MIT
 * @copyright 2014-2023, Nick Campbell
 */ //############################################################################################
/*global module, define */                                      //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */                                     //# Enable max complexity warnings for JSHint
(function () {
    'use strict'; //<MIXIN>

    function init(core) {
        //# .require the necessary ish plugins
        //#     NOTE: core.require includes the required scripts/CSS then runs the provided function
        core.require(["ish.io.net.js", "ish.type-ex.js"], function (/*a_sUrls, bAllLoaded*/) {
            //################################################################################################
            /** Collection of Enumeration-based functionality.
             * @namespace ish.type.enum
             * @ignore
             */ //############################################################################################
            core.oop.partial(core.type, function (/*oProtected*/) {
                var g_oEnums = {};

                //#
                function processOptions(vOptions) {
                    var oReturnVal = core.extend({
                            asEntry: (vOptions === true)
                            //compare: function (x, y) {
                            //    return (x === y);
                            //}
                        }, vOptions)
                    ;

                    //# If the vOptions had "i" set in the .compare, reset it to be .caseInsensitive
                    if (oReturnVal.compare === true || oReturnVal.compare === "i") { //# TODO: Remove "i"
                        oReturnVal.compare = core.type.str.cmp;
                    }
                    //# Else if .compare !.is .fn then set it to the default .compare'son function
                    else if (!core.type.fn.is(oReturnVal.compare)) {
                        oReturnVal.compare = function (x, y) {
                            return (x == y);
                        };
                    }

                    return oReturnVal;
                } //# processOptions

                //#
                function processCompareOnly(vCompare) {
                    return processOptions({
                        compare: (core.type.fn.is(vCompare) || vCompare === true || vCompare === "i" ? vCompare : undefined)
                    });
                } //# processCompareOnly

                //#
                function xcoder(sEnumName, sValue, vOptions, fnCompare) {
                    var i,
                        oReturnVal = {
                            value: sValue,
                            label: sValue,
                            notFound: true
                        },
                        a_oEnum = core.resolve(g_oEnums, sEnumName)
                    ;

                    //# If the sEnumName is valid, traverse it looking for a matching .value, setting our oReturnVal if found
                    if (core.type.arr.is(a_oEnum, true)) { //#
                        //#
                        for (i = 0; i < a_oEnum.length; i++) {
                            if (fnCompare(a_oEnum[i], vOptions)) {
                                oReturnVal = a_oEnum[i];
                                break;
                            }
                            else if (a_oEnum[i].value === undefined) {
                                oReturnVal.value = a_oEnum[i].value;
                                oReturnVal.label = a_oEnum[i].label;
                                oReturnVal.notFound = false;
                            }
                        }
                    }

                    return oReturnVal;
                } //# xcoder

                //#
                function enumAsObj(a_oEnum) {
                    var oCurrent, i,
                        oReturnVal /*= undefined*/
                    ;

                    //# If we have an a_oEnum .arr to traverse, reset our oReturnVal to an object
                    if (core.type.arr.is(a_oEnum, true)) {
                        oReturnVal = {};

                        //# Traverse our a_oEnum, entering each valid .value/.label into our oReturnVal
                        for (i = 0; i < a_oEnum.length; i++) {
                            oCurrent = a_oEnum[i];
                            if (core.type.obj.is(oCurrent)) {
                                oReturnVal[oCurrent.label] = oCurrent.value;
                            }
                        }
                    }

                    return oReturnVal;
                } //# enumAsObj


                return {
                    //#########
                    /** Determines the enumeration data for the passed value.
                     * @function ish.type.enum.!
                     * @param {string} sEnum Value representing the enumeration to interrogate.
                     * @param {boolean} [bAsObj=false] Value representing if the return value is to be represented by a simplified <code>{ label: value }</code>-based object.
                     * @returns {object[]|object} Value representing the enumeration data.
                     */ //#####
                    enum: core.extend(
                        function (sEnum, bAsObj) {
                            var a_oReturnVal = core.resolve(g_oEnums, sEnum);

                            return (bAsObj ? enumAsObj(a_oReturnVal) : a_oReturnVal);
                        }, {
                            //#########
                            /** Determines if the passed value represents a <code>label</code> or <code>value</code> within the passed enumeration entry.
                             * @function ish.type.enum.is
                             * @param {string} sEnum Value representing the enumeration to interrogate.
                             * @param {variant} vValue Value to interrogate within the passed enumeration representing a <code>label</code> or <code>value</code>.
                             * @param {boolean|function} [vCompare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparisons.
                             * @returns {boolean} Value representing if the passed value represents a <code>label</code> or <code>value</code> within the passed enumeration entry.
                             */ //#####
                            is: core.extend(
                                function (sEnum, vValue, vCompare) {
                                    var vReturnVal;

                                    //#
                                    vReturnVal = xcoder(sEnum, vValue, processCompareOnly(vCompare), function (oPicklistEntry, oOptions) {
                                        return oOptions.compare(oPicklistEntry.label, vValue) || oOptions.compare(oPicklistEntry.value, vValue);
                                    });

                                    return (vReturnVal.notFound !== true && vReturnVal.value !== undefined);
                                }, {
                                    //#########
                                    /** Determines if the passed value represents a <code>label</code> within the passed enumeration entry.
                                     * @function ish.type.enum.is:label
                                     * @param {string} sEnum Value representing the enumeration to interrogate.
                                     * @param {string} sValue Value to interrogate within the passed enumeration representing a <code>label</code>.
                                     * @param {boolean|function} [vCompare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparisons.
                                     * @returns {boolean} Value representing if the passed value represents a <code>label</code> within the passed enumeration entry.
                                     */ //#####
                                    label: function (sEnum, sValue, vCompare) {
                                        var vReturnVal;

                                        //#
                                        vReturnVal = xcoder(sEnum, sValue, processCompareOnly(vCompare), function (oPicklistEntry, oOptions) {
                                            return oOptions.compare(oPicklistEntry.label, sValue);
                                        });

                                        return (vReturnVal.notFound !== true && vReturnVal.value !== undefined);
                                    }, //# type.enum.is.label


                                    //#########
                                    /** Determines if the passed value represents a <code>value</code> within the passed enumeration entry.
                                     * @function ish.type.enum.is:value
                                     * @param {string} sEnum Value representing the enumeration to interrogate.
                                     * @param {variant} vValue Value to interrogate within the passed enumeration representing a <code>value</code>.
                                     * @param {boolean|function} [vCompare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparisons.
                                     * @returns {boolean} Value representing if the passed value represents a <code>value</code> within the passed enumeration entry.
                                     */ //#####
                                    value: function (sEnum, vValue, vCompare) {
                                        var vReturnVal;

                                        //#
                                        vReturnVal = xcoder(sEnum, vValue, processCompareOnly(vCompare), function (oPicklistEntry, oOptions) {
                                            return oOptions.compare(oPicklistEntry.value, vValue);
                                        });

                                        return (vReturnVal.notFound !== true && vReturnVal.value !== undefined);
                                    }
                                } //# type.enum.is.value
                            ), //# type.enum.is


                            //#########
                            /** Casts the passed value into an enumeration.
                             * @function ish.type.enum.mk
                             * @param {string} sEnum Value representing the enumeration to interrogate.
                             * @param {variant} vValue Value to interrogate within the passed enumeration representing a <code>value</code>.
                             * @param {boolean|object} [vOptions] Value representing if the full enumeration entry is to be returned or the desired options:
                             *      @param {boolean} [vOptions.asEntry=false] Value representing if the full enumeration entry is to be returned.
                             *      @param {boolean|function} [vOptions.compare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparison.
                             * @returns {variant|object} Value representing the passed value as an enumeration.
                             */ //#####
                            mk: function (sEnum, vValue, vOptions) { // vDefault
                                var vReturnVal;

                                //#
                                vOptions = processOptions(vOptions);
                                vReturnVal = xcoder(sEnum, vValue, vOptions, function (oPicklistEntry, oOptions) {
                                    return oOptions.compare(oPicklistEntry.label, vValue) || oOptions.compare(oPicklistEntry.value, vValue);
                                });

                                return (vOptions.asEntry ? vReturnVal : vReturnVal.value);
                            },


                            //#########
                            /** Encodes the passed value as an enumeration.
                             * @function ish.type.enum.encode
                             * @param {string} sEnum Value representing the enumeration to interrogate.
                             * @param {string} sLabel Value to interrogate within the passed enumeration representing a <code>label</code>.
                             * @param {boolean|object} [vOptions] Value representing if the full enumeration entry is to be returned or the desired options:
                             *      @param {boolean} [vOptions.asEntry=false] Value representing if the full enumeration entry is to be returned.
                             *      @param {boolean|function} [vOptions.compare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparison.
                             * @returns {variant|object} Value representing the passed value as an enumeration.
                             */ //#####
                            encode: function (sEnum, sLabel, vOptions) {
                                var oReturnVal;

                                //#
                                vOptions = processOptions(vOptions);
                                oReturnVal = xcoder(sEnum, sLabel, vOptions, function (oPicklistEntry, oOptions) {
                                    return oOptions.compare(oPicklistEntry.label, sLabel);
                                });

                                //#
                                return (vOptions.asEntry ? oReturnVal : oReturnVal.value);
                            }, //# data.enum.encode


                            //#########
                            /** Decodes the passed value as an enumeration.
                             * @function ish.type.enum.decode
                             * @param {string} sEnum Value representing the enumeration to interrogate.
                             * @param {variant} vValue Value to interrogate within the passed enumeration representing a <code>value</code>.
                             * @param {boolean|object} [vOptions] Value representing if the full enumeration entry is to be returned or the desired options:
                             *      @param {boolean} [vOptions.asEntry=false] Value representing if the full enumeration entry is to be returned.
                             *      @param {boolean|function} [vOptions.compare=function (x, y) { return (x == y); }] Value representing if the comparison is to be case-insensitive or the function to execute the comparison.
                             * @returns {string|object} Value representing the passed value as an enumeration.
                             */ //#####
                            decode: function (sEnum, vValue, vOptions) {
                                var oReturnVal;

                                //#
                                vOptions = processOptions(vOptions);
                                oReturnVal = xcoder(sEnum, vValue, vOptions, function (oPicklistEntry, oOptions) {
                                    return oOptions.compare(oPicklistEntry.value, vValue);
                                });

                                //#
                                return (vOptions.asEntry ? oReturnVal : oReturnVal.label);
                            }, //# data.enum.decode


                            //#########
                            /** Determines if the passed value represents an enumeration.
                             * @function ish.type.enum.exists
                             * @param {string} sEnum Value representing the enumeration to interrogate.
                             * @returns {boolean} Value representing if the passed value represents an enumeration.
                             */ //#####
                            exists: function (sEnum) {
                                return core.type.arr.is(core.resolve(g_oEnums, sEnum), true);
                            }, //# data.enum.exists


                            //#########
                            /** Loads the passed value as enumeration(s) into the existing collection.
                             * @$note If <code>vEnums</code> represents a URL, the <code>ish.io.net</code> Mixin is required in order to collect the JSON file.
                             * @function ish.type.enum.load
                             * @param {string|object} vEnums Value representing the URL to a JSON file or an object defining enumeration(s).
                             * @returns {boolean} Value representing if the passed value was successfully loaded.
                             */ //#####
                            load: function (vEnums) {
                                var bReturnVal = true;

                                //# TODO: Make inline alt core.io.net.get?
                                if (core.type.str.is(vEnums) && core.type.fn.is(core.resolve(core, "io.net.get"))) {
                                    //#
                                    core.io.net.get(vEnums, {
                                        contentType: 'application/json; charset=utf-8',
                                        fn: function (bSuccess, oResponse /*, vArg, $xhr*/) {
                                            bReturnVal = bSuccess;

                                            //#
                                            if (bReturnVal) {
                                                core.extend(g_oEnums, oResponse.data);
                                            }
                                        }
                                    });
                                }
                                //#
                                else if (core.type.obj.is(vEnums)) {
                                    core.extend(g_oEnums, vEnums);
                                }
                                //#
                                else {
                                    bReturnVal = false;
                                }

                                return bReturnVal;
                            }, //# data.enum.load


                            //#########
                            /** Returns a new instance of the base interface for an enumeration.
                             * @function ish.type.enum.interface
                             * @returns {object} Value representing the base interface for an enumeration.
                             */ //#####
                            interface: function () {
                                return {
                                    value: 0,
                                    label: ""
                                };
                            } //# data.enum.interface
                        }
                    )
                };
            }); //# core.type.enum

            //#
            //# <minRemove>
            if (core.test) {
                core.oop.partial(core.test, function (/*oProtected*/) {
                    return {
                        type: {
                            enum: {
                                _order: ["load"],

                                load: function ($ /*, results*/) {
                                    $.expect(1);
                                    $.assert.isOk(
                                        core.type.enum.load({
                                            deep: {
                                                enum: {
                                                    letters: [
                                                        { value: undefined, label: "unknown" },
                                                        { value: "a", label: "eh" },
                                                        { value: "b", label: "bee" },
                                                        { value: "c", label: "cee" },
                                                        { value: "d", label: "dee", more: "info" }
                                                    ]
                                                }
                                            },
                                            numbers: [
                                                { value: undefined, label: "unknown" },
                                                { value: 1, label: "one" },
                                                { value: 2, label: "two" },
                                                { value: 3, label: "three" },
                                                { value: 4, label: "four" },
                                                { value: 5, label: "five" }
                                            ]
                                        }),
                                        "type.enum.load"
                                    );
                                },

                                _: function ($) {
                                    $.expect(7);
                                    $.assert(core.type.enum("deep.enum.letters", true).unknown === undefined, "type.enum(deep, asObj)");
                                    $.assert(core.type.enum("numbers", true).one === 1, "type.enum(shallow, asObj)");

                                    $.assert(core.type.enum("deep.enum.letters")[2].value === "b", "type.enum(deep).value");
                                    $.assert(core.type.enum("deep.enum.letters")[3].label === "cee", "type.enum(deep).label");
                                    $.assert(core.type.enum("deep.enum.letters")[4].more === "info", "type.enum(deep).more");
                                    $.assert(core.type.enum("numbers")[4].value === 4, "type.enum(shallow).value");
                                    $.assert(core.type.enum("numbers")[5].label === "five", "type.enum(shallow).label");
                                },

                                is: function ($) {
                                    $.expect(16);
                                    $.assert(core.type.enum.is("deep.enum.letters", "a"), "type.enum.is(deep)");
                                    $.assert(core.type.enum.is("deep.enum.letters", "eh"), "type.enum.is(deep)");
                                    $.assert(!core.type.enum.is("deep.enum.letters", "eee"), "!type.enum.is(deep)");
                                    $.assert(core.type.enum.is("numbers", 5), "type.enum.is(shallow) <==>");
                                    $.assert(core.type.enum.is("numbers", "5"), "type.enum.is(shallow)");
                                    $.assert(core.type.enum.is("numbers", "five"), "type.enum.is(shallow)");
                                    $.assert(!core.type.enum.is("numbers", "six"), "!type.enum.is(shallow)");

                                    $.assert(core.type.enum.is.label("deep.enum.letters", "dee"), "type.enum.is.label(deep)");
                                    $.assert(!core.type.enum.is.label("deep.enum.letters", "hach"), "!type.enum.is.label(deep)");
                                    $.assert(core.type.enum.is.label("numbers", "three"), "type.enum.is.label(shallow)");
                                    $.assert(!core.type.enum.is.label("numbers", "seven"), "!type.enum.is.label(shallow)");

                                    $.assert(core.type.enum.is.value("deep.enum.letters", "c"), "type.enum.is.value(deep)");
                                    $.assert(!core.type.enum.is.value("deep.enum.letters", "f"), "!type.enum.is.value(deep)");
                                    $.assert(core.type.enum.is.value("numbers", 1), "type.enum.is.value(shallow) <==>");
                                    $.assert(core.type.enum.is.value("numbers", "2"), "type.enum.is.value(shallow)");
                                    $.assert(!core.type.enum.is.value("numbers", "7"), "!type.enum.is.value(shallow)");
                                },

                                mk: function ($) {
                                    $.expect(3);
                                    $.assert(core.type.enum.mk("numbers", "5") == 5, "type.enum.mk(shallow) <value>");
                                    $.assert(core.type.enum.mk("numbers", "four") == 4, "type.enum.mk(shallow) <label>");
                                    $.assert(core.type.enum.mk("numbers", "7") === undefined, "type.enum.mk(shallow) <undefined>");
                                },

                                encode: function ($) {
                                    $.expect(2);
                                    $.assert(core.type.enum.encode("numbers", "7") === undefined, "type.enum.mk(shallow) <undefined>");
                                    $.assert(core.type.enum.encode("numbers", "two") === 2, "type.enum.mk(shallow) 2");
                                },

                                decode: function ($) {
                                    $.expect(4);
                                    $.assert(core.type.enum.decode("numbers", 3) === "three", "type.enum.decode");
                                    $.assert(core.type.enum.decode("numbers", "7") === "unknown", "type.enum.decode unknown");
                                    $.assert.deepEqual(core.type.enum.decode("numbers", 5, { asEntry: true }), { value: 5, label: "five" }, "type.enum.decode asEntry");
                                    $.assert.deepEqual(core.type.enum.decode("deep.enum.letters", "d", { asEntry: true }), { value: "d", label: "dee", more: "info" }, "type.enum.decode asEntry 2");
                                },

                                exists: function ($) {
                                    $.expect(3);
                                    $.assert(core.type.enum.exists("numbers"), "type.enum.exists(shallow)");
                                    $.assert(core.type.enum.exists("deep.enum.letters"), "type.enum.exists(deep)");
                                    $.assert(!core.type.enum.exists("doesnotexist"), "!type.enum.exists(");
                                },

                                interface: function ($) {
                                    $.expect(1);
                                    $.assert.deepEqual(core.type.enum.interface(), { value: 0, label: "" }, "type.enum.interface");
                                }
                            } //# type.enum
                        }
                    };
                }); //# core.test.type.enum
            } //# </minRemove>
        }, { baseUrl: "" }); //# core.type

        //# .fire the plugin's loaded event
        core.io.event.fire("ish.type.enum");

        //# Return core to allow for chaining
        return core;
    } //# init


    //# If we are running server-side
    //#     NOTE: Compliant with UMD, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
    //#     NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
    if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
        module.exports = init;
    }
    //# Else if we are running in an .amd environment, register as an anonymous module
    else if (typeof define === 'function' && define.amd) {
        define([], init);
    }
    //# Else we are running in the browser, so we need to setup the _document-based features
    else {
        return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
    }

    //</MIXIN>
}());