import { Panel } from './panel';
import { randomUUID } from './utility';

/**
 * That represent a single configuration information ex.
 * Domain -> globular3.globular.app
 */
export class ConfigurationLine {
    protected id: string;

    // The panel inside where the line is display
    protected panel: ConfigurationPanel;

    // Must the name in the IConfig.
    private name: string;

    // The div that contain the line.
    private _content: any;

    public get content(): any {
        return this._content;
    }

    public set content(value: any) {
        this._content = value;
    }

    // The div that display non-editable values.
    protected valueDiv: any;

    // The div that play editable values.
    protected valueEditor: any;

    private _label: any;
    public get label(): any {
        return this._label;
    }
    public set label(value: any) {
        this._label = value;
    }

    getValueEditor(): any {
        return this.valueEditor;
    }

    constructor(panel: ConfigurationPanel, name: string, label: string, content: any) {
        this.id = randomUUID()
        this.name = name;
        this.content = content;
        this.panel = panel;

        // Name will be use if no label was given.
        if (label == undefined) {
            label = this.name
        }

        // Now I will create the part label of the interface.
        this.content = content.appendElement({ "tag": "div", "class": "row" }).down()
        this.label = this.content.appendElement({ "tag": "div", "class": "col s12 m4", "style": "height: 100%", "innerHtml": label }).down()
    }

    // Return the configuration values.
    public getValue(): any {
        return this.panel.config[this.name]
    }

    // Set the configuration values.
    public setValue(v: any) {
        this.panel.config[this.name] = v
    }

    /**
     * Set the value of the configuration with the value contain in the editor.
     */
    public set() {
        this.setValue(this.valueEditor.getValue())
        this.valueDiv.setValue(this.valueEditor.getValue())
    }

    /**
     * Reset the value of the configuration with it initial value.
     */
    public reset() {
        this.valueEditor.setValue(this.getValue())
        this.valueDiv.setValue(this.getValue())
    }

    /**
     * Non editable mode
     */
    lock() {
        this.valueEditor.element.style.display = "none"
        this.valueDiv.element.style.display = ""
    }

    /**
     * Editable mode.
     */
    unlock() {
        if(this.valueEditor!=undefined){
            this.valueEditor.element.style.display = ""
            this.valueDiv.element.style.display = "none"
        }
    }

    /**
     * Hide the line
     */
    hide() {
        this.content.element.style.display = "none";
    }

    /**
     * Show the line.
     */
    show() {
        this.content.element.style.display = "";
    }
}

export class ConfigurationEnum extends ConfigurationLine {

    constructor(panel: ConfigurationPanel, name: string, options: Array<string>, label: string, content: any, numericIndex: boolean) {
        super(panel, name, label, content);
        let value = this.getValue()
        if (value == null) {
            return
        }

        // Set the value div.
        this.valueDiv = this.content.appendElement({ "tag": "div", "id": this.id + "_" + name + "_div", class: "col s12 m8", "innerHtml": value.toString() }).down()

        // Set the value editor.
        this.valueEditor = this.content.appendElement({ "tag": "select", "id": this.id + "_" + name + "_select", "style": "display: none;", class: "browser-default col s12 m8" }).down()

        this.valueEditor.isNumeric = numericIndex;
        let i: number;
        for (i = 0; i < options.length; i++) {
            if (!numericIndex) {
                this.valueEditor.appendElement({ tag: "option", value: options[i], innerHtml: options[i] }).down()
            } else {
                this.valueEditor.appendElement({ tag: "option", value: i, innerHtml: options[i] }).down()
            }
        }

        M.FormSelect.init(this.valueEditor.element)
        if (numericIndex == true) {
            this.valueEditor.element.selectedIndex = value
        } else {
            this.valueEditor.element.selectedIndex = options.indexOf(value)
        }

        this.valueEditor.element.onchange = () => {
            // set the value in the interface.
            let value = this.valueEditor.getValue()
            this.valueDiv.setValue(value)
            this.panel.hasChange()
        }

        // Return the value of the input.
        this.valueEditor.getValue = () => {
            let value = this.valueEditor.element.value
            if (this.valueEditor.isNumeric == true) {
                value = parseInt(value)
            }
            return value
        }

        // Return the value of the input.
        this.valueEditor.setValue = function (v: any) {
            this.element.value = v
        }

        // Return the value of the input.
        this.valueDiv.setValue = function (v: any) {
            this.element.innerHTML = v
        }
    }
}

