package domain

sealed class Clue {
    abstract fun isRuleViolated(grid: Grid): Boolean
}

sealed class HorizontalClue : Clue()

class NeighbourClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: Item<B>) :
    HorizontalClue() {
    private val aType = a.itemType
    private val bType = b.itemType

    override fun isRuleViolated(grid: Grid): Boolean {
        val rowA = grid[aType.companion]
        val rowB = grid[bType.companion]

        val ia = rowA.indexOf(aType)
        val ib by lazy { rowB.indexOf(bType) }
        if (ia != -1) {
            if (ib != -1) return !(ib == ia - 1 || ib == ia + 1)
            return !(rowB.getOrNull(ia - 1).mayBe(b) ||
                    rowB.getOrNull(ia + 1).mayBe(b))
        }

        if (ib != -1) {
            return !(rowA.getOrNull(ib - 1).mayBe(a) ||
                    rowA.getOrNull(ib + 1).mayBe(a))
        }

        for (i in 1 until grid.size) {
            if (rowA[i - 1].mayBe(a) &&
                rowB[i - 0].mayBe(b)
            )
                return false

            if (rowA[i - 0].mayBe(a) &&
                rowB[i - 1].mayBe(b)
            )
                return false
        }
        return true
    }
}

class OrderClue<L : ItemClass<L>, R : ItemClass<R>>(val left: Item<L>, val right: Item<R>) :
    HorizontalClue() {
    private val leftType = left.itemType
    private val rightType = right.itemType

    override fun isRuleViolated(grid: Grid): Boolean {
        val rowLeft = grid[leftType.companion]
        val rowRight = grid[rightType.companion]

        val iLeft = rowLeft.indexOf(leftType)
        val iRight by lazy { rowRight.indexOf(rightType) }
        if (iLeft != -1) {
            if (iRight != -1) return iRight <= iLeft

            return rowRight.indexOfLast { it.mayBe(right) } in 0..iLeft
        }

        if (iRight != -1) {
            return rowLeft.indexOfFirst { it.mayBe(left) } >= iRight
        }

        return rowLeft.indexOfFirst { it.mayBe(left) } >= rowRight.indexOfLast { it.mayBe(right) }
    }
}

class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
    val a: Item<A>,
    val b: Item<B>,
    val c: Item<C>
) :
    HorizontalClue() {
    private val aType = a.itemType
    private val bType = b.itemType
    private val cType = c.itemType

    override fun isRuleViolated(grid: Grid): Boolean {
        val rowA = grid[aType.companion]
        val rowB by lazy { grid[bType.companion] }
        val rowC by lazy { grid[cType.companion] }

        val ia = rowA.indexOf(aType)
        val ib by lazy { rowB.indexOf(bType) }
        val ic by lazy { rowC.indexOf(cType) }

        if (ia != -1) {
            if (ib != -1) {
                when (ib) {
                    ia - 1 -> {
                        return if (ic != -1) {
                            ic != ia - 2
                        } else {
                            !rowC[ia - 2].mayBe(c)
                        }
                    }

                    ia + 1 -> {
                        return if (ic != -1) {
                            ic != ia + 2
                        } else {
                            !rowC[ia + 2].mayBe(c)
                        }
                    }

                    else -> return true
                }
            }
            return when (ic) {
                ia - 2 -> !rowB[ia - 1].mayBe(b)
                ia + 2 -> !rowB[ia + 1].mayBe(b)
                -1 -> !(rowB.getOrNull(ia - 1).mayBe(b) && rowC.getOrNull(ia - 2).mayBe(c)) &&
                        !(rowB.getOrNull(ia + 1).mayBe(b) && rowC.getOrNull(ia + 2).mayBe(c))

                else -> true
            }
        }

        if (ib != -1) {
            if (ib == 0 || ib == rowB.size - 1) return true

            return when (ic) {
                -1 -> !(rowA.getOrNull(ib - 1).mayBe(a) && rowC.getOrNull(ia - 2).mayBe(c)) &&
                        !(rowA.getOrNull(ib + 1).mayBe(a) && rowC.getOrNull(ia + 2).mayBe(c))

                ib - 1 -> rowA[ib + 1].mayBe(a)
                ib + 1 -> rowA[ib - 1].mayBe(a)
                else -> false
            }
        }

        if (ic != -1) {
            return !(rowB.getOrNull(ic - 1).mayBe(b) && rowA.getOrNull(ic - 2).mayBe(a)) &&
                    !(rowB.getOrNull(ic + 1).mayBe(b) && rowA.getOrNull(ic + 2).mayBe(a))
        }

        for (i in 2 until grid.size) {
            if (rowA[i - 2].mayBe(a) &&
                rowB[i - 1].mayBe(b) &&
                rowC[i - 0].mayBe(c)
            )
                return false

            if (rowA[i - 0].mayBe(a) &&
                rowB[i - 1].mayBe(b) &&
                rowC[i - 2].mayBe(c)
            )
                return false
        }
        return true
    }
}

class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: Item<B>) : Clue() {
    private val aType = a.itemType
    private val bType = b.itemType

    override fun isRuleViolated(grid: Grid): Boolean {
        val rowA = grid[aType.companion]
        val rowB = grid[bType.companion]

        val ia = rowA.indexOf(aType)
        val ib by lazy { rowB.indexOf(bType) }

        if (ia != -1) {
            return if (ib != -1) {
                ib != ia
            } else {
                !rowB[ia].mayBe(b)
            }
        }

        if (ib != -1) {
            return !rowA[ib].mayBe(a)
        }

        for (i in 0 until grid.size) {
            if (rowA[i].mayBe(a) && rowB[i].mayBe(b)) {
                return false
            }
        }
        return true
    }
}

class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue() {
    override fun isRuleViolated(grid: Grid): Boolean {
        val i = grid.indexOf(item.itemType)
        if (i != -1) return i != index

        return grid[item].mayBe(item)
    }
}
