
import { ViewBuildOptions, ViewEntry, ImplementationEntry , BuildAppConfig, ImplementationBuildEntry , ResourceConfig , RuntimeConfiguration , DirectoriesInfo , VirtualImplementationEntry , ViewBuildEntry } from '../index.d';
import { WebpackAppConfig } from '../index-webpack.d';

import path from 'path';
import fs from 'fs-extra';
import fileUtils from '../utils/filepath-utils';

import appConfigUtils from '../utils/module-config-init';

import directoriesUtil from '../tools/directories-utils';
import optionUtils from '../tools/option-utils';

import logUtils from '../utils/log-utils';
import { isWebpackConfig } from '../tools/type-utils';

type ViewDirInfo = {dir: string, ctrlName: string, isLayout: boolean};

const log = logUtils.log;
const warn = logUtils.warn;
var VirtualModulePlugin: any;
function initVirtualModulePlugin(){
    if(!VirtualModulePlugin){
        try{
            VirtualModulePlugin = require('webpack-virtual-modules');
        } catch(err){
            warn('view-utils: failed to initialize webpack-virtual-modules ', err);
        }
    }
    return !!VirtualModulePlugin;
}

function isPartialView(name: string): boolean {
    return name.charAt(0) == '~';
};
var regExprFileExt = /\.ehtml$/i;

function readDir(dir: string, list: ViewEntry[], options?: ViewBuildOptions): void {

    const files = fs.readdirSync(dir);
    const dirs: ViewDirInfo[] = [];
    // log('read dir "'+dir+'" -> ', files);

    files.forEach(function(p){
        var absPath = path.join(dir, p);
        if(fileUtils.isDirectory(absPath)){
            var name = path.basename(absPath);
            var isLayout = /layouts/i.test(name);
            if(!isLayout && name){
                name = name[0].toUpperCase() + name.substring(1);
            }
            dirs.push({dir: absPath, ctrlName: name, isLayout: isLayout});
        } else {
            warn('view-utils.addFromDirectory(): unknow file in view root path: ', absPath);
        }
    });

    // log('read sub-dirs -> ', dirs);
    var size = dirs.length;
    if(size > 0){
        for(var i = 0; i < size; ++i){
            readSubDir(dirs[i], list, options);
        }
    }
}

function readSubDir(dirs: ViewDirInfo, list: ViewEntry[], options?: ViewBuildOptions): void {

    var dir = dirs.dir;
    var files = fs.readdirSync(dir);
    // log('read dir "'+dir+'" -> ', files);

    files.forEach(function(p){
        var absPath = path.join(dir, p);
        if(fileUtils.isDirectory(absPath)){
            warn('view-utils.addFromDirectory(): invalid sub-directory in view-directory: ', absPath);
        } else if(regExprFileExt.test(absPath)) {

            var normalized = fileUtils.normalizePath(absPath);
            var fileName = path.basename(normalized).replace(/\.ehtml/i, '');
            var isLayout = dirs.isLayout;

            var isPartial = false;
            let ctrlName: string, viewName: string;
            if(isLayout && fileName){
                ctrlName = fileName[0].toUpperCase() + fileName.substring(1);
                viewName = ctrlName;
            } else {
                ctrlName = dirs.ctrlName;
                viewName = fileName;
                isPartial = isPartialView(viewName);
                if(isPartial){
                    viewName = viewName.substring(1);
                }
            }

            var id = dirs.ctrlName.toLowerCase() + '/' + fileName;
            var opt = options && options[id];

            list.push({
                id: id,
                ctrlName: ctrlName,
                viewName: viewName,
                file: normalized,
                viewImpl: isLayout? 'mmirf/layout' : isPartial? 'mmirf/partial' : 'mmirf/view',
                isLayout: isLayout,
                isPartial: isPartial,
                strict: opt && typeof opt.strict === 'boolean'? opt.strict : void(0)
            });

        } else {
            warn('view-utils.addFromDirectory(): unknown view template file: ', absPath);
        }
    });
    // log('results for dir "'+dir+'" -> ', ids, views);
}

