import * as Eslint from 'eslint';
import * as Walk from 'walk';
import * as Path from 'path';
import * as Mustache from 'mustache';
import * as Stylelint from 'stylelint';
import ACProject from './cube-project';

const styleConfig = require('stylelint-plugin-antcube');

const AC_LINT_FILE_MESSAGE = `
[FILE]: {{&filePath}}
    [error({{&errorCount}}) warning({{&warningCount}})]
    {{#messages}}
    *   [{{&severity}}] ({{&line}}:{{&column}}) {{&message}} <{{&ruleId}}>
{{/messages}}`;

export default class ACLint {
    
    public async lintProject(path: string): Promise<ACLintReport> {
        const project = new ACProject(path);
        if (project.pkg && project.pkg.cube && project.pkg.cube.lint === 'off') {
            return Promise.resolve({
                errorCount: 0,
                warningCount: 0,
                message: '跳过语法检测'
            });
        }
        if (project.pkg && project.pkg.type === 'templates') {
            return await this.lintTemplates(path, project);
        } else {
            return Promise.resolve({
                errorCount: 0,
                warningCount: 0,
                message: ''
            });
        }
    }
    private async lintTemplates(path: string, project: ACProject): Promise<ACLintReport> {
        const paths = new Array();
        Walk.walkSync(path, {
            listeners: {
                file: (root: string, names: Walk.WalkStats, next: Walk.WalkNext) => {
                    const fpath = Path.join(root, names.name);
                    const finfo = Path.parse(fpath);
                    if (finfo.ext === '.vue') {
                        paths.push(fpath);
                    }
                    next();
                },
                errors: (root: string, names: Walk.WalkStats[], next: Walk.WalkNext) => {
                    next();
                }
            }
        });
        //  ES-LINT
        const esReport = this.eslintEngine(project).executeOnFiles(paths);
        const esFormated = await this.formatESLintReport(esReport);
        //  STYLE-LINT
        const styleReport = await Stylelint.lint({
            config: this.stylelintConfig(project),
            files: paths
        });
        const styleFormated = await this.formatStyleLintReport(styleReport, project);
        let styleErrorCount = 0;
        styleReport.results.forEach(ele => {
            styleErrorCount += ele.warnings.length;
        });

        const errorCount = esReport.errorCount + styleErrorCount;
        const warningCount = esReport.warningCount;
        const result = [
            `antcube-lint found: error(${errorCount}) warning(${warningCount})\n`,
            esFormated,
            styleFormated
        ].join('');

        return Promise.resolve({
            errorCount,
            warningCount,
            message: result
        });
    }
    private async formatESLintReport(report: any): Promise<string> {
        if (report.errorCount === 0 && report.warningCount === 0) {
            return Promise.resolve('');
        }
        const messages = new Array();
        for (let index = 0; index < report.results.length; index++) {
            const file = report.results[index];
            let message = Mustache.render(AC_LINT_FILE_MESSAGE, file);
            message = message.split('*   [1]').join('*   [WARN]');
            message = message.split('*   [2]').join('*   [ERRO]');
            messages.push(message);
        }
        return Promise.resolve(messages.join(''));
    }
    private async formatStyleLintReport(report: any, project: any): Promise<string> {
        if (!report.errored) {
            return Promise.resolve('');
        }
        const messages = new Array();
        for (let index = 0; index < report.results.length; index++) {
            const rep = report.results[index];
            const filemsgs = new Array();
            rep.warnings.forEach((msg: any) => {
                filemsgs.push({
                    severity: 'ERRO',
                    line: msg.line,
                    column: msg.column,
                    message: msg.text,
                    ruleId: msg.rule
                });
            });
            messages.push(Mustache.render(AC_LINT_FILE_MESSAGE, {
                filePath: rep.source,
                errorCount: rep.warnings.length,
                warningCount: 0,
                messages: filemsgs
            }));
        }
        return Promise.resolve(messages.join(''));
    }
    private eslintEngine(project: ACProject): Eslint.CLIEngine {
        let config: Eslint.CLIEngine.Options = {
            useEslintrc: true,
            parser: 'vue-eslint-parser',
            parserOptions: {
                ecmaVersion: 2015
            },
            plugins: ['eslint-plugin-antcube'],
            rules: {
                "antcube/falcon-buildin-component": 'error',
                "antcube/falcon-component-attribute": 'error',
                "antcube/falcon-component-event": 'error',
                "antcube/falcon-valid-expression": 'error'
            }
        };
        if (project.pkg && project.pkg.cube) {
            let rules:any = Object.assign({}, config.rules);
            if (project.pkg.cube.rules) {
                rules = Object.assign(config.rules, this.filterRules(project.pkg.cube.rules, (key, value)=>{return key.startsWith('antcube/falcon-');}));
            }
            if (project.pkg.cube.widgets) {
                rules['antcube/falcon-buildin-component'] = [rules['antcube/falcon-buildin-component'], project.pkg.cube.widgets];
            }
            config = Object.assign(config, {rules});
        }
        return new Eslint.CLIEngine(config);
    }
    private stylelintConfig(project: ACProject): Partial<Stylelint.Configuration> {
        let config = styleConfig;
        if (project.pkg && project.pkg.cube && project.pkg.cube.rules) {
            const rules = this.filterRules(config.rules, (key, value) => {
                const rl = project.pkg.cube.rules[key];
                return rl && rl === 'off' ? false : true;
            });
            config = Object.assign(config, {rules});
        }
        return styleConfig;
    }
    private filterRules(rules: any, conditions: (key:string, value:any)=>boolean): any  {
        const result:any = new Object();
        for (const key in rules) {
            if (rules.hasOwnProperty(key)) {
                if (conditions(key, rules[key])) {
                    result[key] = rules[key];
                }
            }
        }
        return result;
    }
}

export interface ACLintReport {
    errorCount: number;
    warningCount: number;
    message: string;
}