export class ConfigurationTextLine extends ConfigurationLine {

    constructor(panel: ConfigurationPanel, name: string, label: string, content: any, type?: string, step?: number, min?: number, max?: number) {
        super(panel, name, label, content);
        let value = this.getValue()
        if (value == null) {
            return
        }

        // Type can be any type that input box can support.
        if (type == undefined) {
            type = "text"
        }

        if (value == undefined) {
            return
        }

        // Set the value div.
        this.valueDiv = this.content.appendElement({ "tag": "div", "id": this.id + "_div", "class": "col s12 m8", "innerHtml": value.toString() }).down()

        // Set the value editor.
        this.valueEditor = this.content.appendElement({ "tag": "input", "id": this.id + "_input", "style": "display: none;", "class": "col s12 m8", "type": type, "value": value }).down()

        this.valueEditor.element.onchange = () => {
            // set the value in the interface.
            this.valueDiv.setValue(this.valueEditor.getValue())
            this.panel.hasChange()
        }

        // Return the value of the input.
        this.valueEditor.getValue = function () {
            if (type == "number") {
                return parseFloat(this.element.value)
            }
            return this.element.value
        }

        // Return the value of the input.
        this.valueEditor.setValue = function (v: any) {
            this.element.value = v
        }

        // Return the value of the input.
        this.valueDiv.setValue = function (v: any) {
            this.element.innerHTML = v
        }
    }
}

export class ConfigurationLongTextLine extends ConfigurationLine {

    constructor(panel: ConfigurationPanel, name: string, label: string, content: any) {
        super(panel, name, label, content);
        let value = this.getValue()
        if (value == null) {
            return
        }

        if (value == undefined) {
            return
        }

        // Set the value div.
        this.valueDiv = this.content.appendElement({ tag: "div", "id": this.id + "_div", class: "col s12 m8", innerHtml: value.toString() }).down()

        // Set the value editor.
        this.valueEditor = this.content.appendElement({ tag: "textarea", "id": this.id + "_input", style: "display: none; padding: 5px; height: 100px;", rows:"9", type:"text", class: "col s12 m8", innerHtml: value }).down()

        this.valueEditor.element.onchange = () => {
            // set the value in the interface.
            this.valueDiv.setValue(this.valueEditor.getValue())
            this.panel.hasChange()
        }

        // Return the value of the input.
        this.valueEditor.getValue = function () {
            return this.element.value
        }

        // Return the value of the input.
        this.valueEditor.setValue = function (v: any) {
            this.element.value = v
        }

        // Return the value of the input.
        this.valueDiv.setValue = function (v: any) {
            this.element.innerHTML = v
        }
    }
}

export class ConfigurationToggleLine extends ConfigurationLine {

    constructor(panel: ConfigurationPanel, name: string, label: string, content: any, labels: Array<string>) {
        super(panel, name, label, content);
        let value = this.getValue()
        if (value == null) {
            return
        }

        // Set the value div.
        this.valueDiv = this.content.appendElement({ "tag": "div", "id": this.id + "_" + name + "_div", "class": "col s12 m8", "innerHtml": value.toString() }).down()

        // Set the value editor.
        this.valueEditor = this.content
            .appendElement({ "tag": "div", "class": "switch col s12 m8", "style": "display: none;" }).down()

        let uuid = randomUUID()

        this.valueEditor.appendElement({ "tag": "label" }).down()
            .appendElement({ "tag": "span", "innerHtml": labels[1] })
            .appendElement({ "tag": "input", "id": uuid, "type": "checkbox" })
            .appendElement({ "tag": "span", "class": "lever" })
            .appendElement({ "tag": "span", "innerHtml": labels[0] })

        if (value == true) {
            this.valueEditor.getChildById(uuid).element.click()
        }

        this.valueEditor.element.onchange = () => {
            // set the value in the interface.
            this.valueDiv.setValue(this.valueEditor.getValue())
            this.panel.hasChange()
        }

        // Return the value of the input.
        this.valueEditor.getValue = function () {
            return this.getChildById(uuid).element.checked
        }

        // Return the value of the input.
        this.valueEditor.setValue = function (v: any) {
            this.getChildById(uuid).element.checked = v
        }

        // Return the value of the input.
        this.valueDiv.setValue = function (v: any) {
            this.element.innerHTML = v
        }
    }
}

