import React, { Component, ChangeEvent } from "react"
import ReactTooltip from "react-tooltip"
import { filter, remove, sortedIndexBy } from "lodash"

import CLASSES from "../constants/classes"
import { ISettingsElement, Log, IAllRaces } from "../constants/interfaces"
import {
    Constraint,
    TimeConstraint,
    ConstraintType,
    getConstraintList,
    setConstraintList,
} from "../game_logic/optimize"
import { GameLogic } from "../game_logic/gamelogic"

interface MyProps {
    race: IAllRaces
    optimizeSettings: Array<ISettingsElement>
    updateOptimize: (fieldKey: string, fieldValue: number | string, doRerunBO?: boolean) => void
    applyOpitimization: (optimizationList: string[]) => Promise<Log | undefined>
    gamelogic: GameLogic
    getOnAddConstraint: (callback: (index: number, action: ConstraintType) => void) => void
    log: (log: Log | undefined) => void
}

interface MyState {
    show: boolean
    tooltipText: string | JSX.Element
    constraintsChangeCount: number
}

export class Optimize extends Component<MyProps, MyState> {
    /**
     * A small settings menu to enable and disable things
     * And to be able to adjust certain sizes
     */
    constructor(props: MyProps) {
        super(props)
        this.state = {
            show: false,
            tooltipText: "",
            constraintsChangeCount: 0,
        }
        props.getOnAddConstraint(this.onAddConstraint.bind(this))
    }

    showSettings = (_e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
        this.setState({
            show: true,
        })
    }

    hideSettings = (_e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
        this.setState({
            show: false,
        })
    }

    onChangeTextArea = (e: ChangeEvent<HTMLTextAreaElement>, itemShortName: string): void => {
        const newValue = e.target.value
        this.props.updateOptimize(itemShortName, newValue)
    }

    onChangeCheckbox = (e: ChangeEvent<HTMLInputElement>, itemShortName: string): void => {
        const newValue = e.target.checked ? 1 : 0
        this.props.updateOptimize(itemShortName, newValue)
    }

    onChange = (e: ChangeEvent<HTMLInputElement>, itemShortName: string): void => {
        const newValue = parseFloat(e.target.value)
        this.props.updateOptimize(itemShortName, newValue)
    }

    onMouseEnter = (_e: React.MouseEvent<HTMLElement, MouseEvent>, item: JSX.Element): void => {
        this.setState({
            tooltipText: item,
        })
    }

    onApply = async (
        e: React.MouseEvent<HTMLDivElement, MouseEvent>,
        optimizationName: string | number
    ): Promise<void> => {
        const log = await this.props.applyOpitimization([`${optimizationName}`])
        this.props.log(log)
    }

    mouseEnterFunc = (
        e: React.MouseEvent<HTMLElement, MouseEvent>,
        tooltip: string | number
    ): void => {
        this.onMouseEnter(e, <div>{tooltip}</div>)
    }

    resizeTextArea(element: HTMLTextAreaElement): void {
        element.style.height = "inherit"
        element.style.height = `${Math.max(50, element.scrollHeight)}px`
    }

    onAddConstraint(index: number, action: ConstraintType): void {
        let didChangeConstraints = false
        index = index === -1 ? this.props.gamelogic.bo.length - 1 : index
        const name = this.props.gamelogic.bo[index].name
        const pos = filter(this.props.gamelogic.bo.slice(0, index), { name }).length
        const eventLog = filter(this.props.gamelogic.eventLog, { name })[pos]
        const itemEndFrame = eventLog?.end || eventLog?.start
        const endTime = Math.floor(itemEndFrame / 22.4)
        if (endTime === undefined) {
            return
        }
        const list: Constraint[] = getConstraintList(this.props.optimizeSettings)

        if (action === "remove") {
            didChangeConstraints = remove(list, { name, pos }).length > 0
        } else {
            const toRemoveList: Constraint[] = []
            let foundName = false
            for (const constraint of list) {
                if (
                    constraint.type === "time" &&
                    constraint.name === name &&
                    constraint.pos === pos
                ) {
                    if (action === "after" || action === "at") {
                        foundName = true
                        if (constraint.after !== endTime) {
                            didChangeConstraints = true
                            constraint.after = endTime
                        }
                    }
                    if (action === "after" && constraint.before <= endTime) {
                        foundName = true
                        didChangeConstraints = true
                        constraint.before = Infinity
                    }
                    if (action === "before" || action === "at") {
                        foundName = true
                        if (constraint.before !== endTime) {
                            didChangeConstraints = true
                            constraint.before = endTime
                        }
                    }
                    if (action === "before" && constraint.after >= endTime) {
                        foundName = true
                        didChangeConstraints = true
                        constraint.after = -Infinity
                    }
                }
            }
            for (const toRemove of toRemoveList) {
                remove(list, toRemove)
            }
            if (!foundName) {
                didChangeConstraints = true
                const newConstraint: TimeConstraint = {
                    type: "time",
                    name,
                    pos,
                    after: action === "after" || action === "at" ? endTime : -Infinity,
                    before: action === "before" || action === "at" ? endTime : Infinity,
                }
                const whereToInsert = sortedIndexBy(
                    list,
                    newConstraint,
                    (constraint) => `${constraint.name}#${constraint.pos}`
                )
                list.splice(whereToInsert, 0, newConstraint)
            }
        }

        if (didChangeConstraints) {
            const constraints: string = setConstraintList(list)
            this.props.log({
                autoClose: true,
                notice: `New optimize constraints: ${constraints ? constraints : "none!"}`,
                temporary: true,
            })
            this.props.updateOptimize("c", constraints, false)
            this.setState({
                constraintsChangeCount: this.state.constraintsChangeCount + 1,
            })
        }
    }