function toAliasPath(view: ViewEntry): string {
    return path.normalize(view.file);//.replace(/\.ehtml$/i, '')
}

function toAliasId(view: ViewEntry): string {
    return 'mmirf/view/' + view.id;//FIXME formalize IDs for loading views in webpack (?)
}

function containsCtrl(ctrlName: string, ctrlList: Array<ImplementationBuildEntry | VirtualImplementationEntry>): boolean {
    return ctrlList.findIndex(function(c){
        return (c as ImplementationEntry).name === ctrlName;
    }) !== -1;
}

function addCtrlStub(view: ViewEntry, ctrlList: Array<ImplementationBuildEntry | VirtualImplementationEntry>, ctrlMap: Map<string, ImplementationBuildEntry | VirtualImplementationEntry>): void {

    if(view.isLayout){
        return;
    }

    if(containsCtrl(view.ctrlName, ctrlList)){
        return;
    }

    var ctrl = ctrlMap.get(view.ctrlName);
    var isDebug = true;//DEBUG TODO make configurable/settable via options
    if(!ctrl){
        ctrl = {
            moduleName: 'mmirf/controller/'+view.ctrlName[0].toLowerCase()+view.ctrlName.substring(1),
            contents: 'function '+view.ctrlName+'(){'+(isDebug? 'console.log("created stub controller '+view.ctrlName+'");' : '')+'}; ' +
                                    view.ctrlName + '.prototype.on_page_load = function(){'+(isDebug? 'console.log("invoked on_page_load() on stub controller '+view.ctrlName+'");' : '')+'};' +
                                    // 'window.'+view.ctrlName+' = '+view.ctrlName+';' +
                                    'module.exports = '+view.ctrlName+';'
        };
        ctrlMap.set(view.ctrlName, ctrl);
    }
}

