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

import { Component, AfterViewInit, ViewChild, OnDestroy, Input,
    ChangeDetectorRef } from '@angular/core';
import { Router }  from '@angular/router';
import { SelectorRepo } from '../../repository/selector_repo';
import { BaseComponent } from '../../utils/base_component';
import { Version, NodeJSInformation } from '../model';
import { Column } from '../../utils/column';
import { Caption } from '../../model/caption';
import { Tool } from '../../model/tool';
import { GroupedGrid } from '../../utils/grouped_grid';
import { ModalDialog } from '../../utils/ui-elements/modal-dialog/modal-dialog';
import { NotificationsService } from '../../services/notifiaction_service';
import { Message } from '../../model/message';
import { VersionRow } from './version.row';
import { NodeJSIndexComponent } from '../index.component';
import { NOT_AVAILABLE } from '../../services/constants';
import { VersionEvent } from './version.event';
import { AppService } from '../../../selector/app.service';
import { Mixin } from '../../utils/mixins/Mixin';
import { Filterable } from '../../utils/mixins/Filterable';
import { AppState } from '../../app.service';
import { LoaderSelector } from '../../utils/loader';

@Component({
    selector: 'node-js-version',
    template: require('./version.component.html'),
    styles: [require('./version.component.less')],
    providers: [SelectorRepo]
})
@Mixin([Filterable])
export class NodeJSVersionComponent extends BaseComponent implements AfterViewInit, OnDestroy {

    public rows: VersionRow[] = [];
    public columns: Column[] = [];
    public caption: Caption = new Caption('List of installed Node.js versions');
    public tools: Tool[] = [
        new Tool(
            'refresh', new Message('Refresh'), 'refresh',
            () => this.reloadMainTable()
        ),
    ];
    public selectedVersion: string;
    public versionForRemoving: string;
    public watcher = new Watcher();
    public blockInstallation: boolean = false;
    public data: NodeJSInformation = null;

    /**
     * Limit number of attempts for timer when installation/uninstallation
     * process is running or uploading additional versions for preventing
     * looped process.
     * @type {number}
     */
    public limitNumberOfAttempts: number = 30;
    public currentNumberOfAttempts: number = 0;

    @ViewChild(GroupedGrid) grid: GroupedGrid;
    @ViewChild('removingNodeJsVersionWindow') removingNodeJsVersionWindow: ModalDialog;
    @ViewChild('confirmationWindow') confirmationWindow: ModalDialog;

    @Input() highlightPhrase: string = '';

    public headerForRemovingNodeJsVersionWindow: Message;
    public textForRemovingNodeJsVersionWindow: Message = null;
    public toolsForRemovingNodeJsVersionWindow: Tool[] = [
        new Tool(
            'move',
            new Message('Move and remove Node.js'),
            'move',
            async (row) => {
                await this.moveAndRemoveNodeJsVersion(row.majorVersion, this.newVersion, row);
            }, null, true
        )
    ];

    public headerForConfirmationWindow: Message;

    private _newVersion: string = '';

    constructor(
        private selectorRepo: SelectorRepo,
        private notifier: NotificationsService,
        private parent: NodeJSIndexComponent,
        private router: Router,
        private appService: AppService,
        private changeDetector: ChangeDetectorRef,
        public appState: AppState
    ) {
        super();
        this.appState.onChange.subscribe(() => {
            let highlightPhrase = this.appState.get('options.highlightPhrase');
            if (typeof highlightPhrase === 'object') {
                this.appState.set('options.highlightPhrase', '');
            }
            this.highlightPhrase = highlightPhrase;
        });
    }

    ngAfterViewInit() {
        this.loaderSelector = new LoaderSelector('node-js grid', true);
        this.grid.loadMoreCount = false;
        this.grid.enableGroupOperation = false;
        this.grid.noFoundText = 'INFO.noFoundNodeJSVersions';
        this.changeDetector.detectChanges();
        this.loadMainTable(false, true);
    }

