package com.intellisrc.crypt

import com.intellisrc.core.SysClock
import com.intellisrc.crypt.encode.LpCode
import com.intellisrc.crypt.hash.Hash
import spock.lang.Specification
import spock.lang.Unroll

import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

import static com.intellisrc.crypt.encode.LpCode.*

class LpCodeTest extends Specification {

    def "Test all blocks"() {
        setup:
            char[] toEncode = "hello".toCharArray()
        expect:
            println "Original: [$toEncode] (${toEncode.length})"
            charsets.each {
                LpCode lpCode = new LpCode(LOWERCASE, it.value, 9999)
                String encoded = lpCode.encode(toEncode)
                println it.key + " (" + it.value.length + ") [" + encoded + "] (" + encoded.length() + ")"
                assert toEncode == lpCode.decode(encoded.toCharArray())
            }
    }

    def "Test encoding/decoding"() {
        setup:
            char[] toEncode = "HelloWorldThisMustWork".toCharArray()
        expect:
            LpCode lpCode = new LpCode(ALPHA, VISIBLE)
            char[] encoded = lpCode.encode(toEncode)
            println "ENC = " + encoded
            char[] decoded = lpCode.decode(encoded)
            println "DEC = " + decoded
            assert encoded != toEncode
            assert toEncode == decoded
    }

    def "Test encoding/decoding with mistaken input"() {
        setup:
            char[] toEncode = "!Hello World This Must NOT Work 1234".toCharArray()
        expect:
            LpCode lpCode = new LpCode(ANUM, BRAILLE)
            char[] encoded = lpCode.encode(toEncode)
            println "ENC = " + encoded
            char[] decoded = lpCode.decode(encoded)
            println "DEC = " + decoded
            assert encoded != toEncode
            assert toEncode != decoded
    }

    def "Test encoding/decoding with seed"() {
        setup:
            char[] toEncode = "HelloWorld".toCharArray()
            long seed = new Random().nextLong()
            println "Using seed: " + seed
        expect:
            LpCode lpCode = new LpCode(ALPHA, HANZU, seed)
            char[] encoded = lpCode.encode(toEncode)
            println "Encoded: " + encoded
            char[] decoded = lpCode.decode(encoded)
            println "Decoded: " + decoded.toString()
            assert encoded != toEncode
            assert toEncode == decoded
    }

    def "Encoding with the same seed should return same value"() {
        setup:
            char[] toEncode = "HelloWorldThisMustWork".toCharArray()
            long seed = new Random().nextLong()
            println "Using seed: " + seed
            LpCode lpCode1 = new LpCode(ALPHA, LATIN, seed)
            LpCode lpCode2 = new LpCode(ALPHA, LATIN, seed)
            char[] encoded1 = lpCode1.encode(toEncode)
            char[] encoded2 = lpCode2.encode(toEncode)
        expect:
            assert encoded1 == encoded2
    }

    def "Encoding with a different seed should return a different value"() {
        setup:
            char[] toEncode = "HelloWorldThisMustWork".toCharArray()
            long seed1 = new Random().nextLong()
            long seed2 = new Random().nextLong()
            println "Using seed1: " + seed1
            println "Using seed2: " + seed2
            LpCode lpCode1 = new LpCode(ALPHA, LATIN, seed1)
            LpCode lpCode2 = new LpCode(ALPHA, LATIN, seed2)
            char[] encoded1 = lpCode1.encode(toEncode)
            char[] encoded2 = lpCode2.encode(toEncode)
        expect:
            assert encoded1 != encoded2
    }

    def "Storing and restoring number"() {
        setup:
            char[] s = "asoasdiasdqwempoiapsoidas".toCharArray()
            BigInteger num = toNumber(s, LOWERCASE, 999)
            println "Number: " + num
        expect:
            assert toCharArray(num, LOWERCASE, 999) == s
    }

