//################################################################################################
/** @file OOP Dynamic Polymorphism (Function Overloading) mixin for ish.js
 * @mixin ish.oop.overload
 * @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) {
        //################################################################################################
        /** Collection of OOP Dynamic Polymorphism (Function Overloading)-based functionality.
         * @namespace ish.oop.overload
         * @ignore
         */ //############################################################################################
        core.oop.partial(core.oop, function (/*oProtected*/) {
            var a_fnOverloads = [];

            //#
            function registerAlias(fnOverload, fn, sAlias) {
                //#
                if (core.type.str.is(sAlias, true) && !fnOverload[sAlias]) {
                    fnOverload[sAlias] = fn;
                }
            } //# registerAlias

            return {
                overload: core.extend(
                    //#########
                    /** Initiates a wrapper function that marshals calls to the proper function overload based on argument signature.
                     * @function ish.oop.overload.!
                     * @param {function} [fnDefault] Value representing the default overload function to execute if the arguments cannot be matched to an available overload.
                     * @returns {object} =interface Value representing the following properties:
                     *      @returns {function} =interface.add Adds another function to the available overloads; <code>add(fn, sAlias, a_vArgumentTests)</code>:
                     *          <table class="params">
                     *              <tr><td class="name"><code>fn</code><td><td class="type param-type">function<td><td class="description last">Value representing the function to add to the available overloads.</td></tr>
                     *              <tr><td class="name"><code>sAlias</code><td><td class="type param-type">string<td><td class="description last">Value representing the unique alias for the passed function.</td></tr>
                     *              <tr><td class="name"><code>a_vArgumentTests</code><td><td class="type param-type">function[] | string[]<td><td class="description last">Value representing the argument validators as functions or strings referencing <code>ish.types.*.is</code> functions (e.g. <code>str</code>, <code>int</code>, <code>obj</code>, etc).</td></tr>
                     *          </table>
                     *      @returns {function} =interface.default Default function overload to execute if the arguments cannot be matched to an available overload; <code>default()</code>.
                     *      @returns {function} =interface.list Lists the available overloaded functions; <code>list(iArgumentCount)</code>:
                     *          <table class="params">
                     *              <tr><td class="name"><code>iArgumentCount</code><td><td class="type param-type">integer<td><td class="description last">Value representing the desired argument count to limit the returned list to.</td></tr>
                     *          </table>
                     */ //#####
                    function (fnDefault) {
                        var oData = {
                                //#########
                                /** Interface under the returned <code>ish.oop.overload</code> wrapper function that defines the default function overload to execute if the arguments cannot be matched to an available overload.
                                 * @function ish.oop.overload.*:default
                                 * @throws <code>Overload not found for arguments</code> unless defined at initiation.
                                 * @ignore
                                 */ //#####
                                default: (core.type.fn.is(fnDefault) ?
                                    fnDefault :
                                    function (/*arguments*/) {
                                        throw "ish.oop.overload: Overload not found for arguments " + core.type.arr.mk(arguments).join(",");
                                    }
                                )
                            },
                            fnOverload = core.extend(
                                //#
                                function (/*arguments*/) {
                                    var oEntry, i, j,
                                        a = arguments,
                                        oArgumentTests = oData[a.length] || []
                                    ;

                                    //# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
                                    for (i = 0; i < oArgumentTests.length; i++) {
                                        oEntry = oArgumentTests[i];

                                        //# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
                                        for (j = 0; j < a.length; j++) {
                                            if (!oArgumentTests[i].tests[j](a[j])) {
                                                oEntry = undefined;
                                                break;
                                            }
                                        }

                                        //# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
                                        if (oEntry) {
                                            break;
                                        }
                                    }

                                    //# If we found our oEntry above, .fn.call its .fn
                                    if (oEntry) {
                                        oEntry.calls++;
                                        return core.type.fn.call(oEntry.fn, this, a);
                                    }
                                    //# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
                                    else {
                                        return core.type.fn.call(oData.default, this, a);
                                    }
                                }, //# overload*
                                {
                                    //#########
                                    /** Interface under the returned <code>ish.oop.overload</code> wrapper function that adds another function to the available overloads.
                                     * @function ish.oop.overload.*:add
                                     * @param {function} fn Value representing the function to add to the available overloads.
                                     * @param {string} sAlias Value representing the unique alias for the passed function.
                                     * @param {function[]|string[]} [a_vArgumentTests] Value representing the argument validators as functions or strings referencing <code>ish.types.*.is</code> functions (e.g. <code>str</code>, <code>int</code>, <code>obj</code>, etc).
                                     * @ignore
                                     */ //#####
                                    add: function (fn, sAlias, a_vArgumentTests) {
                                        var i,
                                            bValid = (core.type.fn.is(fn) && core.type.str.is(sAlias, true) && !fnOverload[sAlias]),
                                            iLen = (core.type.arr.is(a_vArgumentTests) ? a_vArgumentTests.length : 0)
                                        ;

                                        //sAlias = 'fn_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')

                                        //# TODO: use ish.type.fn.signature?

                                        //#
                                        if (bValid) {
                                            //# Traverse the a_vArgumentTests, processing each to ensure they are functions (or references to )
                                            for (i = 0; i < iLen; i++) {
                                                if (!core.type.fn.is(a_vArgumentTests[i])) {
                                                    a_vArgumentTests[i] = core.resolve(core.type, [a_vArgumentTests[i], "is"]);
                                                    if (!core.type.fn.is(a_vArgumentTests[i])) {
                                                        bValid = false;
                                                    }
                                                }
                                            }
                                        }

                                        //# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests' iLen
                                        if (bValid) {
                                            oData[iLen] = oData[iLen] || [];
                                            oData[iLen].push({
                                                fn: fn,
                                                tests: a_vArgumentTests,
                                                calls: 0
                                            });

                                            //#
                                            registerAlias(fnOverload, fn, sAlias);
                                            //return fnOverload;
                                        }
                                        //# Else one of the passed arguments was not bValid, so throw the error
                                        else {
                                            throw "ish.oop.overload: All tests must be functions or strings referencing `type.*.is`.";
                                        }
                                    }, //# overload.*.add

                                    //#########
                                    /** Interface under the returned <code>ish.oop.overload</code> wrapper function that lists the available overloaded functions.
                                     * @function ish.oop.overload.*:list
                                     * @param {integer} [iArgumentCount] Value representing the desired argument count to limit the returned list to.
                                     * @returns {object} Value representing the available overloaded functions categorized by argument count.
                                     * @ignore
                                     */ //#####
                                    list: function (iArgumentCount) {
                                        return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
                                    } //# overload.*.list
                                }
                            ) //# fnOverload = core.extend(
                        ;

                        //#
                        a_fnOverloads.push(fnOverload);
                        //registerAlias(fnOverload, oData.default, oData.alias);

                        return fnOverload;
                    }, //# oop.overload
                    {
                        //#########
                        /** Determines if the passed value is a wrapper function that marshals calls to the proper function overload based on argument signature.
                         * @function ish.oop.overload.is
                         * @param {function} fnTarget Value representing the function to test.
                         * @returns {boolean} Value representing if the passed value is a wrapper function that marshals calls to the proper function overload based on argument signature.
                         */ //#####
                        is: function (fnTarget) {
                            return (a_fnOverloads.indexOf(fnTarget) > -1);
                        } //# oop.overload.is
                    }
                )
            };
        }); //# core.oop.overload

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

        //# 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>
}());