package ch.dissem.yaep.ui.common

import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Constraints.Companion.fixed
import androidx.compose.ui.unit.Constraints.Companion.fixedWidth
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ch.dissem.yaep.ui.common.AspectRatio.LANDSCAPE
import ch.dissem.yaep.ui.common.AspectRatio.PORTRAIT
import ch.dissem.yaep.ui.common.AspectRatio.SQUARISH
import kotlin.math.max
import kotlin.math.min


private enum class AspectRatio {
    PORTRAIT, LANDSCAPE, SQUARISH;

    companion object {
        private const val ASPECT_RATIO_LANDSCAPE = 1.4f
        private const val ASPECT_RATIO_PORTRAIT = 1 / ASPECT_RATIO_LANDSCAPE

        fun from(constraints: Constraints): AspectRatio {
            val ratio = constraints.maxWidth.toFloat() / constraints.maxHeight.toFloat()
            return when {
                ratio < ASPECT_RATIO_PORTRAIT -> PORTRAIT
                ratio > ASPECT_RATIO_LANDSCAPE -> LANDSCAPE
                else -> SQUARISH
            }
        }
    }
}

@Composable
fun AdaptiveGameLayout(
    modifier: Modifier = Modifier,
    grid: @Composable () -> Unit,
    horizontalClues: @Composable () -> Unit,
    verticalClues: @Composable () -> Unit,
    time: @Composable () -> Unit,
    divider: @Composable () -> Unit = { HorizontalDivider() },
    spacing: Dp = 8.dp
) {
    Layout(
        contents = listOf(grid, horizontalClues, verticalClues, time, divider, divider),
        modifier = modifier
    ) { measurables, constraints ->
        layout(width = constraints.maxWidth, height = constraints.maxHeight) {
            val aspectRatio = AspectRatio.from(constraints)

            val gridMeasurable = measurables[0][0]
            val horizontalCluesMeasurables = measurables[1]
            val verticalCluesMeasurables = measurables[2]
            val timeMeasurable = measurables[3][0]
            val dividerMeasurable = measurables[4][0]

            val spacingPx = spacing.roundToPx()

            when (aspectRatio) {
                PORTRAIT -> {
                    val divider2Measurable = measurables[5][0]
                    portrait(
                        constraints,
                        spacingPx,
                        gridMeasurable,
                        horizontalCluesMeasurables,
                        verticalCluesMeasurables,
                        timeMeasurable,
                        dividerMeasurable,
                        divider2Measurable
                    )
                }

                SQUARISH -> {
                    squarish(
                        constraints,
                        spacingPx,
                        gridMeasurable,
                        horizontalCluesMeasurables,
                        verticalCluesMeasurables,
                        timeMeasurable
                    )
                }

                LANDSCAPE -> {
                    landscape(
                        constraints,
                        spacingPx,
                        gridMeasurable,
                        horizontalCluesMeasurables,
                        verticalCluesMeasurables,
                        timeMeasurable,
                        dividerMeasurable
                    )
                }
            }
        }
    }
}

private fun Placeable.PlacementScope.portrait(
    constraints: Constraints,
    spacingPx: Int,
    gridMeasurable: Measurable,
    horizontalCluesMeasurables: List<Measurable>,
    verticalCluesMeasurables: List<Measurable>,
    timeMeasurable: Measurable,
    divider1Measurable: Measurable,
    divider2Measurable: Measurable
) {
    val gridSize = constraints.maxWidth
    val gridItemSize = (gridSize - 12 * spacingPx) / 18

    val gridConstraints = fixed(gridSize, gridSize)
    val horizontalCluesConstraints = fixed(
        width = 3 * gridItemSize + 2 * spacingPx,
        height = gridItemSize + 2 * spacingPx
    )
    val verticalCluesConstraints = fixed(
        width = gridItemSize + 2 * spacingPx,
        height = 3 * gridItemSize + 2 * spacingPx
    )
    val timeConstraints = Constraints()
    val dividerConstraints = fixedWidth(gridSize)

    val gridPlaceable = gridMeasurable.measure(gridConstraints)
    val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
        it.measure(horizontalCluesConstraints)
    }
    val verticalCluesPlaceables = verticalCluesMeasurables.map {
        it.measure(verticalCluesConstraints)
    }
    val timePlaceable = timeMeasurable.measure(timeConstraints)
    val divider1Placeable = divider1Measurable.measure(dividerConstraints)
    val divider2Placeable = divider2Measurable.measure(dividerConstraints)

    // Position the grid
    gridPlaceable.place(0, 0)

    divider1Placeable.place(0, gridSize + spacingPx)

    // Position the horizontal clues
    var offsetY = placeClues(
        placeables = horizontalCluesPlaceables,
        offsetX = 0,
        offsetY = gridSize + 2 * spacingPx,
        maxWidth = gridSize
    )

    // Add divider in between
    divider2Placeable.place(0, offsetY + spacingPx)

    // Position the vertical clues
    offsetY = placeClues(
        placeables = verticalCluesPlaceables,
        offsetX = 0,
        offsetY = offsetY + spacingPx + divider2Placeable.height,
        maxWidth = gridSize
    )

    // Position the time
    val remainingSpace = constraints.maxHeight - offsetY
    if (remainingSpace < timePlaceable.height) {
        val scale = remainingSpace.toFloat() / timePlaceable.height.toFloat()
        if (scale > 0.1f) {
            timePlaceable.placeWithLayer(
                x = constraints.maxWidth - timePlaceable.width - spacingPx,
                y = constraints.maxHeight - timePlaceable.height
            ) {
                scaleX = scale
                scaleY = scale
                translationX = (timePlaceable.width * (1 - scale)) / 2f
                translationY = (timePlaceable.height * (1 - scale)) / 2f
            }
        }
    } else {
        timePlaceable.place(
            x = constraints.maxWidth - timePlaceable.width - spacingPx,
            y = constraints.maxHeight - timePlaceable.height
        )
    }
}