    def "By chunks"() {
        setup:
            int glue = getCodePoint("|") //BRAILLE.first() //You can use a character included inside the OUTPUT charset
            char[] s = ("""The Basic Latin or C0 Controls and Basic Latin Unicode block is the first block of the 
Unicode standard, and the only block which is encoded in one byte in UTF-8. The block contains all the letters and 
control codes of the ASCII encoding. It ranges from U+0000 to U+007F, contains 128 characters and includes the C0 controls, 
ASCII punctuation and symbols, ASCII digits, both the uppercase and lowercase of the English alphabet and a control character.""").toCharArray()
            println String.format("ORIGINAL (len: %d): %s", s.length, s.toString())
            LpCode lpCode = new LpCode(BASIC + NEWLINES, BRAILLE)
            LocalDateTime start = SysClock.now
            println "ENCODED ALL: " + lpCode.encode(s).toString()
            println "LAPSED: " + ChronoUnit.MILLIS.between(start, SysClock.now) //There is an initialization penalty
        expect:
            [10, 25, 50, 100, 200, 500].each {
                start = SysClock.now
                String chunks = lpCode.encodeByChunks(s, glue, it).toString()
                println "BY CHUNKS [$it]: " + chunks
                println "LAPSED: " + ChronoUnit.MILLIS.between(start, SysClock.now)
                String decoded = lpCode.decodeByChunks(chunks.toCharArray(), glue).toString()
                println "DECODED: " + decoded
                assert decoded == s.toString()
            }
        when:
            start = SysClock.now
            println "ENCODED ALL: " + lpCode.encode(s).toString()
            println "LAPSED: " + ChronoUnit.MILLIS.between(start, SysClock.now)
        then:
            assert true
    }

