/* Copyright IBM Corp. 2017 */
import { escapeRegExp } from 'lodash';
import { resolve } from 'path';
import { combineLatest, concat, defer, Observable } from 'rxjs';
import { map, mergeMap, shareReplay, toArray } from 'rxjs/operators';
import { isString } from 'util';

import { arrayOf, firstOf } from '../utils/file.utils';
import {
    CMD_CREATE,
    OPTION_DATA,
    OPTION_EDITABLE,
    OPTION_FLAT,
    OPTION_NAME,
    OPTION_SCSS,
    OPTION_SOURCE,
    OPTION_TYPE,
    OPTION_VERBOSE,
} from './../constants';
import { LayoutNameMapper, Options, optNormalizeDir } from './../options';
import { assertNotNull, assertParameter } from './../utils/assert';
import { camelCase } from './../utils/transliteration';
import { AbstractCommand } from './abstract.command';
import { Command } from './command';
import { CreateVueCommand } from './create.vue.command';
import { CreateLayoutsCommand } from './create.layouts.command';
import {
    HELP_COMPONENT_SOURCE,
    HELP_DATA_DIRECTORY,
    HELP_EDITABLE,
    HELP_FLAT,
    HELP_LAYOUT_NAME,
    HELP_LAYOUT_TYPE,
    HELP_SCSS,
    HELP_VERBOSE,
} from './help';

function _createLayoutNameMapper(aLayoutName: string): LayoutNameMapper {
    return (aNumber: number, aTotal: number, aTypeName: string) => camelCase(aLayoutName);
}

/**
 * create layout --data <DATA> --src <SRC> --type <TYPE> [--name <NAME>] [--verbose] [--flat] [--scss]
 */
export class CreateLayoutCommand extends AbstractCommand implements Command {

    static readonly TYPE = 'layout';
    static readonly COMMAND = CMD_CREATE;

    constructor(private options: Options) {
        super(options);
    }

    printHelp(): void {
        // log the command line
        console.log(CreateLayoutCommand.COMMAND, CreateLayoutCommand.TYPE,
            `--${OPTION_SOURCE} <SOURCE>`,
            `--${OPTION_DATA} <DATA>`,
            `--${OPTION_TYPE} <TYPE>`,
            `[--${OPTION_NAME} <NAME>]`,
            `[--${OPTION_FLAT}]`,
            `[--${OPTION_SCSS}]`,
            `[--${OPTION_EDITABLE}]`,
            `[--${OPTION_VERBOSE}]`);
        // log the parameters
        console.log(`--${OPTION_SOURCE} <SOURCE>`, ':', HELP_COMPONENT_SOURCE);
        console.log(`--${OPTION_DATA} <DATA>`, ':', HELP_DATA_DIRECTORY);
        console.log(`--${OPTION_TYPE} <TYPE>`, ':', HELP_LAYOUT_TYPE);
        console.log(`--${OPTION_NAME} <NAME>`, ':', HELP_LAYOUT_NAME);
        console.log(`--${OPTION_FLAT}`, ':', HELP_FLAT);
        console.log(`--${OPTION_SCSS}`, ':', HELP_SCSS);
        console.log(`--${OPTION_EDITABLE}`, ':', HELP_EDITABLE);
        console.log(`--${OPTION_VERBOSE}`, ':', HELP_VERBOSE);
    }

    process(): Observable<string> {
        // defer
        return defer(() => {
            // current options
            const opts = this.options;
            // the directories
            const dataDirOpt = this.getDataDir();
            const srcDirOpt = resolve(firstOf(opts.src) || this.getAppDir());
            // fallback source directories
            const srcDirs = (arrayOf(opts.src) || [srcDirOpt]).map(optNormalizeDir);
            // construct the directories
            const rxDataDir = this.rxMkdirp(dataDirOpt);
            const rxSrcDir = this.rxMkdirp(srcDirOpt).pipe(
                shareReplay(1));
            const rxSrcDirs = concat(...srcDirs.map(dir => this.rxMkdirp(dir))).pipe(
                toArray(),
                shareReplay(1)
            );
            // logger
            const logger = this.getLogger();
            // validate the existence of the type attribute
            assertParameter(opts.type, OPTION_TYPE);
            assertNotNull(dataDirOpt, OPTION_DATA);
            assertNotNull(srcDirOpt, OPTION_SOURCE);
            // type pattern
            const includePattern = `^${escapeRegExp(opts.type)}$`;
            // name of the layout
            const mapper: LayoutNameMapper | undefined = isString(opts.name) ? _createLayoutNameMapper(opts.name) : undefined;
            // log the name
            logger.next(`Data dir [${dataDirOpt}], source dir [${srcDirOpt}], type [${opts.type}], name [${opts.name}].`);
            // log the pattern
            logger.next(`Using regular expression [${includePattern}].`);
            // create the mappings
            const createLayoutsOpts: Observable<Options> =
                rxDataDir.pipe(map(aDataDir => ({
                    command: CreateLayoutsCommand.COMMAND,
                    commandType: CreateLayoutsCommand.TYPE,
                    data: aDataDir,
                    verbose: opts.verbose,
                    flat: opts.flat,
                    module: opts.module,
                    modules: opts.modules,
                    include: includePattern,
                    count: 1,
                    layoutNameMapper: mapper,
                    allArgs: opts.allArgs
                })));
            // create the components
            const CreateVueOpts: Observable<Options> =
                combineLatest([rxDataDir, rxSrcDirs]).pipe(map(([aDataDir, aSrcDirs]) => ({
                    command: CreateVueCommand.COMMAND,
                    commandType: CreateVueCommand.TYPE,
                    data: aDataDir,
                    include: includePattern,
                    src: aSrcDirs,
                    scss: opts.scss,
                    flat: opts.flat,
                    module: opts.module,
                    modules: opts.modules,
                    skipRegistration: opts.skipRegistration,
                    layoutHtmlTemplate: opts.layoutHtmlTemplate,
                    editable: opts.editable,
                    verbose: opts.verbose,
                    allArgs: opts.allArgs
                })));
            // construct the layouts
            const rxLayouts = createLayoutsOpts.pipe(map(o => new CreateLayoutsCommand(o)),
                mergeMap(cmd => cmd.process()));
            // construct the components
            const rxComponents = CreateVueOpts.pipe(map(o => new CreateVueCommand(o)),
                mergeMap(cmd => cmd.process()));
            // chain
            return concat(rxLayouts, rxComponents);
        });
    }
}
