package org.openrs2.util.charset

import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.charset.Charset
import java.nio.charset.CharsetDecoder
import java.nio.charset.CharsetEncoder
import java.nio.charset.CoderResult

public object ModifiedUtf8Charset : Charset("ModifiedUtf8", null) {
    override fun contains(cs: Charset): Boolean {
        return Charsets.UTF_8.contains(cs) || cs is Cp1252Charset || cs is ModifiedUtf8Charset
    }

    override fun newEncoder(): CharsetEncoder {
        return object : CharsetEncoder(this, 1F, 3F) {
            override fun encodeLoop(input: CharBuffer, output: ByteBuffer): CoderResult {
                while (input.hasRemaining()) {
                    val char = input.get()

                    val len = if (char != '\u0000' && char < '\u0080') {
                        1
                    } else if (char < '\u0800') {
                        2
                    } else {
                        3
                    }

                    if (output.remaining() < len) {
                        input.position(input.position() - 1)
                        return CoderResult.OVERFLOW
                    }

                    when (len) {
                        1 -> output.put(char.code.toByte())
                        2 -> {
                            output.put((0xC0 or ((char.code shr 6) and 0x1F)).toByte())
                            output.put((0x80 or (char.code and 0x3F)).toByte())
                        }
                        else -> {
                            output.put((0xE0 or ((char.code shr 12) and 0x1F)).toByte())
                            output.put((0x80 or ((char.code shr 6) and 0x1F)).toByte())
                            output.put((0x80 or (char.code and 0x3F)).toByte())
                        }
                    }
                }

                return CoderResult.UNDERFLOW
            }
        }
    }

    override fun newDecoder(): CharsetDecoder {
        return object : CharsetDecoder(this, 1F, 1F) {
            override fun decodeLoop(input: ByteBuffer, output: CharBuffer): CoderResult {
                while (input.hasRemaining()) {
                    if (!output.hasRemaining()) {
                        return CoderResult.OVERFLOW
                    }

                    val a = input.get().toInt() and 0xFF
                    if (a != 0 && a < 0x80) {
                        output.put(a.toChar())
                    } else if ((a and 0xE0) == 0xC0) {
                        if (!input.hasRemaining()) {
                            input.position(input.position() - 1)
                            return CoderResult.UNDERFLOW
                        }

                        val b = input.get().toInt() and 0xFF
                        if ((b and 0xC0) != 0x80) {
                            input.position(input.position() - 2)
                            return CoderResult.malformedForLength(2)
                        }

                        output.put((((a and 0x1F) shl 6) or (b and 0x3F)).toChar())
                    } else if ((a and 0xF0) == 0xE0) {
                        if (input.remaining() < 2) {
                            input.position(input.position() - 1)
                            return CoderResult.UNDERFLOW
                        }

                        val b = input.get().toInt() and 0xFF
                        val c = input.get().toInt() and 0xFF
                        if ((b and 0xC0) != 0x80 || (c and 0xC0) != 0x80) {
                            input.position(input.position() - 3)
                            return CoderResult.malformedForLength(3)
                        }

                        output.put((((a and 0x0F) shl 12) or ((b and 0x3F) shl 6) or (c and 0x3F)).toChar())
                    } else {
                        input.position(input.position() - 1)
                        return CoderResult.malformedForLength(1)
                    }
                }

                return CoderResult.UNDERFLOW
            }
        }
    }
}
