import {isNull, isUndefined} from 'util';
import {path} from '../../environments/path';
import {AgGridLocale} from '../lang/ag-gridlocale';
import {IGridValidationErrors} from '../model/virtual/grid-validation-errors';
import {GridSelectEditorComponent} from '../grid-cellrenderer/grid-select/grid-select-editor/grid-select-editor.component';
import {GridSelectRendererComponent} from '../grid-cellrenderer/grid-select/grid-select-renderer/grid-select-renderer.component';
import {GridCheckboxRendererComponent} from '../grid-cellrenderer/grid-checkbox/grid-checkbox-renderer/grid-checkbox-renderer.component';
import {GridCheckboxEditorComponent} from '../grid-cellrenderer/grid-checkbox/grid-checkbox-editor/grid-checkbox-editor.component';
import {GridDateRendererComponent} from '../grid-cellrenderer/grid-date/grid-date-renderer/grid-date-renderer.component';
import {GridDateEditorComponent} from '../grid-cellrenderer/grid-date/grid-date-editor/grid-date-editor.component';
import {GridTextRendererComponent} from '../grid-cellrenderer/grid-text/grid-text-renderer/grid-text-renderer.component';
import {GridTextEditorComponent} from '../grid-cellrenderer/grid-text/grid-text-editor/grid-text-editor.component';
import {AgGridComponentConst} from './ag-grid-component-const';
import {GridInputRendererComponent} from '../grid-cellrenderer/grid-input/grid-input-renderer/grid-input-renderer.component';
import {GridInputEditorComponent} from '../grid-cellrenderer/grid-input/grid-input-editor/grid-input-editor.component';
import {GridBlockedRendererComponent} from '../grid-cellrenderer/grid-blocked/grid-blocked-renderer/grid-blocked-renderer.component';
import {GridBlockedEditorComponent} from '../grid-cellrenderer/grid-blocked/grid-blocked-editor/grid-blocked-editor.component';
import {Utils} from '../utils/utils';

export class AgGridComponent {
    static readonly MIN_HEIGHT: number = 16;
    public params: any;
    public columnDefs: any[] = [];
    public defaultColDef: {} = {};
    public rowData: any[] = [];
    public rowSelection: string;
    public gridParamsColumnApi;
    public gridParamsApi;
    public apiAssetsPath: string;
    public resetSearchBtn: boolean;
    public localeText: {};
    public frameworkComponents: {};
    public bSizeColumnsToFit: boolean;
    public gridErrors: IGridValidationErrors[];
    public errorIndex: number;
    public resetValueByError: boolean;
    public errorMessage: string;
    public nextAddedItemId: number;

    /**
     * Constructor
     * @param {boolean} bSizeColumnsToFit
     */
    constructor(bSizeColumnsToFit: boolean = true) {
        AgGridComponentConst.setGridInputError();
        this.rowData = [];
        this.columnDefs = [];
        this.rowSelection = 'multiple';
        this.defaultColDef = {
            editable: true
        };
        this.apiAssetsPath = path.path_assets;
        this.frameworkComponents = {};
        this.localeText = AgGridLocale.getLocale();
        this.bSizeColumnsToFit = bSizeColumnsToFit;
        this.gridErrors = [];
        this.errorIndex = -1;
        this.resetValueByError = false;
        this.errorMessage = null;
        this.nextAddedItemId = null;

        this.frameworkComponents = {
            gridSelectEditor: GridSelectEditorComponent,
            gridSelectRenderer: GridSelectRendererComponent,
            gridCheckboxRenderer: GridCheckboxRendererComponent,
            gridCheckboxEditor: GridCheckboxEditorComponent,
            gridDateRenderer: GridDateRendererComponent,
            gridDateEditor: GridDateEditorComponent,
            gridInputRenderer: GridInputRendererComponent,
            gridInputEditor: GridInputEditorComponent,
            gridTextRenderer: GridTextRendererComponent,
            gridTextEditor: GridTextEditorComponent,
            gridBlockedRenderer: GridBlockedRendererComponent,
            gridBlockedEditor: GridBlockedEditorComponent
        };
    }