    public get defaultVersion(): string {
        return this.data.default_version;
    }

    public set newVersion(val: string) {
        this._newVersion = val;
        this.removingNodeJsVersionWindow.tools.forEach((tool) => {
            if (tool.name === 'move') {
                tool.disabled = (val === '');
            }
        });
    }

    public get newVersion(): string {
        return this._newVersion;
    }

    public async loadMainTable(refresh: boolean = false, silent: boolean = false) {
        try {
            if (!silent) this.loading = true;
            if (refresh) {
                await this.appService.update();
            }
            this.data = await this.appService.data();
        } finally {
            if (!silent) this.loading = false;
        }
        this.rows = this.prepareRowsForMainTable();
        this.columns = this.getColumnsForMainTable();
        this.parent.selectorEnabled = this.data.selector_enabled;
        await this.checkProcessingRows();
    }

    public async reloadMainTable() {
        await this.loadMainTable(true, false);
    }

    public async silentLoadMainTable() {
        await this.loadMainTable(true, true);
    }

    public async updateMainTable() {
        await this.loadMainTable(false, false);
    }

    public getColumnsForMainTable(): Column[] {
        this.grid.key = 'version'; // Set sorting column
        return [
            new Column({name: 'version', descr: 'Version', convertToFormat: 'string'}),
            new Column({name: 'path', descr: 'Path', convertToFormat: 'string'}),
            new Column({
                name: 'appCount', descr: 'Applications', convertToFormat: 'string'
            }).setAction(async (event, row: VersionRow) => {
                this.navigateToVersionPage(row);
            }).setButton(new Tool(
                'navigateToVersionPage',
                new Message('View all apps'),
                'nav',
                row => this.navigateToVersionPage(row),
                null,
                false,
                false,
                'default'
            )),
            new Column({
                name: 'enabled', descr: 'Enabled', convertToFormat: 'string'
            }).makeSwitcher(),
            new Column({
                name: 'actions', descr: 'Actions', convertToFormat: 'string'
            }).sortableDisable()
        ];
    }

    public prepareRowsForMainTable(): VersionRow[] {
        let rows: VersionRow[];
        rows = Object.keys(this.data.versions).map((versionNumber) => {
            let row = new VersionRow();
            this.loadRowData(row, versionNumber);
            if (!row.loadingStage && row.appCount > 0) {
                row.buttonEnabled = true;
            }
            return row;
        });
        if (this.data.isCacheUpdating() && this.isAvailableAttempts()) {
            let lastRow = new VersionRow();
            lastRow.loadingStage = true;
            lastRow.isEmpty = true;
            rows.push(lastRow);
        }
        return rows;
    }

    public navigateToVersionPage(row: VersionRow){
        this.router.navigate(
            ['/options', {outlets: {nodejs: ['applications', row.version]}}]
        );
    }

    public loadRowData(row: VersionRow, versionNumber: string) {
        let version = this.data.versions[versionNumber];
        row.version = versionNumber;
        row.majorVersion = version.majorNumber;
        row.path = version.base_dir;
        row.appCount = version.appCount;
        row.status = version.status;
        row.enabled = (version.notInstalled || version.installing)
            ? NOT_AVAILABLE : version.enabled;
        row.hasInstallButton = version.notInstalled || version.installing;
        row.hasMakeDefaultButton = (
            version.majorNumber !== this.defaultVersion
            && version.enabled
            && (version.installed || version.removing)
        );
        row.hasDeleteButton = version.installed || version.removing;
        row.hasDefaultLabel = (version.majorNumber === this.defaultVersion);
        row.loadingStage = version.installing || version.removing;
    }

    public isProcessingRowInTable(): boolean {
        for (let version in this.data.versions) {
            if (
                this.data.versions[version].installing
                || this.data.versions[version].removing
            ) return true;
        }
        return this.data.isCacheUpdating();
    }

