/* Copyright CloudLinux Zug GmbH 2010-2017 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 */

import {
    Component, AfterViewInit, OnDestroy, ViewChild, QueryList,
    ViewChildren, ApplicationRef, Inject, ChangeDetectorRef
} from '@angular/core';
import { BaseComponent } from '../../app/utils/base_component';
import { SelectorRepo } from '../../app/repository/selector_repo';
import { AppState } from '../../app/app.service';
import { UiSelectOption } from '../../app/utils/ui-elements/ui-select/ui-select';
import { Column } from '../../app/utils/column';
import { EnvRow } from '../../app/model/env.row';
import { AppService } from '../app.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Application, NodeJSInformation } from '../../app/nodejs/model';
import { NotificationsService } from '../../app/services/notifiaction_service';
import { Message } from '../../app/model/message';
import { ModalDialog } from '../../app/utils/ui-elements/modal-dialog/modal-dialog';
import { ValidatorInterface } from '../../app/utils/validator';
import { FeText } from '../../app/utils/ui-elements/form-elements/fe-text/fe-text';
import { Tool } from '../../app/model/tool';
import { classToClass } from 'class-transformer';
import { Helper } from '../../app/utils/helper';
import { TranslateClass } from '../../app/utils/pipes/translate';
import { WindowRef } from '../../app/services/window.service';
import { NotificationMessageData, VersionOption } from '../../app/utils/types';
import { Subject } from 'rxjs/Subject';
import { LoaderSelector } from '../../app/utils/loader';

enum ERunConfigTools {
    cancel = 0,
    run = 1
}

enum EButtonNames {
    saveButton = 'saveFileButton',
    cancelButton = 'cancelFileButton'
}

@Component({
    template: require('./application.component.html'),
    styles: [
        require('./application.component.less'),
    ],
    providers: [SelectorRepo]
})

export
class ApplicationComponent extends BaseComponent implements AfterViewInit, OnDestroy {
    get dataForFileEdit(): string {
        return this._dataForFileEdit;
    }
    set dataForFileEdit(value: string) {
        this._dataForFileEdit = value;
        this.observableEditFile.next(value);
    }
    get editFileSaveButton(): Tool {
        return this.editFileTools.find(tool => tool.name === EButtonNames.saveButton);
    }
    headerForFileEditDialog: Message;
    runScriptWarnings: Array<NotificationMessageData> = [
        {
            title: 'WARNING.daemonScriptsRunWarning.title',
            description: 'WARNING.daemonScriptsRunWarning.description'
        },
        {
            title: 'WARNING.portListenersWarning.title',
            description: 'WARNING.portListenersWarning.description'
        }
    ];
    loaderSelector = new LoaderSelector('.application-component', true);

    get npmInstallRunning(): boolean {
        return this._npmInstallRunning;
    }
    set npmInstallRunning(value: boolean) {
        if (value) {
            this.detectedFilesTitle = 'Installing NPM...';
        }else {
            this.detectedFilesTitle = this.defaultDetectedConfigurationTitle;
        }
        this._npmInstallRunning = value;
    }

    @ViewChild('runScriptWindow') runScriptWindow: ModalDialog;

    public modeList: UiSelectOption[] = [
      {'id': 'development', 'caption': 'Development'},
      {'id': 'production', 'caption': 'Production'}
    ];

    public headerConfirmationWindow: Message;
    @ViewChild('confirmationWindow') confirmationWindow: ModalDialog;
    @ViewChild('fileEditDialog') fileEditDialog: ModalDialog;
    @ViewChildren(FeText) feTextList: QueryList<FeText>;

    public versionList: VersionOption[] = [];

    public data: Application;
    public originData: Application;
    public defaultVersion: string;
    public domainList: UiSelectOption[] = [];

    public envColumns = [
        new Column({name: 'name', descr: 'Name', convertToFormat: 'string'}),
        new Column({name: 'value', descr: 'Value', convertToFormat: 'string'}),
        new Column({
            name: 'actions', descr: 'Actions', convertToFormat: null
        }).sortableDisable()
    ];

    public editFileTools: Array<Tool> = [
        new Tool(
            EButtonNames.cancelButton,
            new Message('Cancel'),
            '',
            () => this.fileEditDialog.cancel(),
            null,
            false,
            false,
            'default'
        ),
        new Tool(
            EButtonNames.saveButton,
            new Message('Save'),
            '',
            () => this.fileEditDialog.agree(),
            null,
            false,
            false,
            'primary'
        )
    ];

    public defaultDetectedConfigurationTitle: string = 'Detected configuration files';
    public detectedFilesTitle: string = this.defaultDetectedConfigurationTitle;

