import * as Mustache from 'mustache';
import * as FS from 'fs';
import * as Path from 'path';
import * as Walk from 'walk';
import { ACUtils } from './cube-utils';

const CopyDIR = require('copy-dir');

export default class ACProject {
    pkg : any | undefined;
    constructor(path?:string) {
        if (path) {
            this.pkg = JSON.parse(FS.readFileSync(Path.join(path, 'package.json')).toString());
        }
    }
    async create(option: ACPTemplatesOption | ACPApplicationOption): Promise<void> {
        switch (option.type) {
            case ACProjectType.ACProjectApplication:
                return await this.createApplicationProject(option);
            case ACProjectType.ACProjectTemplates:
                return await this.createTemplatesProject(option);
            default:
                return Promise.reject(new Error(`无效的工程参数 \n${JSON.stringify(option, undefined, 2)}`));
        }
    }
    private async createTemplatesProject(option: ACPTemplatesOption): Promise<void> {
        if (FS.existsSync(option.path)) {
            return Promise.reject(new Error(`工程已存在 ${option.path}`));
        }
        FS.mkdirSync(option.path);
        const mode = this.project(option.id);
        if (!mode) {
            return Promise.reject(new Error(`无效的工程模式 ${option.id} : ${option.type}`));
        }
        const fillup = (modepath: string, optionpath: string, filename: string, value: any): void => {
            let content = FS.readFileSync(Path.join(modepath, filename)).toString();
            content = Mustache.render(content, value);
            FS.writeFileSync(Path.join(optionpath, filename), content);
        };
        //  fill package
        fillup(mode.path, option.path, 'package.json', option);
        
        if (option.templates) {
            for (let index = 0; index < option.templates.length; index++) {
                const tploption = option.templates[index];
                tploption.path = Path.join(option.path, `${tploption.name}`);
                await this.createTemplateFiles(tploption);
            }
        }

        return Promise.resolve();
    }
    async createTemplateFiles(option: ACFTemplateOption): Promise<void> {
        if (FS.existsSync(option.path)) {
            return Promise.reject(`模版已存在 ${option.path}`);
        }
        FS.mkdirSync(option.path);
        const mode = this.template(option.id);
        if (!mode) {
            return Promise.reject(new Error(`无效的模版类型 ${option.id} : ${option.name}`));
        }
        const fillup = (modepath: string, optionpath: string, filename: string, value: any): void => {
            let content = FS.readFileSync(Path.join(modepath, filename)).toString();
            content = Mustache.render(content, value);
            FS.writeFileSync(Path.join(optionpath, filename), content);
        };
        fillup(mode.path, option.path, 'main.vue', option);
        fillup(mode.path, option.path, 'manifest.json', option);
        fillup(mode.path, option.path, 'mock.json', option);
        return Promise.resolve();
    }
    private async createApplicationProject(option: ACPApplicationOption): Promise<void> {
        if (FS.existsSync(option.path)) {
            return Promise.reject(new Error(`工程已存在 ${option.path}`));
        }
        return new Promise((resolve, rejects)=>{
            const mode = this.project(option.id);
            if (mode) {
                CopyDIR(mode.path, option.path, (stats:any, filepath:any, filename:any)=>{
                    return ['info.json'].indexOf(filename) === -1 ? true : false;
                }, (err:any)=>{
                    if (err) {
                        rejects(err);
                    } else {
                        Walk.walkSync(option.path, {
                            listeners: {
                                file: (root: string, names: Walk.WalkStats, next: Walk.WalkNext) => {
                                    option.type = mode.type;
                                    const fpath = Path.join(root, names.name);
                                    let content = FS.readFileSync(fpath).toString();
                                    content = Mustache.render(content, option);
                                    FS.writeFileSync(fpath, content);
                                    next();
                                },
                                errors: (root: string, names: Walk.WalkStats[], next: Walk.WalkNext) => {
                                    next();
                                }
                            }
                        });
                        resolve();
                    }
                });
            } else {
                rejects(new Error('invalid project type'));
            }
        });
    }
    projects(): Array<ACMode> {
        const modes = new Array();
        this.index().projects.forEach((ele: string) => {
            modes.push(new ACMode(ele));
        });
        return modes;
    }
    project(id: string): ACMode {
        return this.projects().filter(value => {
            return value.id === id;
        })[0];
    }
    templates(): Array<ACMode> {
        const modes = new Array();
        this.index().templates.forEach((ele: string) => {
            modes.push(new ACMode(ele));
        });
        return modes;
    }
    template(id: string): ACMode {
        return this.templates().filter(value => {
            return value.id === id;
        })[0];
    }
    sdks(): ACSdk[] {
        return this.index().cubeSDK;
    }
    private index(): any {
        return JSON.parse(FS.readFileSync(Path.join(ACUtils.respath, `template/index.json`)).toString());
    }
}

export class ACMode {
    id: string = '';
    info: any = {};
    path: string = '';
    
    constructor(id:string) {
        this.id = id;
        this.path = Path.join(ACUtils.respath, `template/${this.id}`);
        this.info = JSON.parse(FS.readFileSync(Path.join(this.path, `info.json`)).toString());
    }
    get name(): string {
        return this.info.name;
    }
    get type(): string {
        return this.info.type;
    }
}

export interface ACPApplicationOption {
    id: string;
    name: string;
    type: string;
    path: string;
    desc?: string;
}
export interface ACPTemplatesOption extends ACPApplicationOption {
    templates?: Array<ACFTemplateOption>;
}
export interface ACFTemplateOption {
    id: string;
    version: string;
    name: string;
    sdk_version: string;
    path: string;
    desc?: string;
    extend?: string;
}

export enum ACProjectType {
    ACProjectApplication = 'application',
    ACProjectTemplates = 'templates'
}

export interface ACSdk {
    version:string;
}