import { _decorator } from "cc";
const { ccclass } = _decorator;

@ccclass("Lodash")
export class Lodash {
    /* class member could be defined like this */
    // dummy = '';
    /**
     * 遍历 collection（集合）元素，返回 predicate（断言函数）第一个返回真值的第一个元素
     * @param  {any} collection 一个用来迭代的集合
     * @param {Function} predicate 每次迭代调用的函数。
     * @returns 返回匹配元素，否则返回 undefined。
     */
    public static find (collection: any, predicate: Function) {
        var result;
        if (!Array.isArray(collection)) {
            collection = Lodash._toArray(collection);
        }

        result = collection.filter(predicate);
        if (result.length) {
            return result[0];
        }

        return undefined;
    }

    /**
     * 调用 iteratee 遍历 collection(集合) 中的每个元素
     * @param  {any} collection 一个用来迭代的集合
     * @param {Function} iteratee 每次迭代调用的函数。
     */
    public static forEach(collection: any, iteratee: any) {
        if (!Array.isArray(collection)) {
            var array = Lodash._toArrayKey(collection);
            array.forEach(function (value: any, index: number, arr: any[]) {
                var key1 = value['key'];
                var value1 = value['value'];
                iteratee(value1, key1, collection);
            });
        } else {
            collection.forEach(iteratee);
        }
    }

    /**
     * 深度拷贝
     * @param {any} sObj 拷贝的对象
     * @returns 
     */
    public static cloneDeep(sObj: any) {
        if (sObj === null || typeof sObj !== "object") {
            return sObj;
        }

        let s: any = {};
        if (sObj.constructor === Array) {
            s = [];
        }

        for (let i in sObj) {
            if (sObj.hasOwnProperty(i)) {
                s[i] = Lodash.cloneDeep(sObj[i]);
            }
        }

        return s;
    }

    /**
     * 创建一个数组， value（值） 是 iteratee（迭代函数）遍历 collection（集合）中的每个元素后返回的结果。
     * @param {Array|Object} collection  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数，用来转换key（键
     * @returns {Array} 返回一个组成集合数组
     */
    public static map(collection: any, iteratee: any) {
        if (!Array.isArray(collection)) {
            collection = Lodash._toArray(collection);
        }

        let arr: any[] = [];
        collection.forEach(function (value: any, index: number, array: []) {
            arr.push(iteratee(value, index, array));
        });

        return arr;
    }

    /**
     * 
     * @param srcObj 
     * @returns 
     */
    private static _toArrayKey(srcObj: { [x: string]: any; hasOwnProperty: (arg0: string) => any; }) {
        var resultArr = [];

        // to array
        for (var key in srcObj) {
            if (!srcObj.hasOwnProperty(key)) {
                continue;
            }

            resultArr.push({ key: key, value: srcObj[key] });
        }

        return resultArr;
    }

    private static _toArray(srcObj: any) {
        let resultArr: any[] = [];

        // to array
        for (var key in srcObj) {
            if (!srcObj.hasOwnProperty(key)) {
                continue;
            }

            resultArr.push(srcObj[key]);
        }

        return resultArr;
    }

    /**
     * 遍历 collection（集合）元素，返回 predicate（断言函数）返回真值 的所有元素的数组。
     * @param {Array|Object} collection  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数，用来转换key（键
     * @returns 返回一个新的过滤后的数组。
     */
    public static filter(collection: any, iteratees: Function) {
        if (!Array.isArray(collection)) {
            collection = Lodash._toArray(collection);
        }

        return collection.filter(iteratees);
    }

    /**
     * 执行深比较来确定两者的值是否相等。
     * @param {any}x 
     * @param {any}y 
     * @returns {boolean} 如果 两个值完全相同，那么返回 true，否则返回 false。
     */
    public static isEqual(x: any, y: any): boolean {
        var in1 = x instanceof Object;
        var in2 = y instanceof Object;
        if (!in1 || !in2) {
            return x === y;
        }

        if (Object.keys(x).length !== Object.keys(y).length) {
            return false;
        }

        for (var p in x) {
            var a = x[p] instanceof Object;
            var b = y[p] instanceof Object;
            if (a && b) {
                return Lodash.isEqual(x[p], y[p]);
            } else if (x[p] !== y[p]) {
                return false;
            }
        }

        return true;
    }

    /**
     * 接收一个要移除值的数组。
     * @param {Array} array 修改的数组
     * @param {Array} value 移除值的数组
     * @param  {Function} comparator comparator（比较器）调用每个元素。
     * @returns 
     */
    public static pullAllWith(array: any[], value: any[], comparator: Function) {
        value.forEach(function (item) {
            var res = array.filter(function (n) {
                return comparator(n, item);
            });

            res.forEach(function (item) {
                var index = array.indexOf(item);
                if (array.indexOf(item) !== -1) {
                    array.splice(index, 1);
                }
            });
        });

        return array;
    }

    /**
     * 返回当前时间戳
     * @returns 
     */
    public static now() {
        return Date.now();
    }