    public envRows: EnvRow[];

    userName = '';

    hasError: boolean = true;

    private observableEditFile: Subject<any>;
    private _dataForFileEdit: string;
    private _npmInstallRunning: boolean = false;

    private createMode: boolean = false;

    private disabledVersions: string[] = [];

    private packageJsonIsPresent: boolean = false;

    private runConfig = new RunConfig(
        new RunConfigModal([
            new Tool(
                'cancelScriptButton',
                new Message('Cancel'),
                '',
                async () => await this.runScriptWindow.cancel(),
                null,
                false,
                null,
                'default',
                'normal',
                'fw-100'
            ),
            new Tool(
                'runScriptButton',
                new Message('Run JS script'),
                'start',
                async () => await this.runScriptWindow.agree(),
                null,
                true,
                null,
                'primary',
                'normal',
                'fw-100 m-l-10 p-r-25'
            )
        ]), (state) => {
            if (state.isLoading) {
                this.detectedFilesTitle = 'Running script...';
            }else {
                this.detectedFilesTitle = this.defaultDetectedConfigurationTitle;
            }
        }
    );

    constructor (public appState: AppState,
                 private appService: AppService,
                 private activatedRoute: ActivatedRoute,
                 private notifictions: NotificationsService,
                 private router: Router,
                 private winRef: WindowRef,
                 private selectorRepo: SelectorRepo,
                 @Inject(ApplicationRef) private appRef: ApplicationRef,
                 private changeDetector: ChangeDetectorRef) {
        super();
        this.userName = this.appState.get('userName');
        this.appState.set('createApp', false);
        this.observableEditFile = new Subject();
        this.observableEditFile.asObservable()
            .debounceTime(500)
            .subscribe((value) => {
                this.editFileSaveButton.disabled = false;
                try {
                    JSON.parse(value);
                } catch (e) {
                    this.editFileSaveButton.disabled = true;
                }
            });
    }

    openApplicationPage(){
        this.winRef.openExternalHttpTab(`${this.data.domain}/${this.data.app_uri}`);
    }

    async ngAfterViewInit() {
        let appName = this.activatedRoute.params['value']['id'];
        await this.load(appName || '');
        if (!appName) {
            this.createMode = true;
            this.data = new Application();
            this.appState.set('createApp', true);
            this.appState.set('currentAppEdit', null);
            if (this.versionList.length) {
                let enabledVersion = this.versionList
                    .find(version => version.status === 'enabled');
                this.data.version = this.defaultVersion
                    || enabledVersion ? enabledVersion.id : null;
            }
            this.data.domain = this.appState.get('userDomain');
            this.data.app_mode = 'development';
        } else if (this.data) {
            this.createMode = false;
            this.appState.set('currentAppEdit', `${this.data.domain}/${this.data.app_uri}`);
        }
        this.changeDetector.detectChanges();
    }

    public async load(appName) {
        this.loading = true;
        this.changeDetector.detectChanges();
        let data: NodeJSInformation;
        try {
            data = await this.appService.data(this.appState.get('userName'));
            let domains = await this.appService.getDomains();
            this.domainList = domains.map(domain => ({id: domain, caption: domain}));
        } catch (e) {
            this.loading = false;
            this.changeDetector.detectChanges();
            return;
        }
        this.data = this.findApp(appName, data);
        if (this.data) {
            this.checkEnvironment(this.data.env_vars);
        }

        // Clone of class Application
        this.originData = classToClass(this.data);
        this.defaultVersion = data.getFullDefaultVersion();
        this.disabledVersions = data.getDisabledVersion();
        this.versionList = data.getListOfVersions().map((x: any) => (<VersionOption>{
            'id': x.id,
            'caption': x.id === this.defaultVersion
                ? new TranslateClass().transform(['%(version)s (recommended)', {version: x.id}])
                : x.id,
            'status': x.status,
            'hidden': (this.data)
                ? x.id !== this.data.version && x.status === 'disabled'
                : x.status === 'disabled'
        }));
        this.envRows = [];
        if (this.data) {
            this.data.onChange.subscribe(() => {
                this.detectFormChanges();
            });
            this.packageJsonIsPresent = this.data.config_files.some(
                file => file === 'package.json'
            );
            this.loadEnvRows();
        } else {
            const appInstance = this.appRef.components[0].instance;
            appInstance.hasChanges = false;
        }
        this.loading = false;
        this.changeDetector.detectChanges();
    }

    checkEnvironment(env_vars) {
        for (let varName in env_vars) {
            if (env_vars.hasOwnProperty(varName)) {
                if (env_vars[varName]) {
                    env_vars[varName] = env_vars[varName].toString();
                } else {
                    env_vars[varName] = '';
                }
            }
        }
    }