    componentDidMount(): void {
        const textAreas = document.getElementsByTagName("textarea")
        for (const textArea of textAreas) {
            this.resizeTextArea(textArea)
        }
    }

    createInput(item: ISettingsElement, doesHaveConstraints: boolean): JSX.Element {
        if (item.v === undefined) {
            return <></>
        }
        let inputElement: JSX.Element

        const isTextarea = typeof item.v === "string"
        if (isTextarea) {
            inputElement = (
                <textarea
                    className={CLASSES.dropDownInputMultiline}
                    name={item.n}
                    key={item.n}
                    id={item.n}
                    value={item.v}
                    placeholder="Add constraints by moving your cursor on an item and press e, r, t, or y. Then edit constraints here."
                    onChange={(e) => {
                        this.onChangeTextArea(e, item.n)
                    }}
                ></textarea>
            )
        } else if (item.min === 0 && item.max === 1) {
            inputElement = (
                <input
                    className={CLASSES.dropDownInput}
                    name={item.n}
                    key={item.n}
                    id={item.n}
                    type="checkbox"
                    checked={!!item.v}
                    onChange={(e) => {
                        this.onChangeCheckbox(e, item.n)
                    }}
                />
            )
        } else if (item.step !== undefined) {
            inputElement = (
                <input
                    className={CLASSES.dropDownInput}
                    name={item.n}
                    key={item.n}
                    id={item.n}
                    type="number"
                    placeholder={`${item.v}`}
                    defaultValue={item.v}
                    step={item.step}
                    min={item.min}
                    max={item.max}
                    onChange={(e) => {
                        this.onChange(e, item.n)
                    }}
                />
            )
        } else {
            return <></>
        }
        //else
        return (
            <div
                key={item.n}
                className={
                    isTextarea
                        ? CLASSES.dropDownSubContainerMultiline
                        : CLASSES.dropDownSubContainer
                }
            >
                <div
                    className={CLASSES.dropDownLabelMultiline}
                    data-tip
                    data-for="optimizeSettingsTooltip"
                    onMouseEnter={(e) => this.mouseEnterFunc(e, item.tooltip)}
                >
                    {`${item.name}`.split("\n").map((item, key) => {
                        return <div key={key}>{item}</div>
                    })}
                    {item.removes && doesHaveConstraints && item.v === 1 ? (
                        <div className={CLASSES.warningLabel}>
                            Warning: for this optimization to work,constraints will
                            <br />
                            have to be matched when these items are removed!
                        </div>
                    ) : (
                        <></>
                    )}
                </div>
                {inputElement}
            </div>
        )
    }

    render(): JSX.Element {
        const classes = CLASSES.dropDown
        const classesDropdown = this.state.show ? `visible ${classes}` : `hidden ${classes}`
        if (this.state.show) {
            setTimeout(() => this.componentDidMount())
        }
        const optionlessSettings = filter(
            this.props.optimizeSettings,
            (setting: ISettingsElement) =>
                !/Option[0-9]+/.test(`${setting.variableName}`) &&
                (setting.races === undefined || `${setting.races}`.indexOf(this.props.race) >= 0)
        )
        const doesHaveConstraints = getConstraintList(optionlessSettings).length > 0

        const settingsElements = optionlessSettings.map((item, index) => {
            const addiItems = filter(this.props.optimizeSettings, (setting) =>
                new RegExp(`^${item.variableName}Option[0-9]+$`).test(`${setting.variableName}`)
            )
            const additionalField = addiItems.map((item) =>
                this.createInput(item, doesHaveConstraints)
            )
            const classes = [
                CLASSES.dropDownContainer,
                "flex flex-col",
                index > 0 ? "border-t border-black pt-3" : "",
            ]
            const applyButton: JSX.Element | undefined = item.apply ? (
                <div
                    className={`${CLASSES.dropDownButton} m-2 p-2`}
                    onClick={(e) => this.onApply(e, item.variableName)}
                    onMouseEnter={(e) => this.mouseEnterFunc(e, item.tooltip)}
                    data-tip
                    data-for="optimizeSettingsTooltip"
                >
                    {item.apply}
                </div>
            ) : undefined
            return (
                <div key={index} className={classes.join(" ")}>
                    {this.createInput(item, doesHaveConstraints)}
                    {additionalField}
                    {applyButton}
                </div>
            )
        })

        // TODO Add apply button because onChange doesnt work reliably (laggy behavior)

        const settingsButton = (
            <div
                className={CLASSES.buttons}
                onMouseEnter={this.showSettings}
                onMouseLeave={this.hideSettings}
            >
                Optimize
                <div className={classesDropdown}>{settingsElements}</div>
            </div>
        )
        return (
            <div>
                <ReactTooltip place="bottom" id="optimizeSettingsTooltip" className="max-w-xs">
                    {this.state.tooltipText}
                </ReactTooltip>
                {settingsButton}
            </div>
        )
    }
}