/*
 * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package generators.unicode

import generators.unicode.ranges.RangesWritingStrategy
import templates.COPYRIGHT_NOTICE
import templates.autoGeneratedWarning
import java.io.File
import java.io.FileWriter

internal fun FileWriter.writeHeader(file: File, pkg: String) {
    println("Generating file: $file")
    appendLine(COPYRIGHT_NOTICE)
    appendLine("package $pkg")
    appendLine()
    appendLine(autoGeneratedWarning("GenerateUnicodeData.kt"))
}

internal fun FileWriter.writeIntArray(name: String, elements: List<Int>, strategy: RangesWritingStrategy, useHex: Boolean = true) {
    val toString = if (useHex) Int::toHexIntLiteral else Int::toString
    writeCollection(name, "intArrayOf", elements.map(toString), strategy)
}

private fun FileWriter.writeCollection(
    name: String,
    constructingFun: String,
    elements: List<String>,
    strategy: RangesWritingStrategy
) {
    fun appendWithIndentation(string: String) {
        append(strategy.indentation + string)
    }

    appendWithIndentation("${strategy.rangesVisibilityModifier} val $name = $constructingFun(")
    for (i in elements.indices) {
        if (i % 20 == 0) {
            appendLine()
            appendWithIndentation("    ")
        }
        append(elements[i] + ", ")
    }
    appendLine()
    appendWithIndentation(")")
    appendLine()
}

internal fun FileWriter.writeMappings(mappings: Map<Int, List<String>>, strategy: RangesWritingStrategy) {
    val keys = mappings.keys.map { it.toHexIntLiteral() }
    writeCollection("keys", "intArrayOf", keys, strategy)
    val values = mappings.values.map { it.hexCharsToStringLiteral() }
    writeCollection("values", "arrayOf", values, strategy)
}

internal const val TO_BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

internal fun List<Int>.toVarLenBase64(): String {
    val base64 = flatMap { it.to6Bits() }
    return base64.joinToString(separator = "") { TO_BASE64[it].toString() }
}

private fun Int.to6Bits(): List<Int> {
    require(this >= 0)

    val result = mutableListOf<Int>()

    var value = this
    do {
        var fiveBits = value and 0x1f
        value = value shr 5
        if (value != 0) {
            fiveBits = fiveBits or 0x20
        }
        result.add(fiveBits)
    } while (value != 0)

    return result
}

internal fun Int.toHexIntLiteral(): String {
    val result = toString(radix = 16)
    if (result.first() == '-') {
        return "-0x" + result.substring(startIndex = 1).padStart(4, '0')
    }
    return "0x" + result.padStart(4, '0')
}

internal fun Int.toHexCharLiteral(): String {
    return "'\\u${toString(radix = 16).padStart(4, '0')}'"
}

internal fun String.hexToInt(): Int {
    return toInt(radix = 16)
}

internal fun List<String>.hexCharsToStringLiteral(): String {
    return "\"${joinToString(separator = "") { "\\u$it" }}\""
}

internal fun IntRange.rangeCheck(ch: String, indent: String): String {
    val firstHex = first.toHexIntLiteral()
    val lastHex = last.toHexIntLiteral()
    return when (first) {
        last ->
            "$ch == $firstHex"
        last - 1 ->
            "$ch == $firstHex\n$indent|| $ch == $lastHex"
        else ->
            "$ch in $firstHex..$lastHex"
    }
}
