package online.fisk.filters

import kotlin.concurrent.thread

abstract class FilterImage {
    abstract var width: Int
    abstract var height: Int

    abstract fun getPixel(x: Int, y: Int): Int
    abstract fun setPixel(x: Int, y: Int, colour: Int)

}

abstract class Filter {

    enum class AvailableFilters(val filterName: String, val filter: Filter){
        Atkinson("Atkinson", FilterAtkinson()),
        BayerTwoByTwo("2x2Bayer", Filter2By2Bayer()),
        BayerThreeByThree("3x3Bayer", Filter3By3Bayer()),
        BayerFourByFour("4x4Bayer", Filter4By4Bayer()),
        BayerEightByEight("8x8Bayer", Filter8By48ayer()),
        BayerFiveByThree("5x3Bayer", Filter5By3Bayer()),
        Burkes("Burkes", FilterBurkes()),
        FalseFloydSteinberg("FalseFloydSteinberg", FilterFalseFloydSteinberg()),
        FloydSteinberg("FloydSteinberg", FilterFloydSteinberg()),
        JarvisJudiceNinke("JarvisJudiceNinke", FilterJarvisJudiceNinke()),
        LeftToRightErrorDiffusion("LeftToRightErrorDiffusion", FilterLeftToRightErrorDiffusion()),
        NewspaperHalftone("NewspaperHalftone", FilterNewspaperHalftone()),
        Random("Random", FilterRandom()),
        Sierra("Sierra", FilterSierra()),
        SierraLite("SierraLite", FilterSierraLite()),
        Stucki("Stucki", FilterStucki()),
        Threshold("Threshold", FilterThreshold()),
        TwoRowSierra("TwoRowSierra", FilterTwoRowSierra())
    }

    companion object {
        var threshold = 128

        const val BLACK = 0x000000
        const val WHITE = 0xffffff
    }

    abstract fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit)

    fun threshold(value: Int): Filter {
        threshold = value
        return this
    }
}

class FilterError: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        System.out.println("Filter name not recognised")
        callback()
    }
}

class Filter2By2Bayer : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(1, 3),
                intArrayOf(4, 2)
            )

            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF

                    gray += gray * matrix[x % 2][y % 2] / 5

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}

class Filter3By3Bayer : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(3, 7, 4),
                intArrayOf(6, 1, 9),
                intArrayOf(2, 8, 5)
            )

            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF
                    gray += gray * matrix[x % 3][y % 3] / 10

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}

class Filter4By4Bayer : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(1, 9, 3, 11),
                intArrayOf(13, 5, 15, 7),
                intArrayOf(4, 12, 2, 10),
                intArrayOf(16, 8, 14, 6)
            )


            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF
                    gray += gray * matrix[x % 4][y % 4] / 17

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }

}

class Filter8By48ayer : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(1, 49, 13, 61, 4, 52, 16, 64),
                intArrayOf(33, 17, 45, 29, 36, 20, 48, 32),
                intArrayOf(9, 57, 5, 53, 12, 60, 8, 56),
                intArrayOf(41, 25, 37, 21, 44, 28, 40, 24),
                intArrayOf(3, 51, 15, 63, 2, 50, 14, 62),
                intArrayOf(35, 19, 47, 31, 34, 18, 46, 30),
                intArrayOf(11, 59, 7, 55, 10, 58, 6, 54),
                intArrayOf(43, 27, 39, 23, 42, 26, 38, 22)
            )

            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF
                    gray += gray * matrix[x % 8][y % 8] / 65

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}

class Filter5By3Bayer : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(9, 3, 0, 6, 12),
                intArrayOf(10, 4, 1, 7, 13),
                intArrayOf(11, 5, 2, 8, 14)
            )

            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF

                    //Horizontal:
                    gray += gray * matrix[x % 3][y % 5] / 16

                    //Vertical:
                    //gray += gray * matrix[y % 3][x % 5] / 16

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}

class FilterNewspaperHalftone: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val matrix = arrayOf(
                intArrayOf(24, 10, 12, 26, 35, 47, 49, 37),
                intArrayOf(8, 0, 2, 14, 45, 59, 61, 51),
                intArrayOf(22, 6, 4, 16, 43, 57, 63, 53),
                intArrayOf(30, 20, 18, 28, 33, 41, 55, 39),
                intArrayOf(34, 46, 48, 36, 25, 11, 13, 27),
                intArrayOf(44, 58, 60, 50, 9, 1, 3, 15),
                intArrayOf(42, 56, 62, 52, 23, 7, 5, 17),
                intArrayOf(32, 40, 54, 38, 31, 21, 19, 29)
            )

            for (y in 0 until height) {
                for (x in 0 until width) {

                    var gray = source.getPixel(x, y) shr 16 and 0xFF

                    gray += gray * matrix[x % 8][y % 8] / 65

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }

}

class FilterFloydSteinberg : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }

            for (y in 0 until height - 1) {
                for (x in 1 until width - 1) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF
                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 7 * error / 16
                    errors[x - 1][y + 1] += 3 * error / 16
                    errors[x][y + 1] += 5 * error / 16
                    errors[x + 1][y + 1] += 1 * error / 16
                }
            }

            callback()
        }
    }
}

