/*
 * @Author: Rindy
 * @Date: 2021-09-03 11:53:21
 * @LastEditors: Rindy
 * @LastEditTime: 2021-09-03 15:14:25
 * @Description:
 */

export const objectToString = Object.prototype.toString
export const toTypeString = value => objectToString.call(value)

// 获取制定对象的类型比如toRawType(1) Number
export const toRawType = value => toTypeString(value).slice(8, -1)

/**
 * 判断是否函数
 * @param {any} target 参数对象
 */
export const isFunction = target => toRawType(target) === 'Function'

/**
 * 判断是否普通对象
 * @param {any} target 参数对象
 */
export function isPlainObject(target) {
    if (!target || typeof target !== 'object' || {}.toString.call(target) != '[object Object]') {
        return false
    }
    var proto = Object.getPrototypeOf(target)
    if (proto === null) {
        return true
    }
    var Ctor = {}.hasOwnProperty.call(proto, 'constructor') && proto.constructor
    return typeof Ctor == 'function' && Ctor instanceof Ctor && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object)
}

/**
 * 获取忽略指定属性的对象
 * @param {Object} obj 源对象
 * @param  {...any} props 忽略属性
 */
export function omit(obj, ...props) {
    const result = { ...obj }
    props.forEach(function (prop) {
        delete result[prop]
    })
    return result
}

export const randomId = (e = 6) => {
    var t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678',
        a = t.length,
        n = ''
    for (let i = 0; i < e; i++) {
        n += t.charAt(Math.floor(Math.random() * a))
    }
    return n
}

/**
 * 缓存指定方法运行结果
 * @param {*} fn
 * @param {*} overdue 缓存超时时间
 * @returns
 */
export const cache = (fn, overdue) => {
    const cacheMap = new WeakMap()

    return function (...args) {
        let caches = cacheMap.get(fn)
        if (!caches) {
            caches = []
            cacheMap.set(fn, caches)
        }

        for (let i = 0; i < caches.length; i++) {
            const { oldNow, ret, oldArgs } = caches[i]

            if (oldArgs.length === args.length && args.every((arg, i) => arg === oldArgs[i])) {
                if (Date.now() - oldNow > overdue) {
                    caches.splice(i, 1)
                    break
                } else {
                    return ret
                }
            }
        }

        const item = {
            oldNow: Date.now(),
            ret: fn.apply(this, args),
            oldArgs: args,
        }
        caches.push(item)

        setTimeout(() => {
            const index = caches.indexOf(item)
            if (~index) {
                caches.splice(index, 1)
            }
        })

        return item.ret
    }
}

// 是否修改
const _inRevise = (raw1, raw2, readly) => {
    if (raw1 === raw2) return false

    const rawType1 = toRawType(raw1)
    const rawType2 = toRawType(raw2)

    if (rawType1 !== rawType2) {
        return true
    } else if (rawType1 === 'String' || rawType1 === 'Number' || rawType1 === 'Boolean') {
        return raw1 !== raw2
    }

    const rawsArray = Array.from(readly.values())
    for (const raws of rawsArray) {
        if (raws.includes(raw1) && raws.includes(raw2)) {
            return false
        }
    }
    readly.add([raw1, raw2])

    if (rawType1 === 'Array') {
        return raw1.length !== raw2.length || raw1.some((item1, i) => _inRevise(item1, raw2[i], readly))
    } else if (rawType1 === 'Object') {
        const rawKeys1 = Object.keys(raw1).sort()
        const rawKeys2 = Object.keys(raw2).sort()

        return _inRevise(rawKeys1, rawKeys2, readly) || rawKeys1.some(key => _inRevise(raw1[key], raw2[key], readly))
    } else if (rawType1 === 'Map') {
        const rawKeys1 = Array.from(raw1.keys()).sort()
        const rawKeys2 = Array.from(raw2.keys()).sort()

        return _inRevise(rawKeys1, rawKeys2, readly) || rawKeys1.some(key => _inRevise(raw1.get(key), raw2.get(key), readly))
    } else if (rawType1 === 'Set') {
        return inRevise(Array.from(raw1.values()), Array.from(raw2.values()))
    } else {
        return raw1 !== raw2
    }
}

export const os = (function () {
    let ua = navigator.userAgent,
        isWindowsPhone = /(?:Windows Phone)/.test(ua),
        isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
        isAndroid = /(?:Android)/.test(ua),
        isFireFox = /(?:Firefox)/.test(ua),
        isChrome = /(?:Chrome|CriOS)/.test(ua),
        isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
        isPhone = /(?:iPhone)/.test(ua) && !isTablet,
        isPc = !isPhone && !isAndroid && !isSymbian

    if (isPc && navigator.maxTouchPoints > 1) {
        isTablet = true
    }
    return {
        isTablet: isTablet,
        isPhone: isPhone,
        isAndroid: isAndroid,
        isPc: isPc,
    }
})()

export const inRevise = (raw1, raw2) => _inRevise(raw1, raw2, new Set())

export * from './dom'
export * from './zindex'
export * from './vm'
