import data.bitvec.basic
import data.bitvec.core
import data.list.basic
import control.random

section lfsr


-- Example: https://www.youtube.com/watch?v=Ks1pw1X22y4

-- The video shows how to random generate numbers. 
-- This is not cryptographically secure but just a decent random number generator technique.
-- TODO: This is currently a naive implementation, not proving anything.

-- We will be working with bitvectors of length 4
def n : ℕ := 4

-- A seed number that will generate a random initial state.
def seed_for_generator : ℕ := 44343344

-- An initial state, can be anything except "b0000".
def initial_state : bitvec n := bitvec.of_nat n (rand_nat (mk_std_gen seed_for_generator) 1 16).1

-- Taps located at bit 4 and 3.
-- Feed polynomial is then x^4 + x^3 + 1: 1100 (https://en.wikipedia.org/wiki/Linear-feedback_shift_register#Example_polynomials_for_maximal_LFSRs)
-- However, we represent this in reverse order.
def taps : bitvec n := bit1 (bit1 (bit0 0))

-- Taps polynomial should be irreducible and primitive in order to produce 
-- maximum length sequences: https://www.ece.unb.ca/tervo/ece4253/polyprime.shtml
--
-- The taps are hard coded into the program, this means we can't just use
-- any tap setting currently but just 0011.

-- Get the bit at position as a boolean
def bit_at_position (input : bitvec n) (pos : fin n) : bool := input.nth pos

-- Convert the last bit to a bitvec of length 1
def last_as_bitvec (input : bitvec n) : bitvec 1 := 
  if bit_at_position input (fin.of_nat (n-1)) then bitvec.one 1
  else bitvec.zero 1

-- Convert the last bit -1 to a bitvec of length 1
def last_minus_one_as_bitvec (input : bitvec n) : bitvec 1 := 
  if bit_at_position input (fin.of_nat (n-2)) then bitvec.one 1
  else bitvec.zero 1

-- xor the 2 bits
def xored (input : bitvec n) : bitvec 1 := bitvec.xor (last_as_bitvec input) (last_minus_one_as_bitvec input)

-- Do one round of computation
def generate_one (input : bitvec n) : bitvec n := bitvec.fill_shr input 1 (xored input).head

-- Do n rounds of computations
def generate_n : ℕ → bitvec n → bitvec n
| 0 m := generate_one m
| (n+1) m := generate_n n (generate_one m)

-- The maximum period.
def maximum_period : ℕ := (2^n) - 1

-- When counting iterations we start at 0 so we expect to get the initial state
-- back again at maximum_period - 1
def maximum_period_counter : ℕ := maximum_period - 1

-- Test that after maximum_period iterations, we are back at the initial state
#eval initial_state
#eval generate_n maximum_period_counter initial_state

-- Test all outputs are different - naive way.
def r1 : bitvec n := generate_one initial_state
def r2 : bitvec n := generate_one r1
def r3 : bitvec n := generate_one r2
def r4 : bitvec n := generate_one r3
def r5 : bitvec n := generate_one r4
def r6 : bitvec n := generate_one r5
def r7 : bitvec n := generate_one r6
def r8 : bitvec n := generate_one r7
def r9 : bitvec n := generate_one r8
def r10 : bitvec n := generate_one r9
def r11 : bitvec n := generate_one r10
def r12 : bitvec n := generate_one r11
def r13 : bitvec n := generate_one r12
def r14 : bitvec n := generate_one r13
def r15 : bitvec n := generate_one r14

-- We know that we get back to initial state after maximum_period
#eval if (initial_state = r15) then tt else ff

-- Insert all the results into a list.
def results_list : list (bitvec n) := [
  initial_state,
  r1,
  r2,
  r3,
  r4,
  r5,
  r6,
  r7,
  r8,
  r9,
  r10,
  r11,
  r12,
  r13,
  r14,
  r15
]

-- At least one we have one duplicate here as initial_state = r15
#eval results_list.length

-- Dedup the list
#eval results_list.dedup.length

-- List length is now = maximum_period
#eval if results_list.dedup.length = maximum_period then tt else ff


end lfsr
