package quantity

import "base:intrinsics"

LITERS_PER_GALLON :: 3.785411784
MICRO_LITERS_PER_GALLON :: 473176473.0 / 125.0

//----- Liters ----------------------------------
Liters :: struct($V: typeid) where intrinsics.type_is_numeric(V) {
	v: V,
}

@(private = "file")
liters_to_milli_liters :: #force_inline proc "contextless" (
	liters: Liters($V),
) -> Milli_Liters(V) where intrinsics.type_is_numeric(V) {
	return Milli_Liters(V){liters.v * MILLI}
}

@(private = "file", fast_math = {.Allow_Reciprocal})
liters_to_gallons :: #force_inline proc "contextless" (
	liters: Liters($V),
) -> Gallons(V) where intrinsics.type_is_float(V) {
	return Gallons(V){liters.v / LITERS_PER_GALLON}
}

@(private = "file")
liters_to_micro_liters :: #force_inline proc "contextless" (
	liters: Liters($V),
) -> Micro_Liters(V) where intrinsics.type_is_numeric(V) {
	return Micro_Liters(V){liters.v * MICRO}
}

//----- Milliliters ----------------------------------
Milli_Liters :: struct($V: typeid) where intrinsics.type_is_numeric(V) {
	v: V,
}

@(private = "file", fast_math = {.Allow_Reciprocal})
milli_liters_to_liters :: #force_inline proc "contextless" (
	milli_liters: Milli_Liters($V),
) -> Liters(V) where intrinsics.type_is_numeric(V) {
	return Liters(V){milli_liters.v / MILLI}
}

@(private = "file")
milli_liters_to_micro_liters :: #force_inline proc "contextless" (
	milli_liters: Milli_Liters($V),
) -> Micro_Liters(V) where intrinsics.type_is_numeric(V) {
	return Micro_Liters(V){milli_liters.v * (MICRO / MILLI)}
}

//----- Microliters ----------------------------------
Micro_Liters :: struct($V: typeid) where intrinsics.type_is_numeric(V) {
	v: V,
}

@(private = "file", fast_math = {.Allow_Reciprocal})
micro_liters_to_liters :: #force_inline proc "contextless" (
	micro_liters: Micro_Liters($V),
) -> Liters(V) where intrinsics.type_is_numeric(V) {
	return Liters(V){micro_liters.v / MICRO}
}

@(private = "file", fast_math = {.Allow_Reciprocal})
micro_liters_to_milli_liters :: #force_inline proc "contextless" (
	micro_liters: Micro_Liters($V),
) -> Milli_Liters(V) where intrinsics.type_is_numeric(V) {
	return Milli_Liters(V){micro_liters.v / (MICRO / MILLI)}
}

@(private = "file", fast_math = {.Allow_Reciprocal})
micro_liters_to_gallons :: #force_inline proc "contextless" (
	micro_liters: Micro_Liters($V),
) -> Gallons(V) where intrinsics.type_is_float(V) {
	return Gallons(V){micro_liters.v / MICRO_LITERS_PER_GALLON}
}

//----- Gallons ----------------------------------
Gallons :: struct($V: typeid) where intrinsics.type_is_numeric(V) {
	v: V,
}

@(private = "file")
gallons_to_liters :: #force_inline proc "contextless" (
	gallons: Gallons($V),
) -> Liters(V) where intrinsics.type_is_float(V) {
	return Liters(V){gallons.v * LITERS_PER_GALLON}
}

@(private = "file")
gallons_to_micro_liters :: #force_inline proc "contextless" (
	gallons: Gallons($V),
) -> Micro_Liters(V) where intrinsics.type_is_float(V) {
	return Micro_Liters(V){gallons.v * MICRO_LITERS_PER_GALLON}
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Conversion Overloads ------------------------
// ---------------------------------------------------------------------------------------------------------------------
to_liters :: proc {
	milli_liters_to_liters,
	micro_liters_to_liters,
	gallons_to_liters,
}

to_milli_liters :: proc {
	liters_to_milli_liters,
	micro_liters_to_milli_liters,
}

to_micro_liters :: proc {
	liters_to_micro_liters,
	milli_liters_to_micro_liters,
	gallons_to_micro_liters,
}

to_gallons :: proc {
	liters_to_gallons,
	micro_liters_to_gallons,
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Tests ------------------------
// ---------------------------------------------------------------------------------------------------------------------
import "core:testing"

@(test)
test_liters_to_milli_liters :: proc(t: ^testing.T) {
	liters := Liters(int){12}
	milli_liters := to_milli_liters(liters)

	testing.expect_value(t, milli_liters, Milli_Liters(int){12_000})
}

@(test)
test_milli_liters_to_liters :: proc(t: ^testing.T) {
	milli_liters := Milli_Liters(int){12_000}
	liters := to_liters(milli_liters)

	testing.expect_value(t, liters, Liters(int){12})
}

@(test)
test_gallons_to_liters :: proc(t: ^testing.T) {
	gallons := Gallons(f32){1}
	liters := to_liters(gallons)

	testing.expect(t, liters.v > 3.78 && liters.v < 3.79)
}

@(test)
test_liters_to_gallons :: proc(t: ^testing.T) {
	liters := Liters(f32){3.785411784}
	gallons := to_gallons(liters)

	testing.expect(t, gallons.v > 0.99 && gallons.v < 1.01)
}

@(test)
test_liters_to_micro_liters :: proc(t: ^testing.T) {
	liters := Liters(int){12}
	micro_liters := to_micro_liters(liters)

	testing.expect_value(t, micro_liters, Micro_Liters(int){12_000_000})
}

@(test)
test_micro_liters_to_liters :: proc(t: ^testing.T) {
	micro_liters := Micro_Liters(int){12_000_000}
	liters := to_liters(micro_liters)

	testing.expect_value(t, liters, Liters(int){12})
}

@(test)
test_milli_liters_to_micro_liters :: proc(t: ^testing.T) {
	milli_liters := Milli_Liters(int){5}
	micro_liters := to_micro_liters(milli_liters)

	testing.expect_value(t, micro_liters, Micro_Liters(int){5_000})
}

@(test)
test_micro_liters_to_milli_liters :: proc(t: ^testing.T) {
	micro_liters := Micro_Liters(int){5_000}
	milli_liters := to_milli_liters(micro_liters)

	testing.expect_value(t, milli_liters, Milli_Liters(int){5})
}

@(test)
test_gallons_to_micro_liters :: proc(t: ^testing.T) {
	gallons := Gallons(f64){1}
	micro_liters := to_micro_liters(gallons)

	testing.expect(t, micro_liters.v > 3_785_411.783 && micro_liters.v < 3_785_411.785)
}

@(test)
test_micro_liters_to_gallons :: proc(t: ^testing.T) {
	micro_liters := Micro_Liters(f64){3_785_411.784}
	gallons := to_gallons(micro_liters)

	testing.expect(t, gallons.v > 0.9999999 && gallons.v < 1.0000001)
}
