import { observable, action, computed } from "mobx";
import * as uuid from "uuid";

import { CellEvent, GridCellModel } from "./GridCellModel";

import { EventArguments } from "@dewesoft-web/ui/events";
import { EventEmitter, EventHandler } from "./EventEmitter";

export interface GridRowData {
    [key : string] : GridCellModel;
}

export enum RowEvent {
    IndexChanged,
    VisibleChanged,
    ReadOnlyChanged,
    SelectedChanged,
    CellValueChanged,
    RowCellChanged,
    CellChanged,
}

export class GridRowModel extends EventEmitter<RowEvent> {
    @observable
    visible : boolean;

    @observable
    selected : boolean;

    @observable
    first : boolean;

    @observable
    last : boolean;

    @observable
    readOnly : boolean;

    @observable
    private searchVisible : boolean;

    data : GridRowData;

    readonly uniqueId : string;

    private index : number;

    private columns : string[];

    constructor(row : any, cols : string[], uniqueId? : string | EventHandler<RowEvent>, eventHandler? : EventHandler<RowEvent>) {
        super();

        this.selected = false;
        this.visible = true;
        this.searchVisible = true;
        this.readOnly = false;

        this.first = false;
        this.last = false;

        this.columns = cols;

        this.data = {};

        this.onEvent = this.onEvent.bind(this);
        this.onCellChanged = this.onCellChanged.bind(this);

        const type = typeof uniqueId;
        if (type !== "function") {
            if (uniqueId) {
                this.uniqueId = uniqueId as string;

                if (eventHandler) {
                    this.addEventHandler(eventHandler);
                }
            }
            else {
                this.uniqueId = uuid.v4();
            }
        }
        else {
            this.uniqueId = uuid.v4();
            this.addEventHandler(uniqueId as EventHandler<RowEvent>);
        }

        this.data = {...row};

        for (const col of cols) {
            this.data[col] = new GridCellModel(row[col], col, this.onCellChanged);
        }
    }

    @computed
    get isVisible() {
        return this.visible && this.searchVisible;
    }

    @action
    hideNotMatching(query : string) : boolean {
        const normalizedQuery = query.toUpperCase();

        for (const col of this.columns) {
            const data = (this.data[col].value + "").toUpperCase();
            if (data.includes(normalizedQuery)) {
                this.searchVisible = true;
                return;
            }
        }

        this.searchVisible = false;
    }

    @action
    setVisible(visible : boolean) {
        this.visible = visible;
    }

    @action
    forceVisible() {
        this.visible = true;
        this.searchVisible = true;
    }

    setIndex(index : number) {
        this.index = index;

        this.triggerEvent(RowEvent.IndexChanged, {
            index: index
        });
    }

    @action
    setReadOnly(readOnly : boolean) {
        this.readOnly = readOnly;

        this.triggerEvent(RowEvent.ReadOnlyChanged, {
            readOnly: readOnly
        });
    }

    @action
    setSelected(selected : boolean) {
        if (this.selected === selected) {
            return;
        }

        this.selected = selected;

        this.triggerEvent(RowEvent.SelectedChanged, {
            selected: selected
        });
    }

    getValue(col : string) {
        return this.data[col] ? this.data[col].value : undefined;
    }

    getIndex() {
        return this.index;
    }

    setValue(col : string, value : any, noEvent? : boolean) {
        if (this.readOnly) {
            return;
        }

        if (!this.data[col]) {
            throw new Error(`Column with name '${col}' does not exist.`);
        }

        if (this.data[col].type && this.data[col].type.readOnly) {
            // console.log(`row: ${this.uniqueId} col: ${col} ReadOnly`);

            return;
        }

        // console.log(`row: ${this.uniqueId} col: ${col} SetValue: ${value}`);

        this.data[col].setValue(value, noEvent);
    }

    setEditing(editing : boolean, column? : string) {
        if (editing) {
            this.data[column].setEditing(true);
        }
        else if (column) {
            this.data[column].setEditing(false);
        }
        else {
            for (const key in this.data) {
                if (this.data.hasOwnProperty(key) && this.data[key] && this.data[key].setEditing) {
                    this.data[key].setEditing(false);
                }
            }
        }
    }

    @action
    select() {
        this.setSelected(true);
        //
        this.first = true;
        this.last = true;
    }

    @action
    deselect() {
        this.setSelected(false);

        this.first = false;
        this.last = false;

        for (const key in this.data) {
            if (this.data.hasOwnProperty(key) &&
                this.data[key] &&
                this.data[key].setEditing
            ) {
                this.data[key].setEditing(false);
            }
        }
    }

    onCellChanged(cell : GridCellModel, event : CellEvent, args : EventArguments) {
        this.triggerEvent(RowEvent.CellChanged, {
            event: event,
            column: cell.getColumn(),
            args: args
        });
    }

    /*
     *  C++ event handlers
     */

    onEvent(event : RowEvent, ...args : any[]) {
        switch (event) {
            case RowEvent.VisibleChanged:
                this.visible = args[1];
                break;
            case RowEvent.ReadOnlyChanged:
                this.readOnly = args[1];
                break;
            case RowEvent.CellValueChanged:
                this.setValue(args[1], args[2], true);
                break;
            case RowEvent.CellChanged:
                const [eventCell, column, cellEvent, cellArgs] = args;

                this.data[column].onEvent(cellEvent, ...cellArgs);
                break;
        }
    }
}
