import { BrowserDetectInfo, OsDefinition, OsDefinitionInterface } from './browser-detect.interface';
import { browsers, os, osVersions } from './definitions';
import { mobilePrefixRegExp, mobileRegExp } from './regexp';
import Process = NodeJS.Process;

export class Detector {
    private userAgent: string;

    public constructor(
        userAgent: string,
        private navigator?: Navigator,
        private process?: Process
    ) {
        this.userAgent = userAgent
            ? userAgent
            : this.navigator ? (navigator.userAgent || navigator.vendor) : '';
    }

    public detect(): BrowserDetectInfo {
        if (this.process && !this.userAgent) {
            const version = this.process.version.slice(1) .split('.').slice(0, 3);
            const versionTail = Array.prototype.slice.call(version, 1).join('') || '0';

            return {
                name: 'node',
                version: version.join('.'),
                versionNumber: parseFloat(`${version[0]}.${versionTail}`),
                mobile: false,
                os: this.process.platform
            };
        }

        if (!this.userAgent)
            this.handleMissingError();

        return {
            ...this.checkBrowser(),
            ...this.checkMobile(),
            ...this.checkOs()
        };
    }

    private checkBrowser(): BrowserDetectInfo {
        return browsers
            .filter(definition => (<RegExp>definition[1]).test(this.userAgent))
            .map(definition => {
                const match = (<RegExp>definition[1]).exec(this.userAgent);
                const version = match && match[1].split(/[._]/).slice(0, 3);
                const versionTails = Array.prototype.slice.call(version, 1).join('') || '0';

                if (version && version.length < 3)
                    Array.prototype.push.apply(version, version.length === 1 ? [0, 0] : [0]);

                return {
                    name: String(definition[0]),
                    version: version.join('.'),
                    versionNumber: Number(`${version[0]}.${versionTails}`)
                };
            })
            .shift();
    }

    private checkMobile(): BrowserDetectInfo {
        const agentPrefix = this.userAgent.substr(0, 4);
        const mobile = mobileRegExp.test(this.userAgent) || mobilePrefixRegExp.test(agentPrefix);
        return { mobile };
    }

    private checkOs(): BrowserDetectInfo {
        return os
            .map(definition => {
                const name = (<OsDefinitionInterface>definition).name || <string>definition;
                const pattern = this.getOsPattern(definition);

                return {
                    name,
                    pattern,
                    value: RegExp(
                        `\\b${
                            pattern.replace(/([ -])(?!$)/g, '$1?')
                            }(?:x?[\\d._]+|[ \\w.]*)`,
                        'i'
                    ).exec(this.userAgent)
                };
            })
            .filter(definition => definition.value)
            .map(definition => {
                let os = definition.value[0] || '';
                let osSuffix: string;

                if (
                    definition.pattern &&
                    definition.name &&
                    /^Win/i.test(os) &&
                    !/^Windows Phone /i.test(os) &&
                    (osSuffix = osVersions[os.replace(/[^\d.]/g, '')])
                )
                    os = `Windows ${osSuffix}`;

                if (definition.pattern && definition.name)
                    os = os.replace(RegExp(definition.pattern, 'i'), definition.name);

                os = os
                    .replace(/ ce$/i, ' CE')
                    .replace(/\bhpw/i, 'web')
                    .replace(/\bMacintosh\b/, 'Mac OS')
                    .replace(/_PowerPC\b/i, ' OS')
                    .replace(/\b(OS X) [^ \d]+/i, '$1')
                    .replace(/\bMac (OS X)\b/, '$1')
                    .replace(/\/(\d)/, ' $1')
                    .replace(/_/g, '.')
                    .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
                    .replace(/\bx86\.64\b/gi, 'x86_64')
                    .replace(/\b(Windows Phone) OS\b/, '$1')
                    .replace(/\b(Chrome OS \w+) [\d.]+\b/, '$1')
                    .split(' on ')[0]
                    .trim();

                os = /^(?:webOS|i(?:OS|P))/.test(os)
                    ? os
                    : (os.charAt(0).toUpperCase() + os.slice(1));

                return { os };
            })
            .shift();
    }

    private getOsPattern(definition: OsDefinition): string {
        const definitionInterface = <OsDefinitionInterface>definition;
        return (
            typeof definition === 'string'
                ? <string>definition
                : undefined
        ) ||
        definitionInterface.pattern ||
        definitionInterface.name;
    }

    private handleMissingError() {
        throw new Error('Please give user-agent.\n> browser(navigator.userAgent or res.headers[\'user-agent\']).');
    }
}