import { Observable } from "rxjs";

const dateAndTimeRegex = new RegExp(/^(?<Date>\d{4}-\d{2}-\d{2})T(?<HourMin>\d{2}:\d{2}):((?<SecondAndMillisecond>\d{2}\.\d{0,6})|(?<Second>\d{2}))$/);

export class ObjectUtils {

    private static ReviveDateTime(key: any, value: any): any {
        if (typeof value === 'string') {
            if (dateAndTimeRegex.test(value)) {
                let newDate = new Date(value);
                return newDate;
            }
        }

        return value;
    }

    public static HasAnyData(obj: any, excludes: string[] = []) {
        var hasData = false;

        for (const p in obj) {
            if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(obj, p)) {
                const element = obj[p];
                if (element) {
                    if (typeof element !== 'object' || this.HasAnyData(element, excludes)) {
                        hasData = true;
                        break;
                    }
                }
            }
        }

        return hasData;
    }
    public static Clone<T = any>(obj: T, avoidCirculateRef = false): T {
        if (avoidCirculateRef) {
            return JSON.parse(this.stringify(obj), ObjectUtils.ReviveDateTime);
        }
        return JSON.parse(JSON.stringify(obj), ObjectUtils.ReviveDateTime);
    }

    public static CopyValue(source: any, destination: any, excludes: string[] = ["id"], overwriting: boolean = true) {

        for (const p in source) {
            if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) {
                const element = source[p];

                if (element && Array.isArray(element)) {
                    if ([null, undefined].includes(destination[p])) {
                        destination[p] = [];
                        for (let i = 0; i < element.length; i++) {
                            const cloneItem = {};
                            this.CopyValue(element[i], cloneItem, excludes, true);
                            destination[p].push(cloneItem);
                        }
                    } else if (Array.isArray(destination[p])) {

                        for (let i = 0; i < element.length; i++) {
                            const item = element[i];
                            let destLength = destination[p].length;
                            if (i >= destLength) {
                                destination[p].push(this.Clone(source[p][i]));
                            }
                            this.CopyValue(item, destination[p][i], excludes, overwriting);
                        }
                    }
                }
                else if (element && typeof element.getMonth === 'function') {
                    //For angular will treat Date as object
                    try {
                        destination[p] = element;
                    } catch (error) {
                        console.log(`can\'t copy ${p}`, error);
                    }
                }
                else if (element && typeof element == 'object') {
                    let objectOverwriting = overwriting;
                    if ([null, undefined].includes(destination[p])) {
                        destination[p] = {};
                        objectOverwriting = true;
                    }
                    try {
                        this.CopyValue(element, destination[p], excludes, objectOverwriting);
                    } catch (error) {
                        console.log(`can\'t copy ${p}`, error);
                    }
                } else if (overwriting || [null, '', undefined].includes(destination[p])) {
                    try {
                        destination[p] = element;
                    } catch (error) {
                        console.log(`can\'t copy ${p}`, error);
                    }
                }
            }
        }
    }
    public static ClearPkAndFk(source: any, excludes: string[] = [], clearExtra: string[] = []) {

        for (const p in source) {
            const element = source[p];

            if (element) {
                if (clearExtra.includes(p)) {
                    source[p] = null;
                } else if (typeof element == 'object') {
                    this.ClearPkAndFk(element, excludes, clearExtra);
                } else if (
                    (typeof element == 'string' && (p == 'id' || p.indexOf('Id') > -1) &&
                        false == excludes.includes(p))
                ) {
                    source[p] = null;
                }
            }
        }
    }
    public static ClearValue(source: any, excludes: string[] = ["id"]) {
        for (const p in source) {
            if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) {
                const element = source[p];

                if (element) {
                    if (typeof element == 'object') {
                        this.ClearValue(element, excludes);
                    } else if (typeof element == 'number') {
                        source[p] = 0;
                    } else {
                        source[p] = null;
                    }
                }
            }
        }
    }
    /**
     * return `true` if the comparison has any different value with source.
     */
    public static CompareDiffValue(source: any, comparison: any, excludes: string[] = ["id"]) {
        let isDifferent = false;
        for (const p in source) {
            if (false == excludes.includes(p) &&
                Object.prototype.hasOwnProperty.call(source, p)) {
                const element = source[p];
                if (element && Array.isArray(element)) {
                    if (Array.isArray(comparison[p])) {
                        for (let i = 0; i < element.length; i++) {
                            const item = element[i];
                            let destLength = comparison[p].length;
                            if (i >= destLength) {
                                return true;
                            }
                            isDifferent = this.CompareDiffValue(item, comparison[p][i], excludes);
                            if (isDifferent) return true;
                        }
                    }
                }
                else if (element && typeof element.getMonth === 'function') {
                    //For angular will treat Date as object
                    //TODO:Compare Date                    
                    //comparison[p] = element;
                }
                else if (element && typeof element == 'object') {
                    isDifferent = this.CompareDiffValue(element, comparison[p], excludes);

                } else {
                    isDifferent = comparison[p] != element;
                }

                if (isDifferent) return true;
            }
        }
        return false;
    }
    /**
     * return `true` if the comparison has any different value with source.
     */
    public static CompareDiffArrayContent(source: any[], comparison: any[], excludes: string[] = ["id"]) {
        let isDifferent = false;
        if (source && comparison) {
            if (source.length == comparison.length) {
                for (let i = 0; i < source.length; i++) {
                    const sourceItem = source[i];
                    const comparisonItem = comparison[i];

                    isDifferent = this.CompareDiffValue(sourceItem, comparisonItem);
                    if (isDifferent) return true;
                }
                return false;
            }
        }

        return true;
    }
    public static nullCondition<T>(source: T) {
        if (source != null) return source;
    }

    public static isNullOrUndefined(obj: any) {
        return [null, undefined].includes(obj);
    }

    public static isObservable<T>(template: T | Observable<T>): template is Observable<T> {
        return (template as Observable<T>).subscribe !== undefined;
    }
    public static stringify(obj) {
        let cache = [];
        let str = JSON.stringify(obj, function (key, value) {
            if (typeof value === "object" && value !== null) {
                if (cache.indexOf(value) !== -1) {
                    // Circular reference found, discard key
                    return;
                }
                // Store value in our collection
                cache.push(value);
            }
            return value;
        });
        cache = null; // reset the cache
        return str;
    }

}