    public isAvailableAttempts() {
        return this.currentNumberOfAttempts < this.limitNumberOfAttempts;
    }

    public async checkProcessingRows() {
        this.isProcessingRowInTable() && this.isAvailableAttempts()
            ? this.startWatchingForStatusIfNeeded()
            : this.stopWatchingStatusIfExists();
    }

    public async startWatchingForStatusIfNeeded() {
        if (!this.watcher.timer) {
            this.watcher.timer = setInterval(async() => {
                if (!this.appService.inProgress) {
                    await this.silentLoadMainTable();
                    this.currentNumberOfAttempts++;
                }
            }, 10000);
        }
    }

    public stopWatchingStatusIfExists() {
        if (this.watcher.timer) {
            this.watcher.resetTimer();
            this.blockInstallation = false;
            this.currentNumberOfAttempts = 0;
        }
    }

    public actionClicked(event) {
        if (event.hasOwnProperty('action')
            && typeof this[event.action] === 'function'
        ) {
            this[event.action](event);
        }
    }

    /**
     * Open confirm window for setting of default nodejs version.
     *
     * @param event
     * @returns {Promise<void>}
     */
    public async makeDefaultAction(event) {
        if (event.row) {
            try {
                this.headerForConfirmationWindow = new Message(
                    'confirmationMessage.makeDefaultNodeJS',
                    {version: event.row['version']}
                );
                await this.confirmationWindow.open();
                await this.makeDefault(event.row);
            } catch (e) {
                // Nothing to do
            }
        } else {
            console.log('===>', 'Row is not defined');
        }
    }

    public async makeDefault(row: VersionRow) {
        try {
            row.loadingStage = true;
            await this.appService.setDefaultNodeJsVersion(row.majorVersion);
            await this.updateMainTable();
        } catch (e) {
            // Nothing to do
        } finally {
            row.loadingStage = false;
        }
    }

    /**
     * Open window for removing nodejs version.
     *
     * @param event
     * @returns {Promise<void>}
     */
    public async removeAction(event: VersionEvent) {
        if (event.row && event.row.hasOwnProperty('version')) {
            let row = event.row;
            try {
                this.versionForRemoving = row.version;
                this.checkAvailabilityOfRemoving(row);
                if (row.appCount > 0) {
                    this.prepareElementForRemovingWindow(row);
                    await this.removingNodeJsVersionWindow.open(event.row);
                } else {
                    this.headerForConfirmationWindow = new Message(
                        'confirmationMessage.uninstallNodeJS',
                        {version: this.versionForRemoving}
                    );
                    await this.confirmationWindow.open();
                    await this.removeNodeJsVersion(row.version, row);
                }
            } catch (e) {
                // Nothing to do
            } finally {
                this.newVersion = '';
            }
        } else {
            console.log('===>', 'Wrong row format');
        }
    }

    public prepareElementForRemovingWindow(row: VersionRow) {
        this.headerForRemovingNodeJsVersionWindow = new Message(
            'Removing Node.js version %(version)s',
            {version: this.versionForRemoving}
        );
        this.textForRemovingNodeJsVersionWindow = new Message(
            'confirmationMessage.moveApplicationsIntoAnotherNodeJsVersion',
            {version: this.versionForRemoving}
        );
    }

    public checkAvailabilityOfRemoving(row: VersionRow) {
        if (row.majorVersion === this.defaultVersion) {
            this.notifier.warning(
                'Warning',
                'WARNING.restriction_of_default_version'
            );
            throw new Error;
        }
        if (row.appCount > 0) {
            this.notifier.warning(
                'Warning',
                'WARNING.restriction_of_moving_apps'
            );
            throw new Error;
        }
        if (this.blockInstallation) {
            this.notifier.warning(
                'Warning',
                'WARNING.restriction_of_parallel_process'
            );
            throw new Error;
        }
    }