    /**
     * 接收一个要移除值的数组。
     * @param {Array} array 修改的数组
     * @param {Array} value 移除值的数组
     * @returns 
     */
    public static pullAll(array: any[], value: any) {
        value.forEach(function (item: any) {
            var index = array.indexOf(item);
            if (array.indexOf(item) !== -1) {
                array.splice(index, 1);
            }
        });

        return array;
    }

    /**
     * 从右到左遍历集合中每一个元素的。
     * @param {Array|Object} collection  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     */
    public static forEachRight(collection: [] | {}, iteratee: Function) {
        if (!Array.isArray(collection)) {
            collection = Lodash._toArray(collection);
        }

        //@ts-ignore
        for (var i = collection.length - 1; i >= 0; i--) {
            //@ts-ignore
            var ret = iteratee(collection[i]);
            if (!ret) break;
        }
    }

    /**
     * 检查字符串string是否以 target 开头。
     * @param {string} str 要检索的字符串。
     * @param {string}target  要检查的字符串。
     * @param {number}position 检索的位置。
     * @returns 
     */
    public static startsWith(str: string, target: string, position: number) {
        str = str.substr(position);
        return str.startsWith(target);
    }

    /**
     * 检查字符串string是否以 target 结束。
     * @param {string} str 要检索的字符串。
     * @param {string}target  要检查的字符串。
     * @param {number}position 检索的位置。
     * @returns 
     */
    public static endsWith(str: string, target: string, position: number) {
        str = str.substr(position);
        return str.endsWith(target);
    }

    /**
     * 移除数组中predicate（断言）返回为真值的所有元素
     * @param {Array} array  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     * @returns 
     */
    public static remove(array: any[], predicate: Function) {
        var result: any[] = [];
        var indexes: any[] = [];
        array.forEach(function (item, index) {
            if (predicate(item)) {
                result.push(item);
                indexes.push(index);
            }
        });

        Lodash._basePullAt(array, indexes);
        return result;
    }

    private static _basePullAt(array: any[], indexes: any[]) {
        var length = array ? indexes.length : 0;
        var lastIndex = length - 1;
        var previous;

        while (length--) {
            var index = indexes[length];
            if (length === lastIndex || index !== previous) {
                previous = index;
                Array.prototype.splice.call(array, index, 1);
            }
        }

        return array;
    }

