package ru.dbotthepony.mc.otm.core.math

import net.minecraft.nbt.ByteArrayTag
import net.minecraft.nbt.Tag
import java.math.BigInteger

/**
 * Performs type check+cast and sums two numbers.
 *
 * Returns the same type as on input (adding Double to Float will not return Double, etc.)
 */
fun Number.dynPlus(other: Number): Number {
	return when (this) {
		is Float -> this 	+ other.toFloat()
		is Double -> this 	+ other.toDouble()
		is Int -> this 		+ other.toInt()
		is Long -> this 	+ other.toLong()
		is Short -> this 	+ other.toShort()
		is Byte -> this 	+ other.toByte()

		is Decimal -> {
			when (other) {
				is Float -> this 					+ other.toFloat()
				is Double -> this 					+ other.toDouble()
				is Int, is Byte, is Short -> this 	+ other.toInt()
				is Long -> this 					+ other.toLong()
				is Decimal -> this 		+ other
				else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)")
			}
		}

		else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)")
	}
}

/**
 * Performs type check+cast and subtracts two numbers.
 *
 * Returns the same type as on input (adding Double to Float will not return Double, etc.)
 */
fun Number.dynMinus(other: Number): Number {
	return when (this) {
		is Float -> this 	- other.toFloat()
		is Double -> this 	- other.toDouble()
		is Int -> this 		- other.toInt()
		is Long -> this 	- other.toLong()
		is Short -> this 	- other.toShort()
		is Byte -> this 	- other.toByte()

		is Decimal -> {
			when (other) {
				is Float -> this 					- other.toFloat()
				is Double -> this 					- other.toDouble()
				is Int, is Byte, is Short -> this 	- other.toInt()
				is Long -> this 					- other.toLong()
				is Decimal -> this 		- other
				else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)")
			}
		}

		else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)")
	}
}

/**
 * Performs type check+cast and multiplies two numbers.
 *
 * Returns the same type as on input (adding Double to Float will not return Double, etc.)
 */
fun Number.dynTimes(other: Number): Number {
	return when (this) {
		is Float -> this 	* other.toFloat()
		is Double -> this 	* other.toDouble()
		is Int -> this 		* other.toInt()
		is Long -> this 	* other.toLong()
		is Short -> this 	* other.toShort()
		is Byte -> this 	* other.toByte()

		is Decimal -> {
			when (other) {
				is Float -> this 					* other.toFloat()
				is Double -> this 					* other.toDouble()
				is Int, is Byte, is Short -> this 	* other.toInt()
				is Long -> this 					* other.toLong()
				is Decimal -> this 		* other
				else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)")
			}
		}

		else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)")
	}
}

/**
 * Performs type check+cast and divides two numbers.
 *
 * Returns the same type as on input (adding Double to Float will not return Double, etc.)
 */
fun Number.dynDiv(other: Number): Number {
	return when (this) {
		is Float -> this 	/ other.toFloat()
		is Double -> this 	/ other.toDouble()
		is Int -> this 		/ other.toInt()
		is Long -> this 	/ other.toLong()
		is Short -> this 	/ other.toShort()
		is Byte -> this 	/ other.toByte()

		is Decimal -> {
			when (other) {
				is Float -> this 					/ other.toFloat()
				is Double -> this 					/ other.toDouble()
				is Int, is Byte, is Short -> this 	/ other.toInt()
				is Long -> this 					/ other.toLong()
				is Decimal -> this 		/ other
				else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)")
			}
		}

		else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)")
	}
}

val Number.isFractional get() = this is Float || this is Double || this is Decimal
val Number.isWhole get() = !isFractional
fun Number.toDecimal(): Decimal {
	return when (this) {
		is Decimal -> this
		is Float -> Decimal(this)
		is Double -> Decimal(this)
		is Int -> Decimal(this)
		is Byte -> Decimal(this)
		is Short -> Decimal(this)
		is Long -> Decimal(this)
		else -> throw ClassCastException("Can not turn $this into ImpreciseFraction")
	}
}

fun BigInteger(tag: Tag?): BigInteger {
	if (tag !is ByteArrayTag)
		return BigInteger.ZERO

	return BigInteger(tag.asByteArray)
}

fun BigInteger.serializeNBT(): ByteArrayTag {
	return ByteArrayTag(toByteArray())
}
