/*
 * This file is part of LibEuFin.
 * Copyright (C) 2024 Taler Systems S.A.

 * LibEuFin is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.

 * LibEuFin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.

 * You should have received a copy of the GNU Affero General Public
 * License along with LibEuFin; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */

import org.junit.Test
import tech.libeufin.common.Base32Crockford
import tech.libeufin.common.rand
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class EncodingTest {
    @Test
    fun base32() {
        fun roundTripBytes(data: ByteArray) {
            val encoded = Base32Crockford.encode(data)
            val decoded = Base32Crockford.decode(encoded)
            assertContentEquals(data, decoded)
        }

        // Empty
        assert(Base32Crockford.encode(ByteArray(0)).isEmpty())
        assert(Base32Crockford.decode("").isEmpty())
        roundTripBytes(ByteArray(0))

        // Many size
        for (size in 0..100) {
            roundTripBytes(ByteArray(size).rand())
        }

        val ORIGINAL    = "00111VVBASE32TESTXRST7M8J4H2VY3E0N561BAFWDCPKQG9ZNTG"
        val LOWERCASE   = "00111vvbase32testxrst7m8j4h2vy3e0n561bafwdcpkqg9zntg"
        val ALT         = "0O1ILVUBASE32TESTXRST7M8J4H2VY3E0N561BAFWDCPKQG9ZNTG"
        val ALT_LOWER   = "0o1ilvubase32testxrst7m8j4h2vy3e0n561bafwdcpkqg9zntg"
        val specialCases = listOf(LOWERCASE, ALT, ALT_LOWER)

        // Common case
        val decoded = Base32Crockford.decode(ORIGINAL)
        assertEquals(ORIGINAL, Base32Crockford.encode(decoded))

        // Special cases
        for (case in specialCases) {
            assertContentEquals(decoded, Base32Crockford.decode(case))
        }

        // Bad cases
        for (case in listOf('(', '\n', '@')) {
            val err = assertFailsWith<IllegalArgumentException> {
                Base32Crockford.decode("CROCKFORDBASE32${case}TESTDATA")
            }
            assertEquals("invalid Base32 character: $case", err.message)
        }
        assertFailsWith<IllegalArgumentException> {
            Base32Crockford.decode("CROCKFORDBASE32🤯TESTDATA")
        }

        // Gnunet check
        val gnunetInstalled = try {
            val exitValue = ProcessBuilder("gnunet-base32", "-v").start().waitFor()
            exitValue == 0
        } catch (e: java.io.IOException) {
            false
        }
        if (gnunetInstalled) {
            for (size in 0..100) {
                // Generate random blob
                val blob = ByteArray(size).rand()

                // Encode with kotlin
                val encoded = Base32Crockford.encode(blob)
                // Encode with gnunet
                val gnunetEncoded = ProcessBuilder("gnunet-base32").start().run {
                    outputStream.use { it.write(blob) }
                    waitFor()
                    inputStream.readBytes().decodeToString()
                }
                // Check match
                assertEquals(encoded, gnunetEncoded)

                // Decode with kotlin
                val decoded = Base32Crockford.decode(encoded)
                // Decode with gnunet
                val gnunetDecoded = ProcessBuilder("gnunet-base32", "-d").start().run {
                    outputStream.use { it.write(encoded.toByteArray()) }
                    waitFor()
                    inputStream.readBytes()
                }
                // Check match
                assertContentEquals(decoded, gnunetDecoded)
            }
            for (case in specialCases) {
                // Decode with kotlin
                val decoded = Base32Crockford.decode(case)
                // Decode with gnunet
                val gnunetDecoded = ProcessBuilder("gnunet-base32", "-d").start().run {
                    outputStream.use { it.write(case.toByteArray()) }
                    waitFor()
                    inputStream.readBytes()
                }
                // Check match
                assertContentEquals(decoded, gnunetDecoded)
            }
        }
    }
}