/**
 * That class implement multiple options single choice configuration line.
 */
export class ConfigurationMultipleOptionsSingleChoiceLine extends ConfigurationLine {

    constructor(panel: ConfigurationPanel, name: string, options: Array<string>, label: string, content: any) {
        super(panel, name, label, content);
        this.valueDiv = this.content.appendElement({ "tag": "div", "id": this.id + "_" + name + "_div", "class": "col s12 m8", "innerHtml": this.getValue() }).down()
        this.valueEditor = this.content.appendElement({ "tag": "div", "class": "col s12 m8", "style": "display: none; justify-content: flex-start;" }).down()

        // Set the choice from options...
        for (var i = 0; i < options.length; i++) {
            this.valueEditor
                .appendElement({ "tag": "label", "id": this.id + "_" + options[i] + "_label", "style": "padding-right: 15px;" }).down()
                .appendElement({ "tag": "input", "id": this.id + "_" + options[i] + "_input", "name":  this.id + "_" + name + "_group", "type": "radio" })
                .appendElement({ "tag": "span", "innerHtml": options[i] }).up()

            let input = this.content.getChildById(this.id + "_" + options[i] + "_input")
            input.element.onchange = () => {
                // set the value in the interface.
                this.valueDiv.setValue(this.valueEditor.getValue())
                this.panel.hasChange()
            }
        }

        // Return the value of the input.
        this.valueEditor.getValue = () => {
            // That function will return the actual checked value.
            for (var i = 0; i < options.length; i++) {
                let input = this.valueEditor.getChildById(this.id + "_" + options[i] + "_input")
                if (input.element.checked == true) {
                    return options[i]
                }
            }
        }

        // Return the value of the input.
        this.valueEditor.setValue = (v: any) => {
            for (var i = 0; i < options.length; i++) {
                let input = this.content.getChildById(this.id + "_" + options[i] + "_input")
                input.element.checked = false
            }
            let input = this.valueEditor.getChildById(this.id + "_" +v + "_input")
            input.element.checked = true;
        }

        // Return the value of the input.
        this.valueDiv.setValue = function (v: any) {
            this.element.innerHTML = v
        }

        // Set the value of the editor...
        this.valueEditor.setValue(this.getValue())
    }
}

/**
 * Use to display a liste of string values in a configuration.
 */
