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

import { Component, ViewChild } from '@angular/core';
import { GroupedGrid } from './utils/grouped_grid';
import { Column } from './utils/column';
import { XHR } from './utils/xhr';
import { StatsRepo } from './repository/stats_repo';
import { Limits, StatResult, CustomBoolean, Stat } from './model/stat';
import { Helper } from './utils/helper';
import { BaseComponent } from './utils/base_component';
import { AppState } from './app.service';
import { ModalStats } from './utils/modal_stats';
import { Access } from './services/access';
import { US_RESELLER, ViewModes } from './services/view_modes';
import * as common from './common';
import { User } from './model/user';
import { UsersRepo } from './repository/users_repo';
import { isObject } from 'rxjs/util/isObject';
import { UserEditActions } from '../app/utils/user_actions';
import { HIDE_MYUSQL_LIMITS_MODES } from '../app/services/constants';
import {
    ExtendedColumnProperties, commonColumnsArray
} from './utils/componentProperties/columnExtendedProperties';
import { ModalLimitsUser } from './utils/modal_limits_user';
import { DateRange } from './utils/ui-elements/calendar/ui-date-range/ui-date-range.component';
import { LoaderSelector } from './utils/loader';


@Component({
    selector: '.stats_block',
    // templateUrl: Url.getMainUrlForStaticHtml('stats.component'),
    template: require('./../templates/stats.component.html'),
    styles: [
        require('../styles/stats.component.less'),
    ],
    providers: [XHR, StatsRepo, UsersRepo, Access, ViewModes, UserEditActions]
})
export class StatsComponent extends BaseComponent{
    rows: Stat[] = [];
    originalRows: Stat[] = [];
    columns: Array<Column> = [];
    public rowsOnPage: number = 100;
    public range: DateRange = {};

    @ViewChild(ModalLimitsUser) modalLimits: ModalLimitsUser;

    selectedParams = {
        'order-by': 'any_faults',
        'period': '10m',
        'limit': '25'
    };
    stat_data: any;
    stat_chart: string = '';

    defaultMainParam: string = 'order-by';

    mutuallyExclusiveParams = {
        'order-by': {'order-by': 'any_faults'},
        'by-usage': {'by-usage': 'cpu', 'percentage': '50'},
        'by-fault': {'by-fault': 'any', 'threshold': '1'}
    };

    mySQLParam = 'mysql';
    possibleColumnsArray: Array<ExtendedColumnProperties> = (<Array<ExtendedColumnProperties>>[
        // Common columns
        {
            name: 'id', descr: 'LVE ID', convertToFormat: 'number', additionalCssClass: 'nowrap'
        },
        {
            name: 'username', descr: 'Username', convertToFormat: 'string'
        },
        {
            name: 'domain', descr: 'Domain', convertToFormat: 'string'
        }
    ]).concat(commonColumnsArray);

    availableLimits: ColumnFilter;

    /**
     * Possible columns for filter.
     * Sample for this.availableLimits.
     */
    possibleLimits = {
        'cpu':   {'title': 'SPEED', 'checked': true},
        'io':    {'title': 'IO',    'checked': true},
        'ep':    {'title': 'EP',    'checked': true},
        'vmem':  {'title': 'VMEM',  'checked': false},
        'pmem':  {'title': 'PMEM',  'checked': true},
        'nproc': {'title': 'NPROC', 'checked': true},
        'iops':  {'title': 'IOPS',  'checked': true},
        'mysql': {'title': 'MySQL', 'checked': true},
    };

    availableTimeframe = common.availableTimeframe;