export = {

    /**
     * add views from a base-directory that adheres to the structure:
     * <pre>
     * <dir>/<controller name 1>/view1.ehtml
     *                          /view2.ehtml
     *                          ...
     * <dir>/<controller name 2>/view1.ehtml
     *                          /view2.ehtml
     *                          ...
     * ...
     * <dir>/layouts/default.ehtml
     *              /<controller name 1>.ehtml
     *              ...
     * </pre>
     * @param  {String} dir the direcotry from which to parse/collect the views
     * @param {String} appRootDir the root directory of the app (against which relative paths will be resolved)
     * @param  {ViewOptionMap} [options] OPTIONAL
     * 										unsupported TODO
     * @return {Array<ViewEntry>} a list of ViewEntry objects:
     * 									{
     * 										id: String
     * 										ctrlName: String,
     * 										viewName: String,
     * 										file: String,
     * 										viewImpl: 'mmirf/layout' | 'mmirf/partial' | 'mmirf/view',
     * 										isLayout: Boolean
     * 										isPartial: Boolean
     * 									}
     */
    viewTemplatesFromDir: function(dir: string, appRootDir: string, options?: ViewBuildOptions): ViewBuildEntry[] {

        if(!path.isAbsolute(dir)){
            dir = path.resolve(appRootDir, dir);
        }
        var list = [];
        readDir(dir, list, options);

        return list;
    },

    /**
     * apply the "global" options from `options` or default values to the entries
     * from `viewList` if its corresponding options-field is not explicitly specified.
     *
     * @param  {ViewOptions} options the view options
     * @param  {{Array<ViewEntry>}} viewList
     * @return {{Array<ViewEntry>}}
     */
    applyDefaultOptions: function(options: ViewBuildOptions, viewList: ViewEntry[]): ViewEntry[] {

        //TODO impl. if/when addFromOpitions is implemented...
        viewList.forEach(function(v){
            [
                // {name: 'ignoreErrors', defaultValue: false},	//TODO impl. if/when addFromOpitions is implemented...
                // {name: 'force', defaultValue: false},	//TODO impl. if/when addFromOpitions is implemented...
                {name: 'strict', defaultValue: true}
            ].forEach(function(fieldInfo){
                optionUtils.applySetting(fieldInfo.name, v, options, fieldInfo.defaultValue);
            });

        });

        return viewList;
    },

    /**
     * add views to (webpack) app build configuration
     *
     * @param  {Array<ViewEntry>} view list of ViewEntry objects:
     * 										view.id {String}: the view id
     * 										view.file {String}: the path to the eHTML view template (from which the executable view will be created)
     * @param  {Array<ControllerEntry>} ctrls list of ControllerEntry objects:
     * 										ctrl.id {String}: the controller id
     * @param  {[type]} appConfig the app configuration to which the views will be added
     * @param  {[type]} directories the directories.json representation
     * @param  {ResourcesConfig} resources the resources configuration
     * @param  {[type]} runtimeConfiguration the configuration.json representation
     */
    addViewsToAppConfig: function(views: ViewEntry[], ctrls: ImplementationBuildEntry[], appConfig: BuildAppConfig | WebpackAppConfig, directories: DirectoriesInfo, resources: ResourceConfig, _runtimeConfiguration: RuntimeConfiguration): void {

        if(!views || views.length < 1){
            return;
        }

        var stubCtrlMap = new Map();

        views.forEach(function(v){

            var aliasId = toAliasId(v);
            if(isWebpackConfig(appConfig)){
                appConfigUtils.addIncludeModule(appConfig, aliasId, toAliasPath(v));
            }
            directoriesUtil.addView(directories, aliasId);
            if((appConfig as BuildAppConfig).includeViewTemplates){
                directoriesUtil.addViewTemplate(directories, aliasId);
            }

            addCtrlStub(v, ctrls, stubCtrlMap);
        });

        //FIXME set simpleViewEngine TODO support setting engine via appConfig
        resources.paths['mmirf/simpleViewEngine'] = 'env/view/simpleViewEngine';

        if(!isWebpackConfig(appConfig)){
            return;
        }

        // include dependencies for loading & rendering views:
        appConfig.includeModules.push('mmirf/storageUtils', 'mmirf/renderUtils');
        appConfig.includeModules.push('mmirf/yield', 'mmirf/layout', 'mmirf/view', 'mmirf/partial');//TODO only include types that were actually parsed

        if(!appConfig.paths){
            appConfig.paths = {};
        }

        // replace default viewLoader with webpack-viewLoader:
        appConfig.paths['mmirf/viewLoader'] = path.resolve(__dirname, '..', 'runtime', 'webpackViewLoader.js');

        //add generated stub controllers if necessary:
        if(stubCtrlMap.size > 0 && appConfig.controllers !== false){

            if(initVirtualModulePlugin()){

                if(!appConfig.webpackPlugins){
                    appConfig.webpackPlugins = [];
                }

                var virtualModules = {};
                stubCtrlMap.forEach(function(ctrl, name){
                    var id = ctrl.moduleName;
                    log('view-utils: adding view controller stub "'+name+'": ', id, ' -> ', ctrl);//DEBUG
                    // appConfig.paths[id] = id;// path.resolve('./viewParser/webpackGenCtrl.js');
                    // appConfig.includeModules.push(id);
                    appConfigUtils.addIncludeModule(appConfig, id, id);

                    directoriesUtil.addCtrl(directories, ctrl.moduleName);
                    virtualModules[ctrl.moduleName] = ctrl.contents;
                });
                appConfig.webpackPlugins.push(new VirtualModulePlugin(virtualModules));

            } else {
                warn('view-utils: cannot add stub controllers, because of misssing package virtual-module-webpack-plugin');
            }
        }
    },

    // getCtrlImpl: function(){
    // 	return controllers.slice();
    // }
};
