// declare require to support dynamic text-encoding module loading in node
declare function require(moduleName: string): any;

// declare window to use in browser
declare var window: any;

import { TextDecoder as TextDecoderFromTE, TextEncoder as TextEncoderFromTE } from 'text-encoding';
import CharacterSetECI from './../common/CharacterSetECI';
import Exception from './../Exception';

export default class StringEncoding {
    public static decode(bytes: Uint8Array, encoding: string | CharacterSetECI): string {
        let encodingString: string;
        if (typeof encoding === 'string') {
            encodingString = encoding;
        } else {
            encodingString = encoding.getName();
        }
        if (StringEncoding.isBrowser()) {
            // tslint:disable-next-line:no-string-literal
            const TextDecoderBrowser = window['TextDecoder'];
            // use TextEncoder if is available (should be in newer browsers)
            if (undefined !== TextDecoderBrowser) {
                // console.log(TextDecoderBrowser)
                return new TextDecoderBrowser(encoding).decode(bytes);
            } else {
                // fall back to minimal decoding
                return StringEncoding.decodeFallBack(bytes, encodingString);
            }
        } else {
            const TextDecoderFromTEClass: typeof TextDecoderFromTE = require('text-encoding').TextDecoder;
            return new TextDecoderFromTEClass(encodingString).decode(bytes);
        }
    }

    public static encode(s: string, encoding: string | CharacterSetECI): Uint8Array {
        let encodingString: string;
        if (typeof encoding === 'string') {
            encodingString = encoding;
        } else {
            encodingString = encoding.getName();
        }
        if (StringEncoding.isBrowser()) {
            // tslint:disable-next-line:no-string-literal
            const TextEncoderBrowser = window['TextEncoder'];
            // use TextEncoder if is available (should be in newer browsers)
            const ec = CharacterSetECI.getCharacterSetECIByName(encodingString);
            if (undefined !== TextEncoderBrowser) {
                // TODO: TextEncoder only supports utf-8 encoding as per specs
                return new TextEncoderBrowser(encoding).encode(s);
            } else {
                // fall back to minimal decoding
                return StringEncoding.encodeFallBack(s, encodingString);
            }
        } else {
            // Note: NONSTANDARD_allowLegacyEncoding is required for other encodings than UTF8
            // TextEncoder only encodes to UTF8 by default as specified by encoding.spec.whatwg.org
            const TextEncoderFromTEClass: typeof TextEncoderFromTE = require('text-encoding').TextEncoder;
            return new TextEncoderFromTEClass(encodingString, { NONSTANDARD_allowLegacyEncoding: true }).encode(s);
        }
    }

    private static isBrowser(): boolean {
        return typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]';
    }

    private static decodeFallBack(bytes: Uint8Array, encoding: string): string {
        const ec = CharacterSetECI.getCharacterSetECIByName(encoding);
        if (ec.equals(CharacterSetECI.UTF8) ||
            ec.equals(CharacterSetECI.ISO8859_1) ||
            ec.equals(CharacterSetECI.ASCII)) {
            let s = '';
            for (let i = 0, length = bytes.length; i < length; i++) {
                let h = bytes[i].toString(16);
                if (h.length < 2) {
                    h = '0' + h;
                }
                s += '%' + h;
            }
            return decodeURIComponent(s);
        } else if (ec.equals(CharacterSetECI.UnicodeBigUnmarked)) {
            return String.fromCharCode.apply(null, new Uint16Array(bytes.buffer));
        } else {
            throw new Exception(Exception.UnsupportedOperationException, `encoding ${encoding} not supported`);
        }
    }

    private static encodeFallBack(s: string, encoding: string): Uint8Array {
        // TODO: encode
        return null;
    }
}