class FilterJarvisJudiceNinke : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {

        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }

            for (y in 0 until height - 2) {
                for (x in 2 until width - 2) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 7 * error / 48
                    errors[x + 2][y] += 5 * error / 48

                    errors[x - 2][y + 1] += 3 * error / 48
                    errors[x - 1][y + 1] += 5 * error / 48
                    errors[x][y + 1] += 7 * error / 48
                    errors[x + 1][y + 1] += 5 * error / 48
                    errors[x + 2][y + 1] += 3 * error / 48

                    errors[x - 2][y + 2] += 1 * error / 48
                    errors[x - 1][y + 2] += 3 * error / 48
                    errors[x][y + 2] += 5 * error / 48
                    errors[x + 1][y + 2] += 3 * error / 48
                    errors[x + 2][y + 2] += 1 * error / 48
                }
            }

            callback()
        }
    }
}

class FilterSierra : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }
            for (y in 0 until height - 2) {
                for (x in 2 until width - 2) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 5 * error / 32
                    errors[x + 2][y] += 3 * error / 32

                    errors[x - 2][y + 1] += 2 * error / 32
                    errors[x - 1][y + 1] += 4 * error / 32
                    errors[x][y + 1] += 5 * error / 32
                    errors[x + 1][y + 1] += 4 * error / 32
                    errors[x + 2][y + 1] += 2 * error / 32

                    errors[x - 1][y + 2] += 2 * error / 32
                    errors[x][y + 2] += 3 * error / 32
                    errors[x + 1][y + 2] += 2 * error / 32
                }
            }

            callback()
        }
    }
}

class FilterTwoRowSierra : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }

            for (y in 0 until height - 1) {
                for (x in 2 until width - 2) {
                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 4 * error / 16
                    errors[x + 2][y] += 3 * error / 16

                    errors[x - 2][y + 1] += 1 * error / 16
                    errors[x - 1][y + 1] += 2 * error / 16
                    errors[x][y + 1] += 3 * error / 16
                    errors[x + 1][y + 1] += 2 * error / 16
                    errors[x + 2][y + 1] += 1 * error / 16
                }
            }

            callback()
        }
    }
}

//Incremented by 2X2 each iteration
class FilterSierraLite : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }

            var y = 0
            while (y < height - 1) {
                var x = 1
                while (x < width - 1) {
                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }


                    errors[x + 1][y] += 2 * error / 4

                    errors[x - 1][y + 1] += 1 * error / 4
                    errors[x][y + 1] += 1 * error / 4

                    x += 2
                }
                y += 2
            }

            callback()
        }
    }

}

class FilterAtkinson : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }

            for (y in 0 until height - 2) {
                for (x in 1 until width - 2) {
                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += error / 8
                    errors[x + 2][y] += error / 8

                    errors[x - 1][y + 1] += error / 8
                    errors[x][y + 1] += error / 8
                    errors[x + 1][y + 1] += error / 8

                    errors[x][y + 2] += error / 8
                }
            }

            callback()
        }
    }
}

class FilterStucki : Filter() {
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }
            for (y in 0 until height - 2) {
                for (x in 2 until width - 2) {
                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 8 * error / 42
                    errors[x + 2][y] += 4 * error / 42

                    errors[x - 2][y + 1] += 2 * error / 42
                    errors[x - 1][y + 1] += 4 * error / 42
                    errors[x][y + 1] += 8 * error / 42
                    errors[x + 1][y + 1] += 4 * error / 42
                    errors[x + 2][y + 1] += 2 * error / 42

                    errors[x - 2][y + 2] += 1 * error / 42
                    errors[x - 1][y + 2] += 2 * error / 42
                    errors[x][y + 2] += 4 * error / 42
                    errors[x + 1][y + 2] += 2 * error / 42
                    errors[x + 2][y + 2] += 1 * error / 42
                }
            }

            callback()
        }
    }
}

class FilterBurkes: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }
            for (y in 0 until height - 1) {
                for (x in 2 until width - 2) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 8 * error / 32
                    errors[x + 2][y] += 4 * error / 32

                    errors[x - 2][y + 1] += 2 * error / 32
                    errors[x - 1][y + 1] += 4 * error / 32
                    errors[x][y + 1] += 8 * error / 32
                    errors[x + 1][y + 1] += 4 * error / 32
                    errors[x + 2][y + 1] += 2 * error / 32
                }
            }

            callback()
        }
    }
}

class FilterFalseFloydSteinberg: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            val errors = Array(width) { IntArray(height) }
            for (y in 0 until height - 1) {
                for (x in 1 until width - 1) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var error: Int

                    when {
                        gray + errors[x][y] < threshold -> {
                            error = gray + errors[x][y]
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            error = gray + errors[x][y] - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    errors[x + 1][y] += 3 * error / 8
                    errors[x][y + 1] += 3 * error / 8
                    errors[x + 1][y + 1] += 2 * error / 8
                }
            }

            callback()
        }
    }
}

class FilterLeftToRightErrorDiffusion: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            for (y in 0 until height) {
                var error = 0

                for (x in 0 until width) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    var delta: Int

                    when {
                        gray + error < threshold -> {
                            delta = gray
                            destination.setPixel(x, y, BLACK)
                        }
                        else -> {
                            delta = gray - 255
                            destination.setPixel(x, y, WHITE)
                        }
                    }

                    if (Math.abs(delta) < 10) delta = 0

                    error += delta
                }
            }

            callback()
        }
    }
}

class FilterRandom: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            for (y in 0 until height) {

                for (x in 0 until width) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    val threshold = (Math.random() * 1000).toInt() % 256

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}

class FilterThreshold: Filter(){
    override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
        thread {
            val width = source.width
            val height = source.height

            for (y in 0 until height) {
                for (x in 0 until width) {

                    val gray = source.getPixel(x, y) shr 16 and 0xFF

                    when {
                        gray < threshold -> destination.setPixel(x, y, BLACK)
                        else -> destination.setPixel(x, y, WHITE)
                    }
                }
            }

            callback()
        }
    }
}