  possibleOrderBy = {
    'any_faults':   {'title': 'Any Faults',           'static': true},
    'cpu':          {'title': 'Average SPEED',        'link': 'cpu'},
    'mysql_cpu':    {'title': 'Average MySQL SPEED',  'link': 'mysql'},
    'io':           {'title': 'Average IO',           'link': 'io'},
    'mysql_io':     {'title': 'Average MySQL IO',     'link': 'mysql'},
    'iops':         {'title': 'Average IOPS',         'link': 'iops'},
    'ep':           {'title': 'Average EP',           'link': 'ep'},
    'nproc':        {'title': 'Average NPROC',        'link': 'nproc'},
    'pmem':         {'title': 'Average PMEM',         'link': 'pmem'},
    'vmem':         {'title': 'Average VMEM',         'link': 'vmem'},
    'cpu_faults':   {'title': 'SPEED Faults',         'link': 'cpu'},
    'ep_faults':    {'title': 'EP Faults',            'link': 'ep'},
    'io_faults':    {'title': 'IO Faults',            'link': 'io'},
    'iops_faults':  {'title': 'IOPS Faults',          'link': 'iops'},
    'nproc_faults': {'title': 'PROCESSES Faults',     'link': 'nproc'},
    'pmem_faults':  {'title': 'PMEM Faults',          'link': 'pmem'},
    'vmem_faults':  {'title': 'VMEM Faults',          'link': 'vmem'},
  };

    availableLimitValues = {
        '25': '25',
        '50': '50',
        '75': '75',
        '100': '100 entries',
        '0': 'Unlimited',
    };

  possibleByUsage = {
    'cpu':       {'title': 'Average SPEED [v2]',       'link': 'cpu'},
    'mysql_cpu': {'title': 'Average MySQL SPEED [v2]', 'link': 'mysql'},
    'io':        {'title': 'Average IO [v2]',          'link': 'io'},
    'mysql_io':  {'title': 'Average MySQL IO [v2]',    'link': 'mysql'},
    'iops':      {'title': 'Average IOPS [v2]',        'link': 'iops'},
    'ep':        {'title': 'Average EP [v2]',          'link': 'ep'},
    'nproc':     {'title': 'Average NPROC [v2]',       'link': 'nproc'},
    'pmem':      {'title': 'Average PMEM [v2]',        'link': 'pmem'},
    'vmem':      {'title': 'Average VMEM [v2]',        'link': 'vmem'},
  };

    availablePercentages = {
        '50': '50%',
        '75': '75%',
        '90': '90%',
        '95': '95% of it\'s limits',
    };

  possibleByFault = {
    'any':   {'title': 'Any Limit',               'static': true},
    'cpu':   {'title': 'SPEED Limit',             'link': 'cpu'},
    'io':    {'title': 'IO Limit',                'link': 'io'},
    'iops':  {'title': 'IOPS Limit',              'link': 'iops'},
    'ep':    {'title': 'EP Limit',   'link': 'ep'},
    'nproc': {'title': 'PROCESSES Limit',         'link': 'nproc'},
    'pmem':  {'title': 'PMEM Limit',              'link': 'pmem'},
    'vmem':  {'title': 'VMEM Limit',              'link': 'vmem'},
  };

    availableThresholds = {
        '1': '1',
        '10': '10',
        '50': '50',
        '100': '100',
        '1000': '1000 faults',
    };

    switchViewMode: Function = () => {
        this.rows = this.viewModeService.switchViewMode(
            this.availableViewModes, this.originalRows
        );
        this.GroupedGrid.noFound = (this.rows.length === 0);
    }

    // noinspection TypeScriptValidateTypes
    @ViewChild(GroupedGrid) GroupedGrid: GroupedGrid;
    @ViewChild(ModalStats) ModalStats: ModalStats;

    constructor(
        public appState: AppState,
        private statsRepo: StatsRepo,
        private usersRepo: UsersRepo,
        private accessService: Access,
        private viewModeService: ViewModes,
        private userEditActions: UserEditActions
    ) {
        super();
        this.load();
    }

