package ru.dbotthepony.kstarbound.math.matrix

import ru.dbotthepony.kommons.arrays.Array2D
import ru.dbotthepony.kommons.arrays.mulMatrixComponents
import java.nio.Buffer

private inline fun <N : Number, F : Array2D> getOrElse(
	get: (F, Int, Int) -> N,
	input: F,
	column: Int,
	row: Int,
	orElse: N
): N {
	if (column < input.columns && row < input.rows) {
		return get(input, column, row)
	} else {
		return orElse
	}
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct4(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R,
	filler: N
): R {
	return factory(
		getOrElse(get, from, 0, 0, filler),
		getOrElse(get, from, 0, 1, filler),
		getOrElse(get, from, 0, 2, filler),
		getOrElse(get, from, 0, 3, filler),
		getOrElse(get, from, 1, 0, filler),
		getOrElse(get, from, 1, 1, filler),
		getOrElse(get, from, 1, 2, filler),
		getOrElse(get, from, 1, 3, filler),
		getOrElse(get, from, 2, 0, filler),
		getOrElse(get, from, 2, 1, filler),
		getOrElse(get, from, 2, 2, filler),
		getOrElse(get, from, 2, 3, filler),
		getOrElse(get, from, 3, 0, filler),
		getOrElse(get, from, 3, 1, filler),
		getOrElse(get, from, 3, 2, filler),
		getOrElse(get, from, 3, 3, filler),
	)
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct3(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R,
	filler: N
): R {
	return factory(
		getOrElse(get, from, 0, 0, filler), getOrElse(get, from, 0, 1, filler), getOrElse(get, from, 0, 2, filler),
		getOrElse(get, from, 1, 0, filler), getOrElse(get, from, 1, 1, filler), getOrElse(get, from, 1, 2, filler),
		getOrElse(get, from, 2, 0, filler), getOrElse(get, from, 2, 1, filler), getOrElse(get, from, 2, 2, filler),
	)
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct2(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N,
		N, N,
	) -> R,
	filler: N
): R {
	return factory(
		getOrElse(get, from, 0, 0, filler), getOrElse(get, from, 0, 1, filler),
		getOrElse(get, from, 1, 0, filler), getOrElse(get, from, 1, 1, filler),
	)
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct4(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	require(from.columns >= 4 && from.rows >= 4) { "Can't use ${from.dimensionsString} matrix to construct 4x4 matrix" }

	return factory(
		get(from, 0, 0), get(from, 0, 1), get(from, 0, 2), get(from, 0, 3),
		get(from, 1, 0), get(from, 1, 1), get(from, 1, 2), get(from, 1, 3),
		get(from, 2, 0), get(from, 2, 1), get(from, 2, 2), get(from, 2, 3),
		get(from, 3, 0), get(from, 3, 1), get(from, 3, 2), get(from, 3, 3),
	)
}

internal inline fun <N : Number, R : Array2D> construct4(
	value: N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	return factory(
		value, value, value, value,
		value, value, value, value,
		value, value, value, value,
		value, value, value, value,
	)
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct3(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	require(from.columns >= 3 && from.rows >= 3) { "Can't use ${from.dimensionsString} matrix to construct 3x3 matrix" }

	return factory(
		get(from, 0, 0), get(from, 0, 1), get(from, 0, 2),
		get(from, 1, 0), get(from, 1, 1), get(from, 1, 2),
		get(from, 2, 0), get(from, 2, 1), get(from, 2, 2),
	)
}

internal inline fun <N : Number, R : Array2D> construct3(
	value: N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	return factory(
		value, value, value,
		value, value, value,
		value, value, value,
	)
}

internal inline fun <N : Number, F : Array2D, R : Array2D> construct2(
	from: F,
	get: (F, Int, Int) -> N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	require(from.columns >= 2 && from.rows >= 2) { "Can't use ${from.dimensionsString} matrix to construct 2x2 matrix" }

	return factory(
		get(from, 0, 0), get(from, 0, 1),
		get(from, 1, 0), get(from, 1, 1),
	)
}

internal inline fun <N : Number, R : Array2D> construct2(
	value: N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	return factory(
		value, value,
		value, value,
	)
}

internal inline fun <N : Number, F : Buffer, R : Array2D> construct4(
	from: F,
	get: (F) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	return factory(
		get(from), get(from), get(from), get(from),
		get(from), get(from), get(from), get(from),
		get(from), get(from), get(from), get(from),
		get(from), get(from), get(from), get(from),
	)
}

internal inline fun <N : Number, F : Buffer, R : Array2D> construct3(
	from: F,
	get: (F) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	return factory(
		get(from), get(from), get(from),
		get(from), get(from), get(from),
		get(from), get(from), get(from),
	)
}

internal inline fun <N : Number, F : Buffer, R : Array2D> construct2(
	from: F,
	get: (F) -> N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	return factory(
		get(from), get(from),
		get(from), get(from),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator4(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)), operator(get(a, 0, 2), get(b, 0, 2)), operator(get(a, 0, 3), get(b, 0, 3)),
		operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)), operator(get(a, 1, 2), get(b, 1, 2)), operator(get(a, 1, 3), get(b, 1, 3)),
		operator(get(a, 2, 0), get(b, 2, 0)), operator(get(a, 2, 1), get(b, 2, 1)), operator(get(a, 2, 2), get(b, 2, 2)), operator(get(a, 2, 3), get(b, 2, 3)),
		operator(get(a, 3, 0), get(b, 3, 0)), operator(get(a, 3, 1), get(b, 3, 1)), operator(get(a, 3, 2), get(b, 3, 2)), operator(get(a, 3, 3), get(b, 3, 3)),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator4(
	a: F, b: N,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), b), operator(get(a, 0, 1), b), operator(get(a, 0, 2), b), operator(get(a, 0, 3), b),
		operator(get(a, 1, 0), b), operator(get(a, 1, 1), b), operator(get(a, 1, 2), b), operator(get(a, 1, 3), b),
		operator(get(a, 2, 0), b), operator(get(a, 2, 1), b), operator(get(a, 2, 2), b), operator(get(a, 2, 3), b),
		operator(get(a, 3, 0), b), operator(get(a, 3, 1), b), operator(get(a, 3, 2), b), operator(get(a, 3, 3), b),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator3(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)), operator(get(a, 0, 2), get(b, 0, 2)),
		operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)), operator(get(a, 1, 2), get(b, 1, 2)),
		operator(get(a, 2, 0), get(b, 2, 0)), operator(get(a, 2, 1), get(b, 2, 1)), operator(get(a, 2, 2), get(b, 2, 2)),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator3(
	a: F, b: N,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), b), operator(get(a, 0, 1), b), operator(get(a, 0, 2), b),
		operator(get(a, 1, 0), b), operator(get(a, 1, 1), b), operator(get(a, 1, 2), b),
		operator(get(a, 2, 0), b), operator(get(a, 2, 1), b), operator(get(a, 2, 2), b),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator2(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)),
		operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator2(
	a: F, b: N,
	get: (F, Int, Int) -> N,
	operator: (N, N) -> N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	return factory(
		operator(get(a, 0, 0), b), operator(get(a, 0, 1), b),
		operator(get(a, 1, 0), b), operator(get(a, 1, 1), b),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> times4(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	times: (N, N) -> N,
	plus: (N, N) -> N,
	factory: (
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
		N, N, N, N,
	) -> R
): R {
	return factory(
		mulMatrixComponents(a, b, get, times, plus, 0, 0),
		mulMatrixComponents(a, b, get, times, plus, 0, 1),
		mulMatrixComponents(a, b, get, times, plus, 0, 2),
		mulMatrixComponents(a, b, get, times, plus, 0, 3),
		mulMatrixComponents(a, b, get, times, plus, 1, 0),
		mulMatrixComponents(a, b, get, times, plus, 1, 1),
		mulMatrixComponents(a, b, get, times, plus, 1, 2),
		mulMatrixComponents(a, b, get, times, plus, 1, 3),
		mulMatrixComponents(a, b, get, times, plus, 2, 0),
		mulMatrixComponents(a, b, get, times, plus, 2, 1),
		mulMatrixComponents(a, b, get, times, plus, 2, 2),
		mulMatrixComponents(a, b, get, times, plus, 2, 3),
		mulMatrixComponents(a, b, get, times, plus, 3, 0),
		mulMatrixComponents(a, b, get, times, plus, 3, 1),
		mulMatrixComponents(a, b, get, times, plus, 3, 2),
		mulMatrixComponents(a, b, get, times, plus, 3, 3),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> times3(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	times: (N, N) -> N,
	plus: (N, N) -> N,
	factory: (
		N, N, N,
		N, N, N,
		N, N, N,
	) -> R
): R {
	return factory(
		mulMatrixComponents(a, b, get, times, plus, 0, 0),
		mulMatrixComponents(a, b, get, times, plus, 0, 1),
		mulMatrixComponents(a, b, get, times, plus, 0, 2),
		mulMatrixComponents(a, b, get, times, plus, 1, 0),
		mulMatrixComponents(a, b, get, times, plus, 1, 1),
		mulMatrixComponents(a, b, get, times, plus, 1, 2),
		mulMatrixComponents(a, b, get, times, plus, 2, 0),
		mulMatrixComponents(a, b, get, times, plus, 2, 1),
		mulMatrixComponents(a, b, get, times, plus, 2, 2),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> times2(
	a: F, b: F,
	get: (F, Int, Int) -> N,
	times: (N, N) -> N,
	plus: (N, N) -> N,
	factory: (
		N, N,
		N, N,
	) -> R
): R {
	return factory(
		mulMatrixComponents(a, b, get, times, plus, 0, 0), mulMatrixComponents(a, b, get, times, plus, 0, 1),
		mulMatrixComponents(a, b, get, times, plus, 1, 0), mulMatrixComponents(a, b, get, times, plus, 1, 1),
	)
}

internal inline fun <N : Number, F : Array2D, R : F> operator4(
	a: R, b: F,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	a.requireSizeEquals(b)
	set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
	set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
	set(a, 0, 2, operator(get(a, 0, 2), get(b, 0, 2)))
	set(a, 0, 3, operator(get(a, 0, 3), get(b, 0, 3)))
	set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
	set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
	set(a, 1, 2, operator(get(a, 1, 2), get(b, 1, 2)))
	set(a, 1, 3, operator(get(a, 1, 3), get(b, 1, 3)))
	set(a, 2, 0, operator(get(a, 2, 0), get(b, 2, 0)))
	set(a, 2, 1, operator(get(a, 2, 1), get(b, 2, 1)))
	set(a, 2, 2, operator(get(a, 2, 2), get(b, 2, 2)))
	set(a, 2, 3, operator(get(a, 2, 3), get(b, 2, 3)))
	set(a, 3, 0, operator(get(a, 3, 0), get(b, 3, 0)))
	set(a, 3, 1, operator(get(a, 3, 1), get(b, 3, 1)))
	set(a, 3, 2, operator(get(a, 3, 2), get(b, 3, 2)))
	set(a, 3, 3, operator(get(a, 3, 3), get(b, 3, 3)))
	return a
}

internal inline fun <N : Number, F : Array2D, R : F> operator4(
	a: R, b: N,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	set(a, 0, 0, operator(get(a, 0, 0), b))
	set(a, 0, 1, operator(get(a, 0, 1), b))
	set(a, 0, 2, operator(get(a, 0, 2), b))
	set(a, 0, 3, operator(get(a, 0, 3), b))
	set(a, 1, 0, operator(get(a, 1, 0), b))
	set(a, 1, 1, operator(get(a, 1, 1), b))
	set(a, 1, 2, operator(get(a, 1, 2), b))
	set(a, 1, 3, operator(get(a, 1, 3), b))
	set(a, 2, 0, operator(get(a, 2, 0), b))
	set(a, 2, 1, operator(get(a, 2, 1), b))
	set(a, 2, 2, operator(get(a, 2, 2), b))
	set(a, 2, 3, operator(get(a, 2, 3), b))
	set(a, 3, 0, operator(get(a, 3, 0), b))
	set(a, 3, 1, operator(get(a, 3, 1), b))
	set(a, 3, 2, operator(get(a, 3, 2), b))
	set(a, 3, 3, operator(get(a, 3, 3), b))
	return a
}

internal inline fun <N : Number, F : Array2D, R : F> operator3(
	a: R, b: F,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	a.requireSizeEquals(b)
	set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
	set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
	set(a, 0, 2, operator(get(a, 0, 2), get(b, 0, 2)))
	set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
	set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
	set(a, 1, 2, operator(get(a, 1, 2), get(b, 1, 2)))
	set(a, 2, 0, operator(get(a, 2, 0), get(b, 2, 0)))
	set(a, 2, 1, operator(get(a, 2, 1), get(b, 2, 1)))
	set(a, 2, 2, operator(get(a, 2, 2), get(b, 2, 2)))
	return a
}

internal inline fun <N : Number, F : Array2D, R : F> operator3(
	a: R, b: N,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	set(a, 0, 0, operator(get(a, 0, 0), b))
	set(a, 0, 1, operator(get(a, 0, 1), b))
	set(a, 0, 2, operator(get(a, 0, 2), b))
	set(a, 1, 0, operator(get(a, 1, 0), b))
	set(a, 1, 1, operator(get(a, 1, 1), b))
	set(a, 1, 2, operator(get(a, 1, 2), b))
	set(a, 2, 0, operator(get(a, 2, 0), b))
	set(a, 2, 1, operator(get(a, 2, 1), b))
	set(a, 2, 2, operator(get(a, 2, 2), b))
	return a
}

internal inline fun <N : Number, F : Array2D, R : F> operator2(
	a: R, b: F,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	a.requireSizeEquals(b)
	set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
	set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
	set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
	set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
	return a
}

internal inline fun <N : Number, F : Array2D, R : F> operator2(
	a: R, b: N,
	get: (F, Int, Int) -> N,
	set: (R, Int, Int, N) -> Unit,
	operator: (N, N) -> N,
): R {
	set(a, 0, 0, operator(get(a, 0, 0), b))
	set(a, 0, 1, operator(get(a, 0, 1), b))
	set(a, 1, 0, operator(get(a, 1, 0), b))
	set(a, 1, 1, operator(get(a, 1, 1), b))
	return a
}