package de.kif.frontend.views.calendar

import de.kif.common.CALENDAR_GRID_WIDTH
import de.kif.common.model.Room
import de.kif.common.model.Schedule
import de.kif.common.model.WorkGroup
import de.kif.frontend.iterator
import de.kif.frontend.launch
import de.kif.frontend.repository.RepositoryDelegate
import de.kif.frontend.repository.ScheduleRepository
import de.westermann.kwebview.*
import de.westermann.kwebview.components.Body
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.MouseEvent
import kotlin.browser.window
import kotlin.dom.appendText
import kotlin.dom.isText
import kotlin.js.Date

class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(view) {

    private lateinit var mouseDelta: Point
    private var newCell: CalendarCell? = null

    private var language by dataset.property("language")

    var scheduleId = dataset["id"]?.toLongOrNull() ?: -1

    val schedule =
        RepositoryDelegate(ScheduleRepository, scheduleId)

    private lateinit var workGroup: WorkGroup

    var pending by classList.property("pending")
    var error by classList.property("error")
    private var nextScroll = 0.0

    var editable: Boolean = false

    var moveLookRoom: Long? = null
    var moveLookTime: Int? = null

    private fun onMove(event: MouseEvent) {
        val position = event.toPoint() - mouseDelta

        val cell = calendar.calendarCells.find {
            position in it.dimension
        }

        if (cell != null) {
            if (moveLookRoom != null &&  cell.roomId != moveLookRoom) {
                return
            }
            if (moveLookTime != null && cell.time != moveLookTime) {
                return
            }

            cell += this

            if (newCell == null) {
                style {
                    left = "0"
                    top = "0.1rem"
                }
            }

            async {
                val now = Date.now()
                if (now <= nextScroll) {
                    return@async
                }

                val width = calendar.calendarTable.clientWidth
                val height = window.innerHeight
                val rect = html.getBoundingClientRect()

                if (rect.left < 0.0) {
                    nextScroll = now + 500.0
                    calendar.scrollHorizontalBy(rect.left - 80.0)
                } else if (rect.right > width) {
                    nextScroll = now + 0.500
                    calendar.scrollHorizontalBy(rect.right - width + 50.0)
                }

                if (rect.top < 20.0) {
                    nextScroll = now + 500.0
                    calendar.scrollVerticalBy(rect.top - 50.0)
                } else if (rect.bottom > height - 20.0) {
                    nextScroll = now + 500.0
                    calendar.scrollVerticalBy(rect.bottom - height + 50.0)
                }
            }

            launch {
                calendarTools.setName(cell.getRoom(), cell.time)
            }

            newCell = cell
        }

        event.preventDefault()
        event.stopPropagation()
    }

    private fun onFinishMove(event: MouseEvent) {
        classList -= "drag"

        newCell?.let { cell ->
            launch {
                val newTime = cell.time
                val newRoom = cell.getRoom()

                pending = true

                if (scheduleId < 0) {
                    ScheduleRepository.create(
                        Schedule(
                            null,
                            workGroup,
                            newRoom,
                            calendar.day,
                            newTime,
                            lookRoom = false,
                            lookTime = false
                        )
                    )
                    html.remove()
                } else {
                    ScheduleRepository.update(schedule.get().copy(room = newRoom, time = newTime))
                }
            }
        }
        newCell = null

        for (it in listeners) {
            it.detach()
        }
        listeners = emptyList()

        event.preventDefault()
        event.stopPropagation()
    }

    private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()

    fun startDrag() {
        classList += "drag"

        listeners = listOf(
            Body.onMouseMove.reference(this::onMove),
            Body.onMouseUp.reference(this::onFinishMove),
            Body.onMouseLeave.reference(this::onFinishMove)
        )
    }

    private val calendarTools = CalendarTools(this)

    init {
        onMouseDown { event ->
            if (!editable || event.target != html || "pending" in classList) {
                event.stopPropagation()
                return@onMouseDown
            }

            launch {
                val s = schedule.get()
                val time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH

                val p = calendar.calendarCells.find {
                    it.day == s.day && it.time == time && it.roomId == s.room.id
                }?.dimension?.center ?: dimension.center

                mouseDelta = event.toPoint() - p

                moveLookRoom = if (s.lookRoom) s.room.id else null
                moveLookTime = if (s.lookTime) s.time else null

                startDrag()
            }
            event.preventDefault()
            event.stopPropagation()
        }

        html.appendChild(calendarTools.html)

        launch {
            val s = schedule.get()
            calendarTools.update(s)
        }
    }

    fun load(schedule: Schedule) {
        pending = false

        if (schedule.id != null) scheduleId = schedule.id
        this.schedule.set(schedule)

        style {
            val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()

            val ps = "${pos * 100}%"

            left = ps
            top = "calc($ps + 0.1rem)"
        }

        load(schedule.workGroup)
        calendarTools.update(schedule)

        val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
        val cell = calendar.calendarCells.find {
            it.day == schedule.day && it.time == time && it.roomId == schedule.room.id
        }

        if (cell != null && cell.html != html.parentElement) {
            cell += this
        }
    }

    fun load(workGroup: WorkGroup) {
        pending = false

        language = workGroup.language.code
        this.workGroup = workGroup

        style {
            val size = workGroup.length / CALENDAR_GRID_WIDTH.toDouble()

            val sz = "${size * 100}%"

            width = sz
            height = "calc($sz - 0.2rem)"

            if (workGroup.track?.color != null) {
                backgroundColor = workGroup.track.color.toString()
                color = workGroup.track.color.calcTextColor().toString()
            }
        }

        for (element in html.childNodes) {
            if (element.isText) {
                html.removeChild(element)
            }
        }

        html.appendText(workGroup.name)
    }

    companion object {
        fun create(calendar: Calendar, schedule: Schedule): CalendarEntry {
            val entry = CalendarEntry(calendar, createHtmlView())

            entry.load(schedule)

            return entry
        }

        fun create(calendar: Calendar, workGroup: WorkGroup): CalendarEntry {
            val entry = CalendarEntry(calendar, createHtmlView())

            entry.load(workGroup)
            entry.mouseDelta = Point.ZERO
            entry.startDrag()

            return entry
        }
    }
}