export class ConfigurationStringListLine extends ConfigurationLine {
    constructor(panel: ConfigurationPanel, name: string, label: string, content: any) {
        super(panel, name, label, content);

        // The value div.
        this.valueDiv = this.content.appendElement({ "tag": "ul", "id": this.id + "_" + name + "_div", "class": "collection col s12 m8" }).down()

        // The editor div.
        this.valueEditor = this.content.appendElement({ "tag": "div", "id": this.id + "_" + name + "_editor", "class": "col s12 m8", "style": "display: none; padding: 0px; margin: 0px; position: relative;" }).down()

        // Return the value of the input.
        this.valueEditor.getValue = () => {
            let inputs = document.getElementsByName(this.id + "_" + name + "_group")
            let values = new Array<string>()
            for (var i = 0; i < inputs.length; i++) {
                values.push((<any>inputs[i]).value)
            }
            return values;
        }

        // Return the value of the input.
        this.valueEditor.setValue = (values: any) => {
            if (values == undefined) {
                return;
            }
            let appendEditor = (index: number, value: string) => {
                let li = ul.appendElement({ "tag": "li", "class": "collection-item", "style": "padding: 0px;" }).down()
                let removeBtn = li.appendElement({ "tag": "label", "id": this.id + "_" +  index + "_label", "style": "display: flex; align-items: center;" }).down()
                    .appendElement({ "tag": "input", "id": this.id + "_" + index + "_input", "name": this.id  + "_" + name + "_group", "type": "text", "value": value })
                    .appendElement({ "tag": "i", "class": "tiny material-icons", "innerHtml": "remove", "style": "z-index: 10;" }).down()
                removeBtn.element.onmouseover = () => {
                    removeBtn.element.style.cursor = "pointer"
                }
                removeBtn.element.onmouseout = () => {
                    removeBtn.element.style.cursor = "default"
                }
                removeBtn.element.onclick = () => {
                    this.panel.hasChange()
                    li.element.parentNode.removeChild(li.element)
                }
                let input = this.content.getChildById(this.id + "_" + index + "_input")
                input.element.onchange = () => {
                    // set the value in the interface.
                    this.valueDiv.setValue(this.valueEditor.getValue())
                    this.panel.hasChange()
                }
            }

            this.valueEditor.removeAllChilds()
            let newLineBtn = this.valueEditor.appendElement({ "tag": "i", "class": "tiny material-icons", "innerHtml": "add_circle_outline", "style": "position: absolute; top: 12px; left: 4px; z-index: 10;" }).down()
            newLineBtn.element.onmouseover = () => {
                newLineBtn.element.style.cursor = "pointer"
            }
            newLineBtn.element.onmouseout = () => {
                newLineBtn.element.style.cursor = "default"
            }

            // Here I will set the list of control to edit the values.
            var ul = this.valueEditor.appendElement({ "tag": "ul", "id": this.id + "_" + name + "_editor", "class": "collection", "style": "padding: 15px;" }).down()

            // Apppend values.
            for (var i = 0; i < values.length; i++) {
                appendEditor(i, values[i])
            }

            newLineBtn.element.onclick = () => {
                // append a new line element.
                appendEditor(this.getValue().length, "")
                this.valueEditor.getChildById(this.id + "_" + this.getValue().length + "_input").element.focus()
                this.panel.hasChange()
            }

        }

        // Return the value of the input.
        this.valueDiv.setValue = (values: any) => {
            if (values == undefined) {
                return;
            }
            // Clear the content.
            this.valueDiv.removeAllChilds()
            // Apppend values.
            for (var i = 0; i < values.length; i++) {
                this.valueDiv.appendElement({ "tag": "li", "class": "collection-item", "id": this.id + "_" + name + "_div_" + i, "innerHtml": values[i] })
            }
        }

        // Set the actual configuration values.
        this.valueDiv.setValue(this.getValue())
        this.valueEditor.setValue(this.getValue())
    }
}

/**
 * That class will contain the general server information.
 */
export class ConfigurationPanel extends Panel {
    public config: any;
    public content: any;
    public btnGroup: any;
    private saveBtn: any;
    private cancelBtn: any;
    private configurationLines: Array<ConfigurationLine>

    constructor(config: any, title: string, id: string) {
        super(id);

        // Keep a pointer to the config.
        this.config = config;

        // Keep textual control
        this.configurationLines = new Array<ConfigurationLine>()

        // Display general information.
        this.div.appendElement({ "tag": "div", "class": "row configuration_panel" }).down()
            .appendElement({ "tag": "div", "class": "col s12 /*m10 offset-m1*/" }).down()
            .appendElement({ "tag": "div", "class": "card" }).down()
            .appendElement({ "tag": "div", "class": "card-content" }).down()
            .appendElement({ "tag": "span", "class": "card-title", "style": "font-size: medium; font-weight: inherit;", "innerHtml": title })
            .appendElement({ "tag": "div", "id": "content" })

            // The action buttons.
            .appendElement({ "tag": "div", "class": "card-action", "id": "btn_group", "style": "text-align: right; display: none;" }).down()
            .appendElement({ "tag": "a", "id": "save_btn", "href": "javascript:void(0)", "class": "waves-effect waves-light btn disabled", "innerHtml": "Save" })
            .appendElement({ "tag": "a", "id": "cancel_btn", "href": "javascript:void(0)", "class": "waves-effect waves-light btn disabled", "innerHtml": "Cancel" })

        // The save button
        this.saveBtn = this.div.getChildById("save_btn")
        this.saveBtn.element.onclick = () => {
            this.save()
        }

        // The cancel button
        this.cancelBtn = this.div.getChildById("cancel_btn")
        this.cancelBtn.element.onclick = () => {
            this.cancel()
        }

        // The group of button.
        this.btnGroup = this.div.getChildById("btn_group")

        // get the content.
        this.content = this.div.getChildById("content")
    }

