// // transpiler.ts // typescript-monkey //

/// <reference types=“typescript/lib/typescriptServices” />

namespace TypescriptRails {

export enum ScriptType {
    Any,
    Javascript,
    Typescript,
};

/**
 * A class to transpile typescript into javascript in the browser, at
 * runtime.
 *
 * @export
 * @class Transpiler
 */
export class Transpiler {
    public domBody: HTMLElement;

    constructor() {
        this._loadDOM();
    }

    /**
     * Set internal DOM properties.
     *
     * @private
     * @returns {void}
     *
     * @memberOf TypescriptRails
     */
    private _loadDOM(): void {
        this.domBody = document.getElementsByTagName("body")[0];

        return;
    }

    /**
     * Transpile all typescript scripts in DOM.
     *
     * All previous transpiled scripts will be removed before their source
     * is re-transpiled and re-appended to the DOM.
     *
     * @returns {void}
     *
     * @memberof Transpiler
     */
    public transpile(): void {
        let typescripts: HTMLScriptElement[] = this.domScripts(ScriptType.Typescript);
        let javascripts: HTMLScriptElement[] = [];

        // remove all transpiled scripts from DOM
        this.purgeTranspiledScripts();

        // transpile all typescripts
        for (const script of typescripts) {
            javascripts.push(this.transpileScript(script));
        }

        // append transpiled scripts to DOM
        this.appendScripts(javascripts);

        return;
    }

    /**
     * Transpile typescript script to javascript.
     *
     * @param {HTMLScriptElement} script The script object to transpile
     * @returns {HTMLScriptElement} The transpiled script object
     *
     * @memberof Transpiler
     */
    public transpileScript(script: HTMLScriptElement): HTMLScriptElement {
        if (script.type !== "text/typescript") return;

        const compilerOptions: ts.TranspileOptions = {
            compilerOptions: {
                module: ts.ModuleKind.CommonJS,
            },
            fileName: undefined,
            reportDiagnostics: false,
            moduleName: undefined,
            renamedDependencies: undefined,
        } as ts.TranspileOptions;

        // transpile script content
        const jsContent = ts.transpileModule(script.text, compilerOptions);

        // create a new script node, insert transpiled content
        const element = document.createElement("script");
        element.type = "text/javascript";
        element.innerHTML = "// Transpiled TypeScript\n\n" + jsContent.outputText;

        return element;
    }

    /**
     * Append scripts to DOM.
     *
     * Script objects will be assigned an id that is a combination of the
     * prefix plus an incremented value.
     *
     * @param {HTMLScriptElement[]} scripts
     * @param {string} [prefix="drty-"] Object id prefix to filter targets
     * @returns {number} Number of scripts appended
     *
     * @memberof Transpiler
     */
    public appendScripts(scripts: HTMLScriptElement[], prefix: string = "drty-"): number {
        let counter: number = 0;

        for (const script of scripts) {
            script.id = `${prefix + counter}`
            this.domBody.appendChild(script);
            counter++;
        }

        return counter;
    }

    /**
     * Remove transpiled scripts from DOM.
     *
     * @param {string} [prefix="drty-"] Object id prefix to filter targets
     * @returns {number} Number of scripts removed
     *
     * @memberof Transpiler
     */
    public purgeTranspiledScripts(prefix: string = "drty-"): number {
        let counter: number = this.purgeScripts(ScriptType.Javascript, prefix);

        return counter;
    }

    /**
     * Remove scripts from DOM.
     *
     * @param {ScriptType} type Object script type
     * @param {string} prefix Object id prefix to filter targets
     * @returns {number} Number of scripts removed
     *
     * @memberof Transpiler
     */
    public purgeScripts(type: ScriptType, prefix: string): number {
        let scripts: HTMLScriptElement[] = this.domScripts(type, prefix);
        let counter: number = 0;

        for (const script of scripts) {
            this.domBody.removeChild(script);
            counter++;
        }

        return counter;
    }

    /**
     * Returns an array of script objects from DOM.
     *
     * This method returns all scripts if no idPrefix is provided.
     *
     * @private
     * @param {ScriptType} [type=ScriptType.Typescript] Object script type
     * @param {string} [prefix=""] Object id prefix to filter results
     * @returns {HTMLScriptElement[]} Array of script objects
     *
     * @memberof Transpiler
     */
    private domScripts(type: ScriptType = ScriptType.Typescript, prefix: string = ""): HTMLScriptElement[] {
        let nodes: NodeList = document.getElementsByTagName("script");
        let scripts: HTMLScriptElement[] = [];

        Array.prototype.forEach.call(nodes, (node, key, listObj, argument) => {
            const script = node as HTMLScriptElement;

            switch(type) {
                case ScriptType.Javascript:
                    if (script.type !== "text/javascript" && script.type.length > 0) return;
                    break;

                case ScriptType.Typescript:
                    if (script.type !== "text/typescript") return;
                    break;

                default:
                    break;
            }

            const regex = new RegExp("^" + `${prefix}`);
            if (prefix.length === 0 || (script.id.length > 0 && regex.test(script.id))) {
                scripts.push(script);
            }
        });

        return scripts;
    }
} // class Transpiler

} // namespace TypescriptRails