    load() {
        this.statsRepo.getStatistics(this.selectedParams).then((data: StatResult) => {
            if (data.users.length) {
                let standard = data.users[0];
                this.accessService.dropPermission(data, standard);
                this.defineAvailableLimits(standard['limits'], data.mySqlGov);
                this.columns = this.getColumns(standard);
                this.updateVisibleColumns();
                if (this.appState.get('isAdmin')) {
                    this.viewModeService.concatResellerWithUser(data);
                    this.viewModeService.detectUserTypes(data, this.viewModes);
                }
                this.originalRows = this.parseStats(data);
                this.switchViewMode();
            } else {
                this.rows = [];
                this.GroupedGrid.noFound = true;
            }
            this.loading = false;
            this.GroupedGrid.noFoundText = 'NO STATISTICS FOUND FOR SELECTED PERIOD';
        });
    }

    parseStats(data) {
        let users: Array<Stat> = [];

        for (let item of data['users']) {
            let user = new Stat();

            if (this.appState.get('mySqlGovMode') === 'all') {
                if (item.usage.cpu.mysql > item.usage.cpu.lve) {
                    item.usage.cpu.mysql = item.usage.cpu.lve;
                }
                if (item.usage.io.mysql > item.usage.io.lve) {
                    item.usage.io.mysql = item.usage.io.lve;
                }
            }
            if (HIDE_MYUSQL_LIMITS_MODES.includes(this.appState.get('mySqlGovMode'))) {
                item.limits.cpu.mysql = '-';
                item.limits.io.mysql = '-';
            }

            for (let column of this.possibleColumnsArray) {
                let value = Helper.getValueFromObjectRecursively(item, column.name);
                if (typeof column.units !== 'undefined'){
                    user[column.name] = Helper.reformatValue(value, column.units);
                } else {
                    user[column.name] = {
                        'value': (typeof value === 'number') ? value.toString() : value
                    };
                }
            }
            user.userType = item.userType;
            user.isReseller = user.userType === US_RESELLER;
            user.id = user['id']['value'];
            user.username = user['username']['value'];

            this.defineWarningForAverageValues(user);
            users.push(user);
        }
        return users;
    }

  /**
   * Define achievement average values to limit values
   * for showing them in red color.
   *
   * @param user
   */
    private defineWarningForAverageValues(user: Stat) {
        for (let pathToParam in user) {
            let parts = pathToParam.split('_');
            if (parts.length > 1 && isObject(user[pathToParam]) && parts[0] === 'usage') {
                let param = parts[1];
                let type = parts[2];
                let string = `limits_${param}_${type}`;

                let value = user[pathToParam].hasOwnProperty('rawValue') ?
                    user[pathToParam].rawValue :
                    Helper.reformatToDirtyValue(user[pathToParam].value);
                let dirty_value = user[string].hasOwnProperty('rawValue') ?
                    user[string].rawValue : Helper.reformatToDirtyValue(user[string].value);

                if (value >= dirty_value && dirty_value) {
                    user[`usage_${param}_${type}`].warning = true;
                }
            }
        }
    }

    getColumns(userStandard: Stat): Column[] {
        let columns = [];
        for (let column of this.possibleColumnsArray) {
            let icon = column.icon;
            let tooltip = column.tooltip;
            let additionalCssClass = column.additionalCssClass;

            if (Helper.hasOwnPropetyRecursively(userStandard, column.name)) {
                let addColumn = new Column(column);
                if (typeof icon !== 'undefined' && icon !== '') {
                    addColumn.setIcon(icon);
                }
                if (typeof tooltip !== 'undefined') {
                    addColumn.setTooltip(tooltip);
                }
                let limit = column.name.split('_')[1];
                if (this.availableLimits.hasOwnProperty(limit)
                    && !this.availableLimits[limit].checked
                ) {
                    addColumn.hide();
                }
                if (typeof additionalCssClass !== 'undefined') {
                    addColumn.setAdditionCssClass(additionalCssClass);
                }
                columns.push(addColumn);
            }
        }
        columns.push(new Column({name: 'actions', descr: 'Actions', convertToFormat: 'string'})
            .sortableDisable());
        return columns;
    }

