# MIT License, Copyright (c) 2023 Marvin Borner
# TODO: Use abstract representation for logic instead of listifying

:import std/Combinator .
:import std/List .
:import std/Logic .
:import std/Number/Ternary T

# bit indicating a one, compatible with std/Logic
b¹ true ⧗ Bit

# bit indicating a zero, compatible with std/Logic
b⁰ false ⧗ Bit

# returns true if a bit is set
b¹? i ⧗ Bit → Boolean

:test (b¹? b¹) (true)
:test (b¹? b⁰) (false)

# returns true if a bit is unset
b⁰? not! ⧗ Bit → Boolean

:test (b⁰? b⁰) (true)
:test (b⁰? b¹) (false)

# shifts a one into a binary number
↑¹‣ [[[[1 (3 2 1 0)]]]] ⧗ Binary → Binary

:test (↑¹[[[0 2]]]) ([[[1 (0 2)]]])

# shifts a zero into a binary number
↑⁰‣ [[[[0 (3 2 1 0)]]]] ⧗ Binary → Binary

:test (↑⁰(+1b)) ((+2b))

# shifts a specified bit into a binary number
up [[[[[4 1 0 (3 2 1 0)]]]]] ⧗ Bit → Binary → Binary

:test (up b¹ [[[0 2]]]) ([[[1 (0 2)]]])
:test (up b⁰ (+1b)) ((+2b))

# converts a binary number to a list of bits
list! [0 z a¹ a⁰] ⧗ Binary → (List Bit)
	z empty
	a¹ [b¹ : 0]
	a⁰ [b⁰ : 0]

:test (list! (+0b)) (empty)
:test (list! (+6b)) (b⁰ : (b¹ : {}b¹))

# converts a list of bits to a binary number
binary! foldr up (+0b) ⧗ (List Bit) → Binary

:test (binary! (list! (+0b))) ((+0b))
:test (binary! (list! (+42b))) ((+42b))

