package ru.dbotthepony.mc.otm.tests

import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import ru.dbotthepony.mc.otm.core.math.Decimal
import java.io.ObjectInputStream
import java.io.ObjectOutputStream

object DecimalTests {
	@Test
	@DisplayName("Decimal comparison")
	fun comparison() {
		check(Decimal(642.43774) > Decimal(641.43774)) { "must be bigger" }
		check(Decimal(642.43774) > Decimal(-641.43774)) { "must be bigger" }
		check(Decimal(0) == Decimal(0)) { "integer notation" }
		check(Decimal(0.1) > Decimal(0)) { "must be bigger" }
		check(Decimal(-0.1) < Decimal(0)) { "must be lesser" }
		check(Decimal(-1.1) < Decimal(-1)) { "must be lesser" }
		check(Decimal(0.1) == Decimal(0.1)) { "double notation" }
		check(Decimal("0.1") == Decimal("0.1")) { "string notation" }
	}

	@Test
	@DisplayName("Decimal mathematical operations")
	fun math() {
		assertEquals(Decimal(3), Decimal(1) + Decimal(2))
		assertEquals(Decimal(5), Decimal(2) + Decimal(3))
		assertEquals(Decimal(-1), Decimal(6) + Decimal(-7))
		assertEquals(Decimal(-1), Decimal(6) - Decimal(7))
		assertEquals(Decimal(2), Decimal(1) * Decimal(2))
		assertEquals(Decimal(4), Decimal(2) * Decimal(2))
		assertEquals(Decimal(16), Decimal(4) * Decimal(4))
		assertEquals(Decimal(100_000), Decimal(100) * Decimal(1_000))
		assertEquals(Decimal(4), Decimal(16) / Decimal(4))
		assertEquals(Decimal(3), Decimal(12) / Decimal(4))
		assertEquals(Decimal(1), Decimal(15) / Decimal(15))

		assertEquals(Decimal("0.1"), Decimal(1) / Decimal(10))
		assertEquals(Decimal("-0.1"), Decimal(1) / Decimal(-10))
		assertEquals(Decimal("-0.1"), Decimal(-1) / Decimal(10))
		assertEquals(Decimal("0.1"), Decimal(-1) / Decimal(-10))
		assertEquals(Decimal("0.5"), Decimal("0.25") + Decimal("0.25"))
		assertEquals(Decimal("0.5"), Decimal("0.25") * Decimal(2))
		assertEquals(Decimal("-0.5"), Decimal("0.25") * Decimal(-2))
		assertEquals(Decimal("-0.5"), Decimal("-0.25") * Decimal(2))
		assertEquals(Decimal("0.3"), Decimal(0.2) + Decimal(0.1))
		assertEquals(Decimal("0.3"), Decimal("0.2") + Decimal("0.1"))
	}

	@Test
	@DisplayName("Decimal precision test")
	fun precisionTest() {
		assertEquals(0.0, Decimal(4).fractionalDouble)
		assertEquals(0.2, Decimal(4.2).fractionalDouble)
		assertEquals(0.2, Decimal("4.2").fractionalDouble)
		assertEquals(0.25, Decimal("4.25").fractionalDouble)
		assertEquals(-0.25, Decimal("-1.25").fractionalDouble)
		assertEquals(-0.2, Decimal("-1.2").fractionalDouble)
		assertEquals(0.0, Decimal("-1").fractionalDouble)

		assertEquals(0.0f, Decimal(4).fractionalFloat)
		assertEquals(0.2f, Decimal(4.2).fractionalFloat)
		assertEquals(0.2f, Decimal("4.2").fractionalFloat)
		assertEquals(0.25f, Decimal("4.25").fractionalFloat)
		assertEquals(-0.25f, Decimal("-1.25").fractionalFloat)
		assertEquals(-0.2f, Decimal("-1.2").fractionalFloat)
		assertEquals(0.0f, Decimal("-1").fractionalFloat)
	}

	@Test
	@DisplayName("Decimal toString() tests")
	fun toStringTest() {
		assertEquals("0.0", Decimal(0).toString())
		assertEquals("0.0", Decimal("0.000").toString())

		assertEquals("0.001", Decimal("0.001").toString())
		assertEquals("0.1", Decimal("0.1").toString())
		assertEquals("1.0", Decimal("1.0").toString())
		assertEquals("4.0", Decimal("4.0").toString())
		assertEquals("4.0", Decimal("4.000").toString())
		assertEquals("4.0001", Decimal("4.0001").toString())

		assertEquals("-0.001", Decimal("-0.001").toString())
		assertEquals("-0.1", Decimal("-0.1").toString())
		assertEquals("-1.0", Decimal("-1.0").toString())
		assertEquals("-4.0", Decimal("-4.0").toString())
		assertEquals("-4.0", Decimal("-4.000").toString())
		assertEquals("-4.0001", Decimal("-4.0001").toString())

		assertEquals("0.0", Decimal("0.000").toString(1))
		assertEquals("0.00", Decimal("0.000").toString(2))
		assertEquals("0.000", Decimal("0.000").toString(3))
		assertEquals("0.001", Decimal("0.001").toString(3))
		assertEquals("0.00", Decimal("0.001").toString(2))
		assertEquals("0.0", Decimal("0.001").toString(1))
		assertEquals("1.0", Decimal("1.001").toString(1))
		assertEquals("1.1", Decimal("1.101").toString(1))
		assertEquals("1.10", Decimal("1.101").toString(2))
		assertEquals("1.101", Decimal("1.101").toString(3))
		assertEquals("1.1010", Decimal("1.101").toString(4))
	}

	@Test
	@DisplayName("Decimal store/load")
	fun storeLoad() {
		run {
			val f = Decimal(4.28)
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}

		run {
			val f = Decimal(1)
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}

		run {
			val f = Decimal(0)
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}

		run {
			val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475")
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}

		run {
			val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475.25")
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}

		run {
			val f = Decimal(32748293658335.3472302174)
			val loaded = Decimal.fromByteArray(f.toByteArray())
			assertEquals(f, loaded)
		}
	}

	@Test
	@DisplayName("ImpreciseFraction.valueOf")
	fun valueOf() {
		for (i in -2100 .. 2100) {
			assertEquals(Decimal(i), Decimal.valueOf(i))
		}

		for (i in -2100 .. 2100) {
			assertEquals(Decimal(i.toLong()), Decimal.valueOf(i.toLong()))
		}
	}

	@Test
	@DisplayName("Decimal serialization")
	fun serialization() {
		val output = FastByteArrayOutputStream()
		val outputStream = ObjectOutputStream(output)

		val val1 = Decimal("283")
		val val2 = Decimal("0")
		val val3 = Decimal("-4")
		val val4 = Decimal("25.225")

		outputStream.writeObject(val1)
		outputStream.writeObject(val2)
		outputStream.writeObject(val3)
		outputStream.writeObject(val4)

		val input = FastByteArrayInputStream(output.array, 0, output.length)
		val inputStream = ObjectInputStream(input)

		assertEquals(val1, inputStream.readObject())
		assertEquals(val2, inputStream.readObject())
		assertEquals(val3, inputStream.readObject())
		assertEquals(val4, inputStream.readObject())
	}
}
