package com.pixelized.desktop.lwa.parser.expression

import com.pixelized.shared.lwa.parser.dice.DiceParser
import com.pixelized.shared.lwa.parser.expression.Expression
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
import com.pixelized.shared.lwa.parser.expression.ExpressionParser.Error
import com.pixelized.shared.lwa.parser.word.Word
import com.pixelized.shared.lwa.parser.word.WordParser
import org.junit.Test
import kotlin.test.assertFailsWith

class ExpressionParserTest {

    @Test
    fun testInvalidExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test("", null)
        parser.test(" ", null)
        assertFailsWith(Error.ExpectedTokenCharacter::class) {
            parser.test("1+", null)
        }
        assertFailsWith(Error.ExpectedOperator::class) {
            parser.test("(153", null)
        }
        assertFailsWith(Error.ExpectedOperator::class) {
            parser.test("min(1+1)", null)
        }
    }

    @Test
    fun testArithmeticExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "-1",
            expected = Expression.Inversion(Expression.Flat(1)),
        )
        parser.test(
            expression = "+1",
            expected = Expression.Flat(1),
        )
        parser.test(
            expression = "1+2",
            expected = Expression.Add(Expression.Flat(1), Expression.Flat(2))
        )
        parser.test(
            expression = "1-2",
            expected = Expression.Minus(Expression.Flat(1), Expression.Flat(2))
        )
        parser.test(
            expression = "1*2",
            expected = Expression.Prod(Expression.Flat(1), Expression.Flat(2))
        )
        parser.test(
            expression = "1/2",
            expected = Expression.Div(Expression.Flat(1), Expression.Flat(2))
        )
    }

    @Test
    fun textUrlExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO",
            expected = Expression.UrlExpression("https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO"),
        )
        parser.test(
            expression = "www.google.fr",
            expected = Expression.UrlExpression("www.google.fr"),
        )
        parser.test(
            expression = "google.fr",
            expected = Expression.UrlExpression("google.fr"),
        )
    }

    @Test
    fun testFunctionExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "min(1,2)",
            expected = Expression.Minimum(Expression.Flat(1), Expression.Flat(2))
        )
        parser.test(
            expression = "max(1,2)",
            expected = Expression.Maximum(Expression.Flat(1), Expression.Flat(2))
        )
        parser.test(
            expression = "floor5(13)",
            expected = Expression.Floor5(Expression.Flat(13))
        )
    }

    @Test
    fun testArithmeticPriorityExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "(1+2)*3",
            expected = Expression.Prod(
                Expression.Add(Expression.Flat(1), Expression.Flat(2)),
                Expression.Flat(3),
            )
        )
        parser.test(
            expression = "1+2*3",
            expected = Expression.Add(
                Expression.Flat(1),
                Expression.Prod(Expression.Flat(2), Expression.Flat(3)),
            )
        )
    }

    @Test
    fun testWordExpression() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "BDC",
            expected = Expression.WordExpression(Word(Word.Type.BDC)),
        )
        parser.test(
            expression = "BDD",
            expected = Expression.WordExpression(Word(Word.Type.BDD)),
        )
        parser.test(
            expression = "STR",
            expected = Expression.WordExpression(Word(Word.Type.STR)),
        )
        parser.test(
            expression = "DEX",
            expected = Expression.WordExpression(Word(Word.Type.DEX)),
        )
        parser.test(
            expression = "CON",
            expected = Expression.WordExpression(Word(Word.Type.CON)),
        )
        parser.test(
            expression = "HEI",
            expected = Expression.WordExpression(Word(Word.Type.HEI)),
        )
        parser.test(
            expression = "INT",
            expected = Expression.WordExpression(Word(Word.Type.INT)),
        )
        parser.test(
            expression = "POW",
            expected = Expression.WordExpression(Word(Word.Type.POW)),
        )
        parser.test(
            expression = "CHA",
            expected = Expression.WordExpression(Word(Word.Type.CHA)),
        )
    }

    @Test
    fun testReadWrite() {
        val parser = ExpressionParser(
            diceParser = DiceParser(),
            wordParser = WordParser(),
        )
        parser.test(
            expression = "((1+2)*3)",
        )
        parser.test(
            expression = "(1+(2*3))",
        )
    }

    private fun ExpressionParser.test(
        expression: String,
        expected: Expression?,
    ) {
        val result = parse(input = expression)
        assert(result == expected) {
            "ExpressionParser.parse(input=$expression) is expected to return:$expected, but was:$result type:${result?.let { it::class.java.simpleName }}"
        }
    }

    private fun ExpressionParser.test(
        expression: String,
    ) {
        val result = parse(parse(expression)?.toString())?.toString()
        assert(result == expression) {
            "ExpressionParser.parse(input=$expression) is expected to return:$expression, but was:$result"
        }
    }
}