    loadEnvRows() {
        this.envRows = [];
        for (let varName in this.data.env_vars) {
            if (this.data.env_vars.hasOwnProperty(varName)) {
                let row = new EnvRow();
                row.name = varName;
                row.value = this.data.env_vars[varName];
                this.envRows.push(row);
            }
        }
    }

    public findAppByUri(domain, appUri, data) {
        appUri = appUri.replace(/^\//, '');
        for (let version in data.versions) {
            if (data.versions.hasOwnProperty(version) &&
                data.versions[version].users[this.userName]) {
                let userApps = data.versions[version].users[this.userName];
                for (let appName in userApps.applications) {
                    if (userApps.applications[appName].app_uri.replace(/^\//, '') == appUri
                        && userApps.applications[appName].domain == domain) {
                        let app = classToClass(userApps.applications[appName]);
                        app.appName = appName;
                        app.version = version;
                        return app;
                    }
                }
            }
        }
    }


    public findApp(appName, data) {
        for (let version in data.versions) {
            if (data.versions.hasOwnProperty(version) &&
                data.versions[version].users[this.userName]) {
                let userApps = data.versions[version].users[this.userName];
                if (userApps.applications.hasOwnProperty(appName)) {
                    let app = classToClass(userApps.applications[appName]);
                    app.appName = appName;
                    app.version = version;
                    return app;
                }
            }
        }
    }

    public async create() {
        try {
            this.loading = true;
            await this.selectorRepo.createCommand(this.data);
            await this.appService.update(this.userName);
            this.notifictions.success('Node.js', 'Application created');
            this.createMode = false;
            this.originData = classToClass(this.data);
            this.appState.set('currentAppEdit', `${this.data.domain}/${this.data.app_uri}`);
            this.appState.set('createApp', null);
            let app = this.findAppByUri(this.data.domain, this.data.app_uri,
                await this.appService.data(this.userName));
            this.router.navigateByUrl(`/applications/${app.appNameEncoded}`);
        } finally {
            this.loading = false;
        }
    }

    public async save() {
        try {
            this.loading = true;
            await this.selectorRepo.saveCommand(this.data, this.originData);
            await this.appService.update(this.userName);
            await this.load(this.data.appName);
            this.appState.set('currentAppEdit', `${this.data.domain}/${this.data.app_uri}`);
            this.router.navigateByUrl(`/applications/${this.originData.appNameEncoded}`);
            this.notifictions.success('Node.js', 'Application saved');
        } finally {
            this.detectFormChanges();
            this.loading = false;
        }
    }

    async environmentActionClicked ({action, row}) {
        switch (action) {
            case 'save':
                delete this.data.env_vars[row.cells['name'].initialValue];
                if (row.cells['name'].value !== '') {
                    this.data.env_vars[row.cells['name'].value] = row.cells['value'].value;
                }
                // this.loadEnvRows();
                break;
            case 'delete':
                await this.environmentDestroyAction(row.cells['name'].value);
                break;
            default:
                break;
        }
        this.data.env_vars = {...this.data.env_vars}; // triggering model changes
    }

    ngOnDestroy(): void {
        this.appState.set('currentAppEdit', null);
        this.appState.set('createApp', null);
    }

    public detectFormError(): boolean {
        if (!this.data.version) {
            return true;
        }
        if (this.feTextList) {
            return this.feTextList.some((item) => {
                let validator: ValidatorInterface = item.innerUiText.validator;
                return validator.error;
            });
        }
    }

    public async destroyAction() {
        try {
            this.headerConfirmationWindow = new Message(
                'confirmationMessage.removeApplication',
                {appName: this.originData.appName}
            );
            await this.confirmationWindow.open();
            this.loading = true;
            await this.selectorRepo.sendApplicationCommand(
                'destroy', this.originData.appName
            );
            try {
                await this.appService.deleteApplication(
                    this.originData.version, this.userName, this.originData.appName
                );
            } catch (e) {
                console.log('Application not found', e);
            }
            this.router.navigateByUrl('/');
        } catch (e) {}
        this.loading = false;
    }

    private detectFormChanges(): void {
        const appInstance = this.appRef.components[0].instance;
        if (!!this.originData) {
            appInstance.hasChanges =
                       this.data.version        === this.originData.version
                    && this.data.app_mode       === this.originData.app_mode
                    && this.data.appName        === this.originData.appName
                    && this.data.app_uri        === this.originData.app_uri
                    && this.data.startup_file   === this.originData.startup_file
                    && this.data.domain         === this.originData.domain
                    && Helper.comparePlainObjects(this.originData.env_vars, this.data.env_vars);
        } else {
            appInstance.hasChanges = false;
        }
    }

    private async appAction(command) {
        try {
            this.loading = true;
            await this.selectorRepo.sendApplicationCommand(
                command, this.originData.appName
            );
            switch (command) {
                case 'start':
                case 'restart':
                    this.data.app_status = 'started';
                    break;
                case 'stop':
                    this.data.app_status = 'stopped';
                    break;
                default:
                    break;
            }
            await this.appService.changeApplicationStatus(
                this.data.app_status, this.data.version, this.userName, this.data.appName
            );
        } finally {
            this.loading = false;
        }
    }

    private async runScriptAction() {
        let packageJson;
        let packageJsonObject;
        try {
            this.loading = true;
            packageJson = await this.selectorRepo.readConfig(
                this.originData.appName,
                'package.json'
            );
            packageJsonObject = JSON.parse(packageJson);
        } catch (e) {
            this.notifictions.error('Package.json', 'Can\'t get list of scripts!');
            this.resolveError(e);
            return;
        }
        try {
            await this.showSelectScriptModal(packageJsonObject);
            await this.runScript();
        } catch (e) {}
    }

    private async showSelectScriptModal(packageJson) {
        let scripts = Object.keys(packageJson.scripts || {});
        this.loading = false;
        this.runConfig.modal.scripts = scripts.map((script) => {
            return {
                id: script,
                caption: script
            };
        });
        this.runConfig.params = '';
        await this.runScriptWindow.open();
    }

    private async runScript() {
        try {
            let config = this.runConfig;
            if (!config.selectedScript) {
                throw 'Script not defined!';
            }
            let params = config.params.trim();
            config.isLoading = true;
            config.modal.result = '';
            let result = await this.selectorRepo.runScript(
                this.originData.appName,
                config.selectedScript.caption,
                params
            );
            this.runConfig.modal.result = result;
            this.notifictions.success('Run script',
                `Script completed successfully.`);
        } catch (e) {
            this.notifictions.error('Run script', e);
            this.resolveError(e);
        } finally {
            this.runConfig.isLoading = false;
        }
    }

    private resolveError (e) {
        this.runConfig.isLoading = false;
        this.loading = false;
    }

    private getParam(paramsString) {
        let param;
        if (paramsString.includes('=')) {
            param = paramsString.split('=');
        } else {
            param = paramsString.split(' ');
        }
        return {
            [param[0]]: param[1] || true
        };
    }

    private async environmentDestroyAction(name) {
        try {
            this.headerConfirmationWindow = new Message('confirmationMessage.removeEnvironment');
            await this.confirmationWindow.open();
            delete this.data.env_vars[name];
            this.loadEnvRows();
        } catch (e) {}
    }

    private async newEnv() {
        let row = new EnvRow();
        row.openEditing();
        this.envRows.push(row);
    }

    private async runNpmInstall(event){
        this.npmInstallRunning = true;
        try {
            await this.selectorRepo.sendApplicationCommand('install-modules',
                this.originData.appName);

            this.notifictions.success('Node.js', 'NPM Install completed successfully.');
        } catch (error) {
            // error is processed already
        } finally {
            this.npmInstallRunning = false;
        }
    }

    private async editFile(fileName) {
        let result: any;
        try {
            this.loading = true;
            this.dataForFileEdit = await this.selectorRepo
                .readConfig(this.originData.appName, fileName);
        } catch (e) {
            return;
        } finally {
            this.loading = false;
        }

        this.headerForFileEditDialog = new Message('Edit %(fileName)s', {fileName: fileName});
        try {
            this.editFileSaveButton.disabled = false;
            await this.fileEditDialog.open();
            this.loading = true;
            await this.selectorRepo.saveConfig(this.originData.appName, fileName,
                this.dataForFileEdit);
        } catch (e) {
            // Nothing to do
        } finally {
            this.loading = false;
        }
    }
}

class RunConfigModal {
    public header = new Message('Choose script');
    public scripts: {id: string, caption: string}[] = [];
    public result: string = '';
    constructor(public tools: Tool[]) {}
}

class RunConfig {

    _isLoading: boolean = false;
    get isLoading(): boolean {
        return this._isLoading;
    }
    set isLoading(value: boolean) {
        this.onChangeCallback({isLoading: value});
        this._isLoading = value;
    }

    public params: string = '';
    private _selectedScript;
    get selectedScript(){
        return this._selectedScript;
    }
    set selectedScript(value){
        this._selectedScript = value;
        this.modal.tools[ERunConfigTools.run].disabled = !this._selectedScript;
    }
    constructor(public modal: RunConfigModal, public onChangeCallback) {}

}