# strips leading 0s from a binary number
strip [^(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : true
	a¹ [0 [[↑¹1 : false]]]
	a⁰ [0 [[(0 (+0b) ↑⁰1) : 0]]]

%‣ strip

:test (%[[[0 2]]]) ((+0b))
:test (%[[[1 (0 (0 (0 (0 2))))]]]) ((+1b))
:test (%(+2b)) ((+2b))

# returns true if a binary number is zero
zero? [0 true [false] i] ⧗ Binary → Boolean

=?‣ zero?

:test (=?(+0b)) (true)
:test (=?[[[0 (0 2)]]]) (true)
:test (=?(+1b)) (false)

# extracts least significant bit from a binary number
lst [0 b⁰ [b¹] [b⁰]] ⧗ Binary → Bit

:test (lst (+0b)) (b⁰)
:test (lst (+1b)) (b¹)
:test (lst (+42b)) (b⁰)

# extracts most significant bit from a binary number
# not really useful for binary numbers, but part of interface
mst [=?0 b⁰ b¹] ⧗ Binary → Bit

:test (mst (+0b)) (b⁰)
:test (mst (+1b)) (b¹)
:test (mst (+42b)) (b¹)

# extracts nth bit from a binary number
nth …!!… ∘ list! ⧗ Binary → Number → Bit

# logical and on two binary numbers
and! binary! ∘∘ (ψ* zip-with …⋀?… list!) ⧗ Binary → Binary → Binary

…⋀!… and!

:test (and! (+1b) (+0b)) ((+0b))
:test (and! (+5b) (+4b)) ((+4b))
:test (and! (+10b) (+12b)) ((+8b))

# logical or on two binary numbers
# TODO: Fix for numbers with different length (→ zero padding?)
or! binary! ∘∘ (ψ* zip-with …⋁?… list!) ⧗ Binary → Binary → Binary

…⋁!… or!

:test (or! (+10b) (+12b)) ((+14b))

# :test (or! (+1b) (+0b)) ((+1b))
# :test (or! (+5b) (+3b)) ((+7b))

# converts the normal binary representation into abstract
abstract! [0 z a¹ a⁰] ⧗ Binary → AbstractBinary
	z (+0b)
	a¹ [[[[1 3]]]]
	a⁰ [[[[0 3]]]]

→^‣ abstract!

:test (→^(+2b)) ([[[0 [[[1 [[[2]]]]]]]]])

# converts the abstracted binary representation back to normal
normal! ω [[0 z a¹ a⁰]] ⧗ AbstractBinary → Binary
	z (+0b)
	a¹ [↑¹([3 3 0] 0)]
	a⁰ [↑⁰([3 3 0] 0)]

→_‣ normal!

:test (→_[[[0 [[[1 [[[2]]]]]]]]]) ((+2b))

# returns true if two binary numbers are equal
# → ignores leading 0s!
# also: ⋀?‣ ∘∘ (ψ* zip-with xnor? list!)
eq? [[abs 1 →^0]] ⧗ Binary → Binary → Boolean
	abs [0 z a¹ a⁰]
		z [=?(→_0)]
		a¹ [[0 false [2 0] [false]]]
		a⁰ [[0 (1 0) [false] [2 0]]]

…=?… eq?

:test ((+0b) =? (+0b)) (true)
:test ([[[1 (0 2)]]] =? [[[1 (0 2)]]]) (true)
:test ([[[1 2]]] =? (+2b)) (false)

# returns true if two binary numbers are not equal
not-eq? not! ∘∘ eq? ⧗ Binary → Binary → Boolean

…≠?… not-eq?

:test ((+0b) ≠? (+0b)) (false)
:test ([[[1 (0 2)]]] ≠? [[[1 (0 2)]]]) (false)
:test ([[[1 (0 2)]]] ≠? (+2b)) (true)

# adds 1 to a binary number (can introduce leading 0s)
inc [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+1b)
	a¹ [0 [[↑¹1 : ↑⁰0]]]
	a⁰ [0 [[↑⁰1 : ↑¹1]]]

++‣ inc

:test (++(+0b)) ((+1b))
:test (++(+2b)) ((+3b))

# subs 1 from a binary number (can introduce leading 0s)
dec [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+0b)
	a¹ [0 [[↑¹1 : ↑⁰1]]]
	a⁰ [0 [[↑⁰1 : ↑¹0]]]

--‣ dec

:test (--(+0b)) ((+0b))
:test (--(+1b)) ([[[0 2]]])
:test (--(+3b)) ((+2b))

# converts a binary number to a balanced ternary number
# TODO: find a better solution
ternary! z [[[rec]]] (+0t) ⧗ Binary → Ternary
	rec =?0 case-end case-inc
		case-inc 2 (T.inc 1) --0
		case-end 1

:test (ternary! (+0b)) ((+0t))
:test (ternary! (+42b)) ((+42t))

# flips the bits of a binary number (1's complement)
complement [[[[3 2 0 1]]]] ⧗ Binary → Binary

-*‣ complement

:test (-*(+0b) =? (+0b)) (true)
:test (-*(+1b) =? (+0b)) (true)
:test (-*(+42b)) ([[[1 (0 (1 (0 (1 (0 2)))))]]])

# inverts a binary number by complementing and incrementing (2's complement)
# don't forget to pad the number with zeroes if needed
invert ++‣ ∘ -*‣ ⧗ Binary → Binary

-‣ invert

:test (-(+0b)) ((+1b))
:test (-(+1b)) ((+1b))

# pads a binary number with zeroes until its as long a another binary number
# TODO: this could probably be done without list magic
pad [[binary! (pad-right ∀(list! %1) b⁰ (list! 0))]] ⧗ Binary → Binary → Binary

:test (pad (+5b) [[[1 2]]]) ([[[1 (0 (0 2))]]])

# adds two binary numbers (can introduce leading 0s)
# second argument gets abstracted (performance)
add [[abs 1 →^0]] ⧗ Binary → Binary → Binary
	abs [c (0 z a¹ a⁰)]
		c¹ [1 ↑⁰(3 0 b¹) ↑¹(3 0 b⁰)]
		a¹ [[[1 (c¹ 1) c¹' c¹]]]
			c¹' [up 1 (3 0 b¹)]
		a⁰ [[[1 (c⁰ 1) c¹ c⁰]]]
			c⁰ [up 1 (3 0 b⁰)]
		z [[0 ++(→_1) →_1]]
		c [[1 0 b⁰]]

…+… add

:test ((+0b) + (+0b) =? (+0b)) (true)
:test ((+0b) + (+3b) =? (+3b)) (true)
:test ((+1b) + (+2b) =? (+3b)) (true)
:test ((+42b) + (+1b) =? (+43b)) (true)
:test ((+1b) + (+42b) =? (+43b)) (true)

# subs two binary numbers (can introduce leading 0s)
# second argument gets abstracted (performance)
# TODO: fix sub*; implementation is completely wrong
sub* [[abs 1 →^0]] ⧗ Binary → Binary → Binary
	abs [c (0 z a¹ a⁰)]
		c¹ [1 ↑¹(3 0 b⁰) ↑¹(3 0 b⁰)]
		a¹ [[[1 (c¹ 1) c¹' c¹]]]
			c¹ [1 ↑⁰(3 0 b¹) ↑¹(3 0 b⁰)]
			c¹' [1 ↑¹(3 0 b⁰) ↑¹(3 0 b⁰)]
		a⁰ [[[1 (c⁰ 1) c⁰' c⁰]]]
			c⁰ [1 ↑¹(3 0 b⁰) ↑⁰(3 0 b⁰)]
			c⁰' [1 (3 0 b⁰) ↑¹(3 0 b¹)]
		z [[0 --(→_1) →_1]]
		c [[1 0 b⁰]]

# subs two binary numbers
# uses addition but with two's complement
# TODO: isn't very performant ⇒ replace with sub*
# TODO: gives wrong results if b>a in a-b
sub [[(0 =? 1) (+0b) -((pad 1 0) + -(pad 0 1))]] ⧗ Binary → Binary → Binary

…-… sub

:test ((+42b) - (+12b) =? (+30b)) (true)
:test ((+3b) - (+0b) =? (+3b)) (true)
:test ((+3b) - (+2b) =? (+1b)) (true)

# TODO: mul/div

# rshifts least significant bit of a binary number
div² [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+0b)
	a¹ [0 [[↑¹1 : 1]]]
	a⁰ [0 [[↑⁰1 : 1]]]

/²‣ div²

:test (/²(+6b) =? (+3b)) (true)
:test (/²(+5b) =? (+2b)) (true)

# returns true if the number is even (remainder mod 2 == 0)
even? ¬‣ ∘ lst ⧗ Binary → Boolean

=²?‣ even?

:test (even? (+0b)) (true)
:test (even? (+1b)) (false)
:test (even? (+41b)) (false)
:test (even? (+42b)) (true)

# returns true if the number is odd (remainder mod 2 == 1)
odd? lst ⧗ Binary → Boolean

≠²?‣ odd?

:test (odd? (+0b)) (false)
:test (odd? (+1b)) (true)
:test (odd? (+41b)) (true)
:test (odd? (+42b)) (false)