    defineAvailableLimits(limitEtalon: Limits, mySqlGov: CustomBoolean) {
        let keysOfAvailableLimits = Object.keys(limitEtalon);
        if (mySqlGov === 'enabled') {
        keysOfAvailableLimits.push(this.mySQLParam);
        }
        // Retrieve available limits from local storage
        // If they have already defined in local storage.
        let availableLimitsFromLS = this.retrieveAvailableLimitsFromLocalStorage();
        let keysOfAvailableLimitsFromLS = Object.keys(availableLimitsFromLS);
        if (Helper.isArraysEqual(keysOfAvailableLimits, keysOfAvailableLimitsFromLS)) {
            this.availableLimits = availableLimitsFromLS;
        } else {
            this.availableLimits = new ColumnFilter;
            for (let limit in this.possibleLimits) {
                if (keysOfAvailableLimits.indexOf(limit) !== -1) {
                    this.availableLimits[limit] = this.possibleLimits[limit];
                }
            }
        }
        this.defineAvailableFilter('possibleOrderBy');
        this.defineAvailableFilter('possibleByUsage');
        this.defineAvailableFilter('possibleByFault');
    }

    defineAvailableFilter(possibleParam: string) {
        for (let order in this[possibleParam]) {
            let orderOptions = this[possibleParam][order];
            let enable = orderOptions.hasOwnProperty('static') ? orderOptions['static'] : false;
            if (!enable && orderOptions['link']
                && !this.availableLimits.hasOwnProperty(orderOptions['link'])
            ) {
                delete this[possibleParam][order];
            }
        }
    }

    updateVisibleColumns() {
        for (let column of this.columns) {
            let name_part = column.name.split('_');
            let group = name_part[1];
            let type = name_part[2];
            for (let key in this.availableLimits) {
                if (column.name.indexOf(`_${key}_`) !== -1) {
                    if (this.availableLimits[key].checked) {
                        if (key === 'mysql') {
                            if (this.availableLimits.hasOwnProperty(group)
                                && this.availableLimits[group].checked
                            ) {
                                column.show();
                            } else {
                                column.hide();
                            }
                        } else {
                            if (type !== 'mysql' ||
                               (type === 'mysql' && this.availableLimits.hasOwnProperty(type)
                                   && this.availableLimits[type].checked)
                            ) {
                                column.show();
                            } else {
                                column.hide();
                            }
                        }
                    } else {
                        column.hide();
                    }
                }
            }
        }
    }

    toggleColumns(key: string, checkedBefore: boolean) {
        this.availableLimits[key].checked = !checkedBefore;
        this.setAvailableLimitsIntoLocalStorage();
        this.updateVisibleColumns();
        this.GroupedGrid.reset();
    }

    getParamGroup(param: string): string {
        for (let group in this.mutuallyExclusiveParams) {
            for (let _param in this.mutuallyExclusiveParams[group]) {
                if (param === _param) {
                    return group;
                }
            }
        }
        return null;
    }

    addParam(param: string, val: string = '', reload: boolean = true) {
        let group = this.getParamGroup(param);
        if (group && !this.selectedParams.hasOwnProperty(group)) {
            for (let _param in this.mutuallyExclusiveParams) {
                if (group !== _param) {
                    this.removeParam(_param, false, false);
                }
            }
            for (let subParam in this.mutuallyExclusiveParams[group]) {
                this.selectedParams[subParam] = this.mutuallyExclusiveParams[group][subParam];
            }
        }
        if (val) {
            this.selectedParams[param] = val;
        }
        if (reload) {
            this.loading = true;
            this.load();
        }
    }