    public async removeNodeJsVersion(version: string, row: VersionRow) {
        try {
            row.loadingStage = true;
            // Blocking of installation will be switched off
            // in case failing of removing process or
            // when timer will be stoped in "stopWatchingStatusIfExists" method.
            this.blockInstallation = true;
            await this.startWatchingForStatusIfNeeded();
            await this.appService.removeNodeJsVersion(version);
        } catch (e) {
            this.blockInstallation = false;
        }
    }

    public async moveAndRemoveNodeJsVersion(
        version: string, newVersion: string, row: VersionRow
    ) {
        try {
            row.loadingStage = true;
            await this.selectorRepo.moveNodeJsVersion(version, newVersion);
            await this.removeNodeJsVersion(version, row);
            delete this.data.versions[version];
            await this.updateMainTable();

        } catch (e) {
            // Nothing to do. It was pressed "cancel" button
        } finally {
            row.loadingStage = false;
        }
    }

    public get availableVersionForRemoving() {
        return [''].concat(this.data.getInstalledVersion().filter((version) => {
            return (version !== this.versionForRemoving);
        }));
    }

    /**
     * Open confirm window for installing of new nodejs version.
     *
     * @param event
     * @returns {Promise<void>}
     */
    public async installAction(event) {
        if (event.row && event.row.hasOwnProperty('version')) {
            try {
                if (this.blockInstallation) {
                    this.notifier.warning(
                        'Warning',
                        'WARNING.restriction_of_parallel_process'
                    );
                    return;
                }
                this.headerForConfirmationWindow = new Message(
                    'confirmationMessage.installNodeJS',
                    {version: event.row['version']}
                );
                await this.confirmationWindow.open();
                await this.installNodeJsVersion(event.row['version'], event.row);
            } catch (e) {
                // Nothing to do
            }
        } else {
            console.log('===>', 'Wrong row format');
        }
    }

    public async installNodeJsVersion(version: string, row: VersionRow) {
        try {
            row.loadingStage = true;
            // Blocking of installation will be switched off
            // in case failing of removing process or
            // when timer will be stoped in "stopWatchingStatusIfExists" method.
            this.blockInstallation = true;
            await this.startWatchingForStatusIfNeeded();
            await this.appService.installNodeJsVersion(version);
        } catch (e) {
            this.blockInstallation = false;
        }
    }

    /**
     * Open confirm window for enabling|disabling of nodejs version.
     *
     * @param event
     * @returns {Promise<void>}
     */
    public async switchAction(event: VersionEvent) {
        if (event.row && event.row.hasOwnProperty('version')) {
            let row: VersionRow = event.row;
            try {
                this.checkAvailabilityOfSwitchingVersion(row);
                this.headerForConfirmationWindow = new Message(
                    'confirmationMessage.switchStatusOfNodeJS',
                    {
                        status: row.enabled ? 'enable' : 'disable',
                        version: row.version
                    }
                );
                await this.confirmationWindow.open();
                await this.switchStatusOfNodeJsVersion(row);
            } catch (e) {
                row.enabled = !row.enabled;
            }
        } else {
            console.log('===>', 'Wrong row format');
        }
    }

    public checkAvailabilityOfSwitchingVersion(row: VersionRow) {
        if (!row.enabled && row.majorVersion === this.defaultVersion) {
            this.notifier.warning(
                'Warning',
                'WARNING.restriction_of_switch_version'
            );
            throw new Error;
        }
    }

    public async switchStatusOfNodeJsVersion(row: VersionRow) {
        try {
            row.loadingStage = true;
            await this.appService.switchStatusOfNodeJsVersion(
                row.version, !!row.enabled
            );
            await this.updateMainTable();
        } catch (e) {
            throw new Error;
        } finally {
            row.loadingStage = false;
        }
    }

    ngOnDestroy() {
        this.stopWatchingStatusIfExists();
    }

}

class Watcher {
    public timer = null;

    public resetTimer() {
        clearInterval(this.timer);
        this.timer = null;
    }
}