    /**
     * Callback on grid ready
     * @param params
     */
    public onGridReady(params): void {
        this.params = params;
        this.gridParamsColumnApi = params.columnApi;
        this.gridParamsApi = params.api;

        // Set for cadasters
        if (!this.bSizeColumnsToFit) {
            const allColumnIds = [];
            this.gridParamsColumnApi.getAllColumns().forEach(function(column) {
                allColumnIds.push(column.colId);
            });
            this.gridParamsColumnApi.autoSizeColumns(allColumnIds);
        }
    }

    /**
     * On row clicked
     * @param e
     */
    public onRowClicked(e): void {
        // Reset error message
        AgGridComponentConst.setGridInputError();
        this.errorMessage = null;
    }

    /**
     * On cell editing stopped
     * @param e
     */
    public onCellEditingStopped(e): void {
        if (!isNull(AgGridComponentConst.gridInputError)) {
            this.errorMessage = AgGridComponentConst.gridInputError;
            e.api.ensureIndexVisible(e.rowIndex, 'middle');
            e.api.ensureColumnVisible(e.colDef.field.toString());
        } else {
            this.errorMessage = null;
        }
    }

    /**
     * Sizes columns to fit
     * @param params
     */
    public onFirstDataRendered(params: any): void {
        // Set for info components
        if (this.bSizeColumnsToFit) {
            params.api.sizeColumnsToFit();
        }
    }

    /**
     * Resizes grid
     * @param params
     */
    public gridSizeChanged(params: any): void {
        if (!isUndefined(this.gridParamsApi)) {
            this.gridParamsApi.sizeColumnsToFit();
        }
    }

    /**
     * Search Form
     * @param params
     */
    public searchFormSubmit(params: any): void {
        if (params.target.value != '') {
            this.resetSearchBtn = true;
            this.gridParamsApi.setQuickFilter(params.target.value);
        } else {
            this.resetSearchBtn = false;
            this.gridParamsApi.setQuickFilter(null);
        }
    }

    /**
     * Reset search input
     * @param e
     */
    public resetSearch(e: any): void {
        e.originalTarget[0].value = '';
        this.resetSearchBtn = false;
        this.gridParamsApi.setQuickFilter(null);
    }

    /**
     * Stop editing state
     */
    public stopEditing(): void {
        this.params.api.stopEditing();
    }

    /**
     * Configures scrollToService - https://www.npmjs.com/package/@nicky-lenaers/ngx-scroll-to
     * @param {string} targetFieldName
     * @param {string} lastFieldName
     */
    public scrollTo(targetFieldName: string = null, lastFieldName: string = null): void {
        if (!isNull(targetFieldName)) {
            if (!isNull(lastFieldName)) {
                // Scroll to last field first to ensure our target field name is on left-hand side
                this.params.api.ensureColumnVisible(lastFieldName);
            }
            this.params.api.ensureColumnVisible(targetFieldName);
        }
    }

    /**
     * Cell renderer selector for individual cells with optional default parameters
     * @param params
     */
    public cellRendererSelector(params: any): {} {
        return AgGridComponentConst.cellRendererSelector(params);
    }

    /**
     * Cell editor selector for individual cells
     * @param params
     */
    public cellEditorSelector(params: any): {} {
        return AgGridComponentConst.cellEditorSelector(params);
    }