    def "Encode inputStream and write to outputStream"() {
        setup:
            String s = """The Basic Latin or C0 Controls and Basic Latin Unicode block is the first 
block of the Unicode standard, and the only block which is encoded in one byte in UTF-8."""
            InputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8))
            ByteArrayOutputStream baos = new ByteArrayOutputStream()
            LpCode lpCode = new LpCode(BASIC + NEWLINES, HEXAGRAM, 1234)
            lpCode.chunkSize = 20
            lpCode.glueChar = getCodePoint("|")
        when:
            lpCode.encode(inputStream, baos)
            String out = new String(baos.toByteArray(), StandardCharsets.UTF_8)
            println out
        then:
            assert out == lpCode.encodeByChunks(s.toCharArray()).toString()
        when:
            InputStream decodeStream = new ByteArrayInputStream(out.getBytes(StandardCharsets.UTF_8))
            ByteArrayOutputStream decodeOut = new ByteArrayOutputStream()
            lpCode.decode(decodeStream, decodeOut)
            String decodedFinal = new String(decodeOut.toByteArray(), StandardCharsets.UTF_8)
            println decodedFinal
        then:
            assert decodedFinal == s.toString()
        cleanup:
            baos.close()
            inputStream.close()
            decodeOut.close()
            decodeStream.close()
    }

    @Unroll
    def "From numbers to string"() {
        setup:
            BigInteger num = 128344192321353453454
            char[] str = toCharArray(num, charset, seed)
            println "String: " + str.toString()
        expect:
            assert toString(num, charset, seed) == str.toString()
            assert toNumber(str, charset, seed) == num
        where:
            charset | seed
            NUMBERS | 0
            NUMBERS | 999
            DIGITS  | 0
            DIGITS  | 999
    }

    def "SMP Test"() {
        setup:
            LpCode lpCode = new LpCode(LOWERCASE, EGYPTIAN)
            String orig = "helloworldthisisatest"
            String enc = lpCode.encode(orig.toCharArray())
            println enc
            String dec = lpCode.decode(enc.toCharArray())
            println dec
        expect :
            assert orig == dec
    }

    static final String origNum = "0123456789"
    static final String origLow = "something"
    static final List<Integer> custom1 = getCodePoints("OlZEASGTBg") //L33t for numbers
    static final List<Integer> custom2 = getCodePoints("48Â©Î”3Ò“6Ô¨Ñ—Ô�Ï�1Ğ¼Ğ˜0Ï¼9Ğ¯57Î¼Ï�Ï�Ã—Ğ§2") //L33t for alphabet
    def "Translate must work"() {
        expect:
            assert translate(value, from, to) == result
        where:
            value   | from      | to                | result
            origNum | NUMBERS   | CIRCLE_NUMS       | "â“ªâ‘ â‘¡â‘¢â‘£â‘¤â‘¥â‘¦â‘§â‘¨"
            origNum | NUMBERS   | CIRCLE_NEG_NUMS   | "â“¿â�¶â�·â�¸â�¹â�ºâ�»â�¼â�½â�¾"
            origNum | NUMBERS   | COMMA_NUM         | "ğŸ„�ğŸ„‚ğŸ„ƒğŸ„„ğŸ„…ğŸ„†ğŸ„‡ğŸ„ˆğŸ„‰ğŸ„Š"
            origNum | NUMBERS   | DIGITS            | "ğŸ¯°ğŸ¯±ğŸ¯²ğŸ¯³ğŸ¯´ğŸ¯µğŸ¯¶ğŸ¯·ğŸ¯¸ğŸ¯¹"
            origNum | NUMBERS   | PAREN_NUMS        | "â‘´â‘µâ‘¶â‘·â‘¸â‘¹â‘ºâ‘»â‘¼â‘½"
            origNum | NUMBERS   | FW_NUM            | "ï¼�ï¼‘ï¼’ï¼“ï¼”ï¼•ï¼–ï¼—ï¼˜ï¼™"
            origNum | NUMBERS   | custom1           | "OlZEASGTBg"
            origNum | NUMBERS   | LOWERCASE         | "abcdefghij"
            origLow | LOWERCASE | UPPERCASE         | "SOMETHING"
            origLow | LOWERCASE | CIRCLE_UP         | "â“ˆâ“„â“‚â’ºâ“‰â’½â’¾â“ƒâ’¼"
            origLow | LOWERCASE | CIRCLE_LOW        | "â“¢â“�â“œâ“”â“£â“—â“˜â“�â“–"
            origLow | LOWERCASE | SQUARE_UP         | "ğŸ…‚ğŸ„¾ğŸ„¼ğŸ„´ğŸ…ƒğŸ„·ğŸ„¸ğŸ„½ğŸ„¶"
            origLow | LOWERCASE | SQUARE_UP_NEG     | "ğŸ†‚ğŸ…¾ğŸ…¼ğŸ…´ğŸ†ƒğŸ…·ğŸ…¸ğŸ…½ğŸ…¶"
            origLow | LOWERCASE | PAREN_LOW         | "â’®â’ªâ’¨â’ â’¯â’£â’¤â’©â’¢"
            origLow | LOWERCASE | PAREN_UP          | "ğŸ„¢ğŸ„�ğŸ„œğŸ„”ğŸ„£ğŸ„—ğŸ„˜ğŸ„�ğŸ„–"
            origLow | LOWERCASE | FW_LOW            | "ï½“ï½�ï½�ï½…ï½”ï½ˆï½‰ï½�ï½‡"
            origLow | LOWERCASE | FW_UP             | "ï¼³ï¼¯ï¼­ï¼¥ï¼´ï¼¨ï¼©ï¼®ï¼§"
            origLow | LOWERCASE | custom2           | "50Ğ¼37Ô¨Ñ—Ğ˜6"
            origLow | LOWERCASE | NUMBERS           | "som4t78n6"
            origLow | LOWERCASE | CIRCLE_NUMS       | "â‘±â‘­â‘«â‘£â‘²â‘¦â‘§â‘¬â‘¥"

    }

    def "Block and unblock should work"() {
        setup:
            char[] s512 = Hash.SHA512("hello".toCharArray()).toCharArray()
            LpCode lp = new LpCode(HASH_UP, BRAILLE)
            String block = lp.encodeBlock(s512)
        expect:
            println block
            assert lp.decodeBlock(block.toCharArray()) == s512
            assert block == """
â£œâ¢¿â¢ªâ¢Œâ ‰â£³â¡³â •â£§â °â¡ªâ¡¬â¡³â ¡â¢¤â¢¦
â£‚â �â£¢â£¬â °â¢¾â ”â£‘â¡½â£ªâ¡¢â£™â¡™â¢�â£¿â¢”
â£¡â£¸â¡†â ‚â¢®â »â¡·â „â ¼â¢’â ¼â¢…â¢®â¡µâ¡¸â ™
â¡¥â �â£�â¢‚â ²â¡€â¡™â ƒâ ¶â££â¡ƒâ –â£ªâ¡³â£„â¡º
""".trim()
    }
}