    /**
     * 
     * @param name 
     * @param label 
     */
    appendEmptyConfig(name: string, label?: string): any {
        let configLine = new ConfigurationLine(this, name, label, this.content)
        return configLine;
    }

    /**
     * Append the textual configuration
     * @param name The name of the property in the configuration object.
     * @param label The value to display as label.
     */
    appendTextualConfig(name: string, label?: string, type?: string, step?: number, min?: number, max?: number): any {
        let configLine = new ConfigurationTextLine(this, name, label, this.content, type, step, min, max)
        this.configurationLines.push(configLine)
        return configLine
    }

    /**
     * Append the textual configuarion.
     * @param name The name of the field in the config.
     * @param label The name to display beside the text area.
     */
    appendLongTextualConfig(name: string, label?: string): any {
        let configLine = new ConfigurationLongTextLine(this, name, label, this.content)
        this.configurationLines.push(configLine)
        return configLine
    }

    /**
     * Append a boolean configuration (on/off true/false male/female...)
     * @param name The name of the property
     * @param labels The labels to display beside the switch
     * @param label Alternative property name in case the property name is a compose name.
     */
    appendBooleanConfig(name: string, labels: Array<string>, label?: string): any {
        let configLine = new ConfigurationToggleLine(this, name, label, this.content, labels)
        this.configurationLines.push(configLine)
        return configLine
    }

    /**
     * Append a multiple line configuration
     * @param name The name of the configuration
     * @param label The display name
     */
    appendStringListConfig(name: string, label?: string): any {
        let configLine = new ConfigurationStringListLine(this, name, label, this.content)
        this.configurationLines.push(configLine)
        return configLine
    }

    /**
     * Append multiple options single choice configuration line.
     * @param name The name of the configuration to display
     * @param options The list of possible values.
     * @param label The name to display in the interface.
     */
    appendMultipleOptionsSingleChoiceConfig(name: string, options: Array<string>, label?: string): any {
        let configLine = new ConfigurationMultipleOptionsSingleChoiceLine(this, name, options, label, this.content)
        this.configurationLines.push(configLine)
        return configLine
    }

    appendEnumConfig(name: string, options: Array<string>, numericalIndex: boolean, label?: string) {
        let configLine = new ConfigurationEnum(this, name, options, label, this.content, numericalIndex)
        this.configurationLines.push(configLine)
        return configLine
    }

    // create control...
    onlogin(data: any) {
        if (this.config == undefined) {
            return // nothing that we can do here.
        }
        super.onlogin(data)

        // set the config with the full values.
        if (data.Services != undefined) {
            this.config = data.Services[this.config.Id]
        } else {
            this.config = data
        }

        if (this.config != undefined) {
            // Display textual input
            for (var i = 0; i < this.configurationLines.length; i++) {
                this.configurationLines[i].unlock()
            }
            this.btnGroup.element.style.display = ""
        } else {
            this.close() // disconnect listners.
            this.config = null;

            console.log(this.id, " has null config!")
        }
    }

    onlogout() {
        if (this.config == undefined) {
            return // nothing that we can do here.
        }

        super.onlogout()

        // display values.
        for (var i = 0; i < this.configurationLines.length; i++) {
            this.configurationLines[i].lock()
        }

        this.btnGroup.element.style.display = "none"
        this.cancel()
    }

    // That function is the same for all configuration panels.
    save() {
        for (var i = 0; i < this.configurationLines.length; i++) {
            this.configurationLines[i].set()
        }

        this.cancelBtn.element.classList.add("disabled")
        this.saveBtn.element.classList.add("disabled")
    }

    // must be overide by each panel.
    cancel() {
        for (var i = 0; i < this.configurationLines.length; i++) {
            this.configurationLines[i].reset()
        }

        this.cancelBtn.element.classList.add("disabled")
        this.saveBtn.element.classList.add("disabled")
    }

    hasChange() {
        this.btnGroup.element.style.display = "" // display it if is not visible.
        this.cancelBtn.element.classList.remove("disabled")
        this.saveBtn.element.classList.remove("disabled")
    }
}