    /**
     * Validates grid
     */
    public validateGrid(): boolean {
        const oldErrors: IGridValidationErrors[] = this.gridErrors;
        this.gridErrors = [];
        this.errorIndex = -1;

        const primaryColumns = this.params.api.columnController.primaryColumns;
        const errorColumn: any[] = [];
        const errors: IGridValidationErrors[] = [];

        for (let k = 0; k < primaryColumns.length; k++) {
            if (!isUndefined(primaryColumns[k].colDef.cellEditorParams)) {
                if (!isUndefined(primaryColumns[k].colDef.cellEditorParams.mandatory) && primaryColumns[k].colDef.cellEditorParams.mandatory == true) {
                    errorColumn.push(primaryColumns[k].colDef.field);
                }
            }
        }

        for (let m = 0; m < errorColumn.length; m++) {
            this.params.api.forEachNode(function(rowNode, index) {
                if (isNull(rowNode.data[errorColumn[m]]) || isUndefined(rowNode.data[errorColumn[m]])) {
                    errors.push({
                        column: errorColumn[m],
                        row: index
                    });
                }
            });
        }

        this.gridErrors = errors;

        if (this.gridErrors.length != 0) {
            if (oldErrors.length != this.gridErrors.length) {
                this.params.api.ensureIndexVisible(this.gridErrors[0].row, 'middle');
                this.params.api.ensureColumnVisible(this.gridErrors[0].column);
            }
        }

        return this.gridErrors.length == 0;
    }

    /**
     * Jumps to error
     * @param direction
     */
    public jumpToError(direction: string): void {
        const errorLength: number = this.gridErrors.length - 1;
        // If any errors
        if (errorLength >= 0) {
            // if errorIndex is smaller or same as list of all errors
            if (errorLength == 0) {
                this.errorIndex = 0;
            } else {
                if (direction == 'next') {
                    this.errorIndex = this.errorIndex < errorLength ? this.errorIndex + 1 : 0;
                } else {
                    this.errorIndex = this.errorIndex <= 0 ? errorLength : this.errorIndex - 1;
                }
            }
            this.params.api.ensureIndexVisible(this.gridErrors[this.errorIndex].row, 'middle');
            this.params.api.ensureColumnVisible(this.gridErrors[this.errorIndex].column);
        } else {
            // reset errorIndex if no errors
            this.errorIndex = -1;
        }
    }

    /**
     * Shows input error
     * @param {number} positionIndex
     * @param {string} field
     * @param {string} oldValue
     * @param {string} type
     */
    public showInputError(positionIndex: number, field: string, oldValue: string, type: string): void {
        this.resetValueByError = true;
        this.params.api.clearFocusedCell();
        this.params.api.ensureIndexVisible(positionIndex, 'middle');
        this.params.api.ensureColumnVisible(field);
        // Lifecycle hack
        setTimeout(() => {
            // Reset to old value
            this.params.api.getRowNode(positionIndex).setDataValue(field, oldValue);
            this.params.api.startEditingCell({
                rowIndex: positionIndex,
                colKey: field,
            });
            switch (type) {
                case 'int':
                    this.errorMessage = 'Bitte geben Sie einen ganzzahligen Wert ein.';
                    break;
                case 'float':
                    this.errorMessage = 'Bitte geben Sie einen dezimalen oder ganzzahligen Wert ein.';
                    break;
            }
        }, 10);
    }

    /**
     * Add row to grid and returns position index
     * @param item
     */
    public addRow(item: {}): number {
        this.initNextAddedItemId();
        const rowModel = this.gridParamsApi.rowModel;
        // Note: default is last row
        let addIndex: number = rowModel.rowsToDisplay.length + 1;
        for (let i = 0; i < rowModel.rowsToDisplay.length; i++) {
            if (rowModel.rowsToDisplay[i].selected == true) {
                // Note: Add index is the row after selected row
                addIndex = i + 1;
                break;
            }
        }
        this.gridParamsApi.updateRowData({
            add: [item],
            addIndex
        });
        return addIndex;
    }

    /**
     * Removes selected rows
     */
    public removeRows(): void {
        this.initNextAddedItemId();
        const selectedRows: {}[] = this.gridParamsApi.getSelectedRows();
        this.gridParamsApi.updateRowData({remove: selectedRows});
    }

    /**
     * Gets next row position
     */
    public getNextRowPosition(): number {
        return this.gridParamsApi.rowModel.length + 1;
    }

    /**
     * inits next dummy id for next added entry, if necessary
     */
    private initNextAddedItemId(): void {
        if (this.nextAddedItemId === null) {
            this.nextAddedItemId = (this.rowData.length + 1) * -1;
        }
    }

