package de.kif.backend.route

import com.soywiz.klock.*
import com.soywiz.klock.locale.german
import de.kif.backend.Configuration
import de.kif.backend.isAuthenticated
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.ScheduleRepository
import de.kif.backend.view.MenuTemplate
import de.kif.backend.view.respondMain
import de.kif.common.CALENDAR_GRID_WIDTH
import de.kif.common.model.Permission
import de.kif.common.model.Room
import de.kif.common.model.Schedule
import io.ktor.application.call
import io.ktor.response.respondRedirect
import io.ktor.routing.Route
import io.ktor.routing.get
import kotlinx.css.CSSBuilder
import kotlinx.css.Color
import kotlinx.css.pct
import kotlinx.css.rem
import kotlinx.html.*
import java.util.*
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min

const val MINUTES_OF_DAY = 24 * 60

private fun DIV.calendarCell(schedule: Schedule?) {
    if (schedule != null) {
        span("calendar-entry") {
            attributes["style"] = CSSBuilder().apply {
                val size = schedule.workGroup.length / CALENDAR_GRID_WIDTH.toDouble()
                val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()

                this.left = (pos * 100).pct
                this.top = (pos * 100).pct + 0.1.rem

                this.width = (size * 100).pct
                this.height = (size * 100).pct - 0.2.rem

                val c = schedule.workGroup.track?.color
                if (c != null) {
                    backgroundColor = Color(c.toString())
                    color = Color(c.calcTextColor().toString())
                }
            }.toString()
            attributes["data-language"] = schedule.workGroup.language.code
            attributes["data-id"] = schedule.id.toString()

            +schedule.workGroup.name
        }
    }
}

private fun DIV.renderCalendar(
    orientation: CalendarOrientation,
    day: Int,
    from: Int,
    to: Int,
    rooms: List<Room>,
    schedules: Map<Room, Map<Int, Schedule>>
) {
    val gridLabelWidth = 60
    val minutesOfDay = to - from

    val now = Calendar.getInstance()
    val currentTime = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)

    div("calendar-table-box ${orientation.name.toLowerCase().replace("_", "-")}") {
        div("calendar-header") {
            div("calendar-cell") {
                span {
                    +"Room"
                }
            }

            for (room in rooms) {
                div("calendar-cell") {
                    span {
                        +room.name
                    }
                }
            }
        }

        for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
            val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
                if (it < 0) it + MINUTES_OF_DAY else it
            }
            val minutes = (time % 60).toString().padStart(2, '0')
            val hours = (time / 60).toString().padStart(2, '0')
            val timeString = "$hours:$minutes"

            val start = i * CALENDAR_GRID_WIDTH + from
            val end = (i + 1) * CALENDAR_GRID_WIDTH + from - 1

            var rowClass = "calendar-row"

            if (currentTime in start..end) {
                rowClass += " calendar-now calendar-now-${currentTime - start}"
            }

            div(rowClass) {

                div("calendar-cell") {
                    if (time % gridLabelWidth == 0) {
                        span {
                            +timeString
                        }
                    }
                }

                for (room in rooms) {
                    div("calendar-cell") {
                        attributes["data-time"] = start.toString()
                        attributes["data-room"] = room.id.toString()
                        attributes["data-day"] = day.toString()
                        title = room.name + " - " + timeString

                        val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()

                        calendarCell(schedule)
                    }
                }
            }
        }
    }
}

fun Route.calendar() {

    get("/calendar") {
        call.respondRedirect("/calendar/0", true)
    }

    get("/calendar/{day}/rtt") {
        call.response.cookies.append(
            "orientation",
            CalendarOrientation.ROOM_TO_TIME.name,
            maxAge = Int.MAX_VALUE,
            path = "/"
        )
        val day = call.parameters["day"]?.toIntOrNull() ?: 0
        call.respondRedirect("/calendar/$day")
    }

    get("/calendar/{day}/ttr") {
        call.response.cookies.append(
            "orientation",
            CalendarOrientation.TIME_TO_ROOM.name,
            maxAge = Int.MAX_VALUE,
            path = "/"
        )
        val day = call.parameters["day"]?.toIntOrNull() ?: 0
        call.respondRedirect("/calendar/$day")
    }

    get("/calendar/{day}") {
        val user = isAuthenticated(Permission.SCHEDULE)
        val editable = user != null

        val day = call.parameters["day"]?.toIntOrNull() ?: return@get

        val range = ScheduleRepository.getDayRange()
        if (!editable && day !in range) {
            return@get
        }

        val rooms = RoomRepository.all()

        val orientation = call.request.cookies["orientation"]?.let { name ->
            CalendarOrientation.values().find { it.name == name }
        } ?: CalendarOrientation.ROOM_TO_TIME

        val h = ScheduleRepository.getByDay(day)
        val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
            it.associateBy {
                it.time
            }
        }

        var max = 0
        var min = 24 * 60
        for (s in h) {
            max = max(max, s.time + s.workGroup.length)
            min = min(min, s.time)
        }

        if (min > max) {
            val h1 = max
            max = min
            min = h1
        }

        if (editable) {
            min = min(min, 0)
            max = max(max, 24 * 60)
        }

        min = (min / 60 - 1) * 60
        max = (max / 60 + 2) * 60

        val refDate = DateTime(Configuration.Schedule.referenceDate.time)
        val date = refDate + day.days
        val dateString = DateFormat("EEEE, d. MMMM")
            .withLocale(KlockLocale.german)
            .format(date)

        respondMain {
            menuTemplate {
                this.user = user
                active = MenuTemplate.Tab.CALENDAR
            }
            content {
                if (rooms.isEmpty()) {
                    return@content
                }

                div("header") {
                    div("header-left") {
                        if (editable || day - 1 > range.start) {
                            a("/calendar/${day - 1}") { i("material-icons") { +"chevron_left" } }
                        }
                        span {
                            +dateString
                        }
                        if (editable || day + 1 < range.endInclusive) {
                            a("/calendar/${day + 1}") { i("material-icons") { +"chevron_right" } }
                        }
                    }
                    div("header-right") {
                        a("/calendar/$day/rtt", classes = "form-btn") {
                            +"Room to time"
                        }
                        a("/calendar/$day/ttr", classes = "form-btn") {
                            +"Time to room"
                        }
                        if (editable) {
                            button(classes = "form-btn") {
                                id = "calendar-check-constraints"
                                +"Check constraints"
                            }
                            button(classes = "form-btn") {
                                id = "calendar-edit-button"
                                +"Edit"
                            }
                        }
                    }
                }

                div("calendar") {
                    attributes["data-day"] = day.toString()
                    attributes["data-editable"] = editable.toString()

                    div("calendar-table") {
                        renderCalendar(
                            orientation,
                            day,
                            min,
                            max,
                            rooms,
                            schedules
                        )
                    }

                    if (editable) {
                        div("calendar-edit") {
                            div("calendar-edit-main") {
                                div("calendar-edit-search") {
                                    input(InputType.search, name = "search", classes = "form-control") {
                                        placeholder = "Search"
                                        value = ""
                                    }
                                }
                                div("calendar-edit-list") {

                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

enum class CalendarOrientation {
    /**
     * Columns contains time
     * Rows contains rooms
     *
     * Like the old kif tool
     */
    TIME_TO_ROOM,

    /**
     * Columns contains rooms
     * Rows contains time
     *
     * Like the congress schedule
     */
    ROOM_TO_TIME
}