    /**
     * 返回第一个通过 predicate 判断为真值的元素的索引值
     * @param {Array} array  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     * @param {number} fromIndex 开始查找索引值
     * @returns 
     */
    public static findIndex(array: any[], predicate: Function, fromIndex: number) {
        array =  array.slice(fromIndex);
        var i;
        if (typeof predicate === "function") {
            for (i = 0; i < array.length; i++) {
                if (predicate(array[i])) {
                    return i;
                }
            }
        } else if (Array.isArray(predicate)) {
            for (i = 0; i < array.length; i++) {
                var key = predicate[0];
                var vaule = true;
                //@ts-ignore
                if (predicate.length > 1) {
                    vaule = predicate[1];
                }

                if (array[i][key] === vaule) {
                    return i;
                }
            }
        } else {
            for (i = 0; i < array.length; i++) {
                if (array[i] === predicate) {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * 创建一个新数组，将array与任何数组 或 值连接在一起。
     * @returns 
     */
    public static concat() {
        var length = arguments.length;
        if (!length) {
            return [];
        }

        var array = arguments[0];
        var index = 1;
        while (index < length) {
            array = array.concat(arguments[index]);
            index++;
        }

        return array;
    }

    /**
     * 检查 value 是否是原始Number数值型 或者 对象。
     * @param {any }value 
     * @returns 
     */
    public static isNumber(value: any) {
        return typeof value === 'number';
    }

    /**
     * 返回首次 value 在数组array中被找到的 索引值
     * @param {Array}array 
     * @param {any}value 
     * @param {number} fromIndex 
     * @returns 
     */
    public static indexOf(array: any[], value: any, fromIndex: number) {
        array =  array.slice(fromIndex);
        return array.indexOf(value);
    }

    /**
     * 将 array 中的所有元素转换为由 separator 分隔的字符串。
     * @param {any} array 要转换的数组
     * @param {string} separator 分隔元素。
     * @returns 
     */
    public static join(array: any[], separator: string) {
        if (array === null) return '';

        var result = '';
        array.forEach(function (item) {
            result += item + separator;
        });

        return result.substr(0, result.length - 1);
    }

    /**
     * 根据separator 拆分字符串string。
     * @param {string} str 要转换的数组
     * @param {RegExp|string} separator 分隔元素。
     * @param {number} limit 限制结果的数量。
     * @returns 
     */
    public static split(str: string, separator: RegExp|string, limit: number) {
        return str.split(separator, limit);
    }

    /**
     * 计算 array 中的最大值。 如果 array 是 空的或者假值将会返回 undefined。
     * @param {Array}array 
     * @returns 
     */
    public static max(array: any[]) {
        if (array && array.length) {
            var result;
            for (var i = 0; i < array.length; i++) {
                if (i === 0) {
                    result = array[0];
                } else if (result < array[i]) {
                    result = array[i];
                }
            }

            return result;
        }

        return undefined;

    }

    /**
     * 创建一个切片数组，去除array前面的n个元素。（n默认值为1。）
     * @param {Array}array : 要查询的数组。
     * @param {number}n 要去除的元素个数。
     * @returns 
     */
    public static drop(array: any[], n: number) {
        var length = array === null ? 0 : array.length;
        if (!length) {
            return [];
        }

        return array.slice(n);
    }

    /**
     * 将array递归为一维数组。
     * @param {Array} arr 
     * @returns 
     */
    public static flattenDeep(arr: any[]): any {
        return arr.reduce(function (prev: any[], cur: any[]) {
            return prev.concat(Array.isArray(cur) ? Lodash.flattenDeep(cur) : cur);
        }, [ ]);
    }

    /**
     * 创建一个去重后的array数组副本。使用了SameValueZero 做等值比较。只有第一次出现的元素才会被保留。
     * @param {Array} array 
     * @returns 
     */
    public static uniq(array: any[]) {
        let result: any[] = [];
        array.forEach(function (item) {
            if (result.indexOf(item) === -1) {
                result.push(item);
            }
        });

        return result;
    }

    /**
     * 检查 value 是否是 NaN。
     * @param {any}value 
     * @returns 
     */
    public static isNaN(value: any) {
        // An `NaN` primitive is the only value that is not equal to itself.
        // Perform the `toStringTag` check first to avoid errors with some
        // ActiveX objects in IE.
        return Lodash.isNumber(value) && value !== +value;
    }

    /**
     * 将数组（array）拆分成多个 size 长度的区块，并将这些区块组成一个新数组
     * @param {Array}array 
     * @param {number}size 
     * @returns 
     */
    public static chunk(array: any[], size: number) {
        var length = array === null ? 0 : array.length;
        if (!length || size < 1) {
            return [];
        }

        var result = [];
        while (array.length > size) {
            result.push(array.slice(0, size));
            array = array.slice(size);
        }

        result.push(array);
        return result;
    }

    /**
     * 转换 value 为一个有限数字
     * @param {any} value 
     * @returns 
     */
    public static toFinite(value: any) {
        var INFINITY = 1 / 0;
        var MAX_INTEGER = 1.7976931348623157e+308;
        if (!value) {
            return value === 0 ? value : 0;
        }
        value = Number(value);
        if (value === INFINITY || value === -INFINITY) {
            var sign = (value < 0 ? -1 : 1);
            return sign * MAX_INTEGER;
        }
        return value === value ? value : 0;
    }

    /**
     * 判断是否为对象
     * @param {any}value  
     * @returns {boolean}
     */
    public static isObject(value: any) {
        var type = typeof value;
        return value !== null && (type === 'object' || type === 'function');
    }

    public static MAX_SAFE_INTEGER = 9007199254740991;

    /**
     * 
     * @param value 
     * @returns 
     */
    public static isLength(value: any) {
        return typeof value === 'number' &&
            value > -1 && value % 1 === 0 && value <= Lodash.MAX_SAFE_INTEGER;
    }

    public static _isArrayLike(value: []) {
        return value !== null && Lodash.isLength(value.length) /*&& !isFunction(value)*/;
    }

    /**
     * 返回数组总符合条件的最大值
     * @param {Array} array  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     * @returns {Object} 返回最大值
     */
    public static maxBy(array: any[], predicate: Function) {
        if (array && array.length) {
            var result;
            var objResult;
            for (var i = 0; i < array.length; i++) {
                if (i === 0) {
                    result = predicate(array[0]);
                    objResult = array[0];
                } else if (result < array[i]) {
                    result = (array[i]);
                    objResult = array[i];
                }
            }

            return objResult;
        }

        return undefined;

    }

    /**
     * 返回数组总符合条件的最小值
     * @param {Array} array  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     * @returns {Object} 返回最小值
     */
    public static minBy(array: any[], predicate: Function) {
        if (array && array.length) {
            let result;
            let objResult;
            for (var i = 0; i < array.length; i++) {
                if (i === 0) {
                    result = predicate(array[0]);
                    objResult = array[0];
                } else if (result > array[i]) {
                    result = predicate(array[i]);
                    objResult = array[i];
                }
            }

            return objResult;
        }

        return undefined;

    }
    /**
     * 返回复合迭代函数的总和
     * @param {Array|Object} collection  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数
     * @returns {Object} 返回总和
     */
    public static sumBy(collection: [] | {}, predicate: Function) {
        let sum: number = 0;
        for (let key in collection) {
            //@ts-ignore
            sum += predicate(collection[key]);
        }

        return sum;
    }

    /**
     * 返回复合迭代函数的次数
     * @param {Array|Object} collection  一个用来迭代的集合.
     * @param {Function} predicate  一个迭代函数，用来转换key（键
     * @returns {Object} 返回一个组成集合对象
     */
    public static countBy(collection: [] | {}, predicate: Function) {
        let objRet: {[key: string]: number} = {};
        for (let key in collection) {
            let value: any = predicate(key);
            if (objRet.hasOwnProperty(value)) {
                objRet[value] += 1;
            } else {
                objRet[value] = 1;
            }
        }

        return objRet;
    }
    
}