    removeParam(param: string, reload: boolean = true, directRemoving = true) {
        let group = this.getParamGroup(param);

        if (group && this.selectedParams.hasOwnProperty(group)) {
            for (let subParam in this.mutuallyExclusiveParams[group]) {
                delete this.selectedParams[subParam];
            }
        }
        if (directRemoving) {
            let isNoneMainParam = true;
            for (let _param in this.mutuallyExclusiveParams) {
                if (this.selectedParams.hasOwnProperty(_param)) {
                    isNoneMainParam = false;
                    break;
                }
            }

            if (isNoneMainParam) {
                this.selectedParams[this.defaultMainParam] = 'any_faults';
            }
        }
        if (reload) {
            this.loading = true;
            this.load();
        }
    }

    // TODO remove if not used or replace with Object.keys
    keys(object): Array<string> {
        let keys = [];
        for (let key in object) {
            keys.push({key: key, value: object[key]});
        }
        return keys;
    }

    setAvailableLimitsIntoLocalStorage() {
        localStorage.setItem('availableLimits', JSON.stringify(this.availableLimits));
    }

    retrieveAvailableLimitsFromLocalStorage(): ColumnFilter {
        return localStorage.getItem('availableLimits')
            ? JSON.parse(localStorage.getItem('availableLimits'))
            : new ColumnFilter;
    }

    public showStat(user_id) {
        this.loading = true;
        let timeUnit;
        switch (this.selectedParams.period) {
            case '1d':
            case 'today':
            case 'yesterday':
                timeUnit = '1h';
                break;
            case '4h':
            case '1h':
            case '30m':
            case '10m':
                timeUnit = '1m';
                break;
            default:
                timeUnit = '1d';
        }
        this.ModalStats.dateRange = this.range;
        this.ModalStats.loadHistory(user_id, true, this.selectedParams.period || null, timeUnit)
            .then(() => {
                this.loading = false;
            });
    }

    ngAfterViewInit() {
        this.loaderSelector = new LoaderSelector('.stats_block');
        this.loading = true;
    }


    actionClicked(event) {
        switch(event.action) {
            case 'edit':
                this.edit(event.row);
                break;
            case 'stat':
                this.showStat(event.row.id);
                break;
            default:
                break;
        }
    }

    edit(statsRow: Stat) {
        this.loading = true;
        let userPromise;
        if ( statsRow.isReseller ) {
            userPromise = this.usersRepo.loadReseller(statsRow.username);
        } else {
            userPromise = this.usersRepo.loadUser(statsRow.id);
        }
        userPromise.then(({user, response}) => {
            this.userEditActions.globalCageFsStatus = (response.cageFS === 'enabled');
            this.userEditActions.globalMySqlGov = (response.mySqlGov === 'enabled');
            statsRow.user = user;
            this.modalLimits.loadEditing(user, this.userEditActions);
            this.loading = false;
            this.modalLimits.show();
        }).catch(() => {
            this.loading = false;
        });
    }

    save(user: User) {
        user.save()
            .then(() => { user.loadingStage = false; })
            .catch(() => { user.loadingStage = false; });
    }

    setPeriod(period) {
        if (this.selectedParams['from']) {
            delete this.selectedParams['from'];
            delete this.selectedParams['to'];
        }
        this.addParam('period', period);
    }

    onUseChange(range) {
        this.setDateRange(range);
    }

    setDateRange(range: DateRange) {
        this.range = range;
        if (this.selectedParams['period']) {
            delete this.selectedParams['period'];
        }
        this.addParam('from', range.start.format('YYYY-MM-DD 00:00'), false);
        this.addParam('to', range.end.format('YYYY-MM-DD 23:59'));
    }
}

class ColumnFilter {
    pmem:  ColumnFilterOptions;
    vmem:  ColumnFilterOptions;
    iops:  ColumnFilterOptions;
    nproc: ColumnFilterOptions;
    ep:    ColumnFilterOptions;
    cpu:   ColumnFilterOptions;
    io:    ColumnFilterOptions;
    mysql: ColumnFilterOptions;
}

class ColumnFilterOptions {
    title: string;
    checked: boolean;
}