    /**
     * Returns dummy id for added entry
     * @returns {number}
     */
    public getNextAddedItemId(): number {
        this.initNextAddedItemId();
        const res: number = this.nextAddedItemId;
        this.nextAddedItemId--;
        return res;
    }

    /**
     * column resize event
     * @param event
     */
    public onColumnResized(event): void {
        if (event.finished !== false) {
            event.api.setHeaderHeight(AgGridComponent.MIN_HEIGHT);
            const headerCells = Array.from(document.querySelectorAll('.content .ag-header-cell-label'));
            let minHeight: number = AgGridComponent.MIN_HEIGHT;
            headerCells.forEach(cell => {
                minHeight = Math.max(minHeight, cell.scrollHeight);
            });

            // set header height to calculated height + padding (top: 8px, bottom: 8px)
            event.api.setHeaderHeight(minHeight + 16);
        }
        if (event.finished) {
            this.gridParamsApi.resetRowHeights();
        }
    }

    /**
     * Renders colored dot
     * @param params
     */
    public dotRenderer(params): string {
        return '<span class=\'dot dot--' + params.value + '\'></span>';
    }

    /**
     * Renders email address
     * @param params
     */
    public emailRenderer(params): string {
        return !isNull(params.value) ? '<a href=\'mailto:' + params.value + '\'>' + params.value + '</a>' : '';
    }

    /**
     * Renders phone number
     * @param params
     */
    public phoneRenderer(params): string {
        return !isNull(params.value) ? '<a href=\'tel:' + params.value + '\'>' + params.value + '</a>' : '';
    }

    /**
     * Renders phone number
     * @param params
     */
    public urlRenderer(params): string {
        return !isNull(params.value) ? '<a href=\'' + Utils.checkUrl(params.value) + '\' target=\'_blank\'>' + params.value + '</a>' : '';
    }

    /**
     * option renderer
     * @param params
     */
    public optionRenderer(params): string {
        return params.data.is_option_meeting ? 'Ja' : '';
    }

    /**
     * address renderer
     * @param params
     */
    public addressRenderer(params): string {
        let address = '';
        if (params.data.typeType === 'visit') {
            const street = params.data.street !== null ? params.data.street : '';
            const streetNo = params.data.street_no !== null ? params.data.street_no : '';
            const zip = params.data.zip !== null ? params.data.zip : '';
            const city = params.data.city !== null ? params.data.city : '';
            let br = '';
            if (zip !== null || city !== null) {
                br = '<br />';
            }
            address = street + ' ' + streetNo + ' ' + br + zip + ' ' + city;
        }
        return address;
    }

    /**
     * participants renderer
     * @param params
     */
    public participantsRenderer(params): string {
        let participantNames = '';
        for (let i = 0; i < params.data.participants.length; i++) {
            participantNames += params.data.that.appService.getConfig().vc_user_by_id[params.data.participants[i].participant_user_id].firstname +
                ' ' + params.data.that.appService.getConfig().vc_user_by_id[params.data.participants[i].participant_user_id].lastname + '<br />';
        }
        return participantNames;
    }

    /**
     * Time renderer
     * @param params
     */
    public timeRenderer(params): string {
        return Utils.getDateTimeToDisplay(params.value, false, true);
    }

    /**
     * Date renderer
     * @param params
     */
    public dateRenderer(params): string {
        return Utils.getDateTimeToDisplay(params.value);
    }

    /**
     * Datetime renderer
     * @param params
     */
    public dateTimeRenderer(params): string {
        return Utils.getDateTimeToDisplay(params.value, true, true);
    }

    /**
     * Date comparator
     * @param date1
     * @param date2
     * @param callbackCheck
     */
    public dateComparator(date1, date2, callbackCheck: boolean = true) {
        if (date1 === null && date2 === null) {
            return 0;
        }
        if (date1 === null) {
            return -1;
        }
        if (date2 === null) {
            return 1;
        }
        const stamp1: number = Utils.getParsedDate(date1);
        const stamp2: number = Utils.getParsedDate(date2);
        return stamp1 < stamp2 ? 1 : stamp1 == stamp2 ? 0 : -1;
    }
}