private fun Placeable.PlacementScope.squarish(
    constraints: Constraints,
    spacingPx: Int,
    gridMeasurable: Measurable,
    horizontalCluesMeasurables: List<Measurable>,
    verticalCluesMeasurables: List<Measurable>,
    timeMeasurable: Measurable
) {
    val gridSize = (7 * min(constraints.maxWidth, constraints.maxHeight)) / 10
    val gridItemSize = (gridSize - 12 * spacingPx) / 18
    val rightBarWidth = constraints.maxWidth - gridSize - spacingPx

    val gridConstraints = fixed(gridSize, gridSize)
    val horizontalCluesConstraints = fixed(
        width = 3 * gridItemSize + 2 * spacingPx,
        height = gridItemSize + 2 * spacingPx
    )
    val verticalCluesConstraints = fixed(
        width = gridItemSize + 2 * spacingPx,
        height = 3 * gridItemSize + 2 * spacingPx
    )
    val timeConstraints = Constraints()

    val gridPlaceable = gridMeasurable.measure(gridConstraints)
    val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
        it.measure(horizontalCluesConstraints)
    }
    val verticalCluesPlaceables = verticalCluesMeasurables.map {
        it.measure(verticalCluesConstraints)
    }
    val timePlaceable = timeMeasurable.measure(timeConstraints)

    // Position the grid
    gridPlaceable.place(0, 0)

    // Position the horizontal clues
    placeClues(
        placeables = horizontalCluesPlaceables,
        offsetX = gridSize + 2 * spacingPx,
        offsetY = 0,
        maxWidth = rightBarWidth
    )

    // Position the vertical clues
    placeClues(
        placeables = verticalCluesPlaceables,
        offsetX = 0,
        offsetY = gridSize + 2 * spacingPx,
        maxWidth = gridSize
    )

    // Position the time
    timePlaceable.place(
        x = constraints.maxWidth - timePlaceable.width - spacingPx,
        y = constraints.maxHeight - timePlaceable.height
    )
}

private fun Placeable.PlacementScope.landscape(
    constraints: Constraints,
    spacingPx: Int,
    gridMeasurable: Measurable,
    horizontalCluesMeasurables: List<Measurable>,
    verticalCluesMeasurables: List<Measurable>,
    timeMeasurable: Measurable,
    dividerMeasurable: Measurable
) {
    val gridSize = constraints.maxHeight
    val gridItemSize = (gridSize - 12 * spacingPx) / 18
    val rightBarWidth = constraints.maxWidth - gridSize - 2 * spacingPx

    val gridConstraints = fixed(gridSize, gridSize)
    val baseSpace = gridSize - 2 * spacingPx
    val horizontalCluesConstraints = fixed(
        width = 3 * gridItemSize + 2 * spacingPx,
        height = gridItemSize + 2 * spacingPx
    )
    val verticalCluesConstraints = fixed(
        width = gridItemSize + 2 * spacingPx,
        height = 3 * gridItemSize + 2 * spacingPx
    )
    val timeConstraints = Constraints.fixedHeight(baseSpace / 10)
    val dividerConstraints = fixedWidth(rightBarWidth - 2 * spacingPx)

    val gridPlaceable = gridMeasurable.measure(gridConstraints)
    val horizontalCluesPlaceables = horizontalCluesMeasurables.map {
        it.measure(horizontalCluesConstraints)
    }
    val verticalCluesPlaceables = verticalCluesMeasurables.map {
        it.measure(verticalCluesConstraints)
    }
    val timePlaceable = timeMeasurable.measure(timeConstraints)
    val dividerPlaceable = dividerMeasurable.measure(dividerConstraints)

    // Position the grid
    gridPlaceable.place(0, 0)

    // Position the horizontal clues
    val offsetY = placeClues(
        placeables = horizontalCluesPlaceables,
        offsetX = gridSize + 2 * spacingPx,
        offsetY = 0,
        maxWidth = rightBarWidth
    )

    // Add divider in between
    dividerPlaceable.place(gridSize + 3 * spacingPx, offsetY + spacingPx)

    // Position the vertical clues
    placeClues(
        placeables = verticalCluesPlaceables,
        offsetX = gridSize + 2 * spacingPx,
        offsetY = offsetY + spacingPx + dividerPlaceable.height,
        maxWidth = rightBarWidth
    )

    // Position the time
    timePlaceable.place(
        x = constraints.maxWidth - timePlaceable.width - spacingPx,
        y = constraints.maxHeight - timePlaceable.height
    )
}

private fun Placeable.PlacementScope.placeClues(
    placeables: List<Placeable>,
    offsetX: Int,
    offsetY: Int,
    maxWidth: Int
): Int {
    if (placeables.isEmpty()) return offsetY

    val itemWidth = placeables.first().width
    val itemHeight = placeables.first().height
    val columns = max(1, maxWidth / itemWidth)
    val spacing = if (columns == 1) 0 else (maxWidth - columns * itemWidth) / (columns - 1)
    var currentX = offsetX
    var currentY = offsetY
    var i = 0
    for (placeable in placeables) {
        placeable.place(currentX, currentY)
        currentX += itemWidth + spacing
        i++
        if (i % columns == 0 && i < placeables.size) {
            currentX = offsetX
            currentY += itemHeight
        }
    }
    return currentY + itemHeight
}

