# Draws stuff based on user input and grammar rules

import display
import buttons
import random
import mch22
from machine import Timer

animate = False
tim1 = Timer(-1) # animate
tim2 = Timer(-2) # randomize
tim3 = Timer(-3) # clean screen

# Weighted randomization for random rulesets
rnd_ltr_A = lambda : random.choice("BBBBBDBBBCCCCDDDDaaaabbbccd")
rnd_ltr_B = lambda : random.choice("AAAAAAAAACCCCDDDDaaaabbbccd")
rnd_ltr_C = lambda : random.choice("AAAABDBBBDDDaaaaaaabbbbbbccccccddddd")
rnd_ltr_D = lambda : random.choice("AABBCCCCDDDDDDaaaabbbccccccccdddddddddd")
rnd_ltr_CAPS = lambda : random.choice("AABBCCCCDDDDDD")
rnd_ltr_TERM = lambda : random.choice("aaabbbccd")

def random_rule():
  return([
    ("AA", rnd_ltr_A() + rnd_ltr_A() + rnd_ltr_A()),
    ("BB", rnd_ltr_B() + rnd_ltr_B() + rnd_ltr_B()),
    ("CC", rnd_ltr_C() + rnd_ltr_C()),
    ("DD", rnd_ltr_D() + rnd_ltr_D()),
    (rnd_ltr_CAPS() + rnd_ltr_CAPS(), rnd_ltr_TERM() + rnd_ltr_TERM()),
    ("A", "a"), ("B", "b"), ("C", "c"), ("D", "d")
  ])


seed = ""   # USER INPUT
result = ""  # INTERMEDIATE RULE
draw_result = []  # END RESULT RULE FOR GRAPHICS


# The ruleset, first 4 are constant and the rest are random each time
rulesets = [
  [("AA", "C"), ("BB", "AbA"), ("B", "ACA"), ("A", "a"), ("C", "c")],
  [("AA", "C"), ("BB", "AbA"), ("CC", "D"), ("B", "ACA"), ("A", "a"), ("C", "c"), ("D", "d")],
  [("AA", "B"), ("BB", "AcA"), ("CC", "BDB"), ("B", "ACA"), ("A", "a"), ("C", "c"), ("D", "d")],
  [("AAA", "AD"), ("AA", "CA"), ("CC", "AB"), ("BB", "BDB"), ("B", "ACA"), ("A", "a"), ("C", "c"), ("D", "d")]
  , random_rule(), random_rule(), random_rule(), random_rule(), random_rule(), random_rule(), random_rule(), random_rule()
]

rules_selection = 0
rules = rulesets[rules_selection]

# X direction, Y direction, color, position X, poisition Y
start_vec = [(1, 0, 0xffa822, (display.width() - 40) / 2 / 5  , display.height() / 2 / 5)]
vec = start_vec

# The different operations a rule can get
identity = lambda v : (v[0], v[1], v[2], v[3]+v[0], v[4]+v[1])
rot_L = lambda v : (v[1], -v[0], v[2], v[3]+v[0], v[4]+v[1])
rot_R = lambda v : (-v[1], v[0], v[2], v[3]+v[0], v[4]+v[1])
rot_F = lambda v : (-v[0], -v[1], v[2], v[3]+v[0], v[4]+v[1]) 

#rot_C = lambda v : (v[0], v[1], v[2] * 255  % 4228250370, v[3]+v[0], v[4]+v[1])
# A nicer colours rot_C:
def rot_C(v):
  if(v[2] == 0xffa822):
    return((v[0], v[1], 0x134e6f, v[3]+v[0], v[4]+v[1]))
  elif(v[2] == 0x134e6f):
    return((v[0], v[1], 0xff6150, v[3]+v[0], v[4]+v[1]))
  elif(v[2] == 0xff6150):
    return((v[0], v[1], 0x1ac0c6, v[3]+v[0], v[4]+v[1]))
  elif(v[2] == 0x1ac0c6):
    return((v[0], v[1], 0xdee0e6, v[3]+v[0], v[4]+v[1]))
  else:
    return((v[0], v[1], 0xffa822, v[3]+v[0], v[4]+v[1]))

# What drawing rules output is assigned with
vrules = [("a", identity), ("b", rot_C), ("c", rot_L), ("d", rot_R)]



# THE HORROR BEGINS BELOW



def draw_rule_text():
  display.drawRect(10, 110, 30, 30, True, display.WHITE)
  display.drawText(10, 110, "Rule\n" + str(rules_selection+1), display.MAGENTA, "roboto_regular12")

def draw_bottom_text():
  global animate
  if animate:
    display.drawText(100, 220, "ANIMATE", display.RED, "roboto_regular12")
  else:
    display.drawText(100, 220, "ANIMATE", display.GREEN, "roboto_regular12")
  display.drawText(30, 220, "QUIT                                               CLEAN          DRAW", display.BLACK, "roboto_regular12")

def draw_input_text():
  global seed
  display.drawText(8, 8, "Set seed sequence with A & B, delete with <-", display.BLACK, "roboto_regular12")
  display.drawText(10, 25, seed, display.BLUE, "roboto_regular18")

def init_screen():
  display.drawFill(display.WHITE)
  draw_input_text()
  draw_rule_text()
  draw_bottom_text()
  display.flush()


# The rules both for parsing the input and converting it to drawing rules

# Enrich the user input by feeding it through parser (with some of rules randomly generated)
def apply_rules(s):
  global rules_selection
  global rulesets
  for r in rulesets[rules_selection]:
    if(s.startswith(r[0])):
      return((r[1], len(r[0])))
  return(s[0], 1)

# Match the parsed output with corresponding drawing rule
def find_draw_rule(s):
  global vrules
  for vr in vrules:
    if(s.startswith(vr[0])):
      return(vr[1])

def apply_draw_rules(acc, s):
  if(len(s) == 0):
    return(acc)
  r = find_draw_rule(s)
  # Associate the next drawing rule based on parsed output
  last = r(acc[-1])
  acc.append(last)
  apply_draw_rules(acc, s[1:])
  return(acc)

def draw_figure(points):
  for p in points:
    display.drawRect(p[3] * 5 % 320, p[4] * 5 % 240, 5, 5, True, p[2])
    #display.drawPixel(p[3] * 5 % 320, p[4] * 5 % 240, p[2])
    display.flush()

def calc_origin_vec():
  global vec
  xorgigin = ( min(vec,key=lambda item:item[3]) + max(vec,key=lambda item:item[3]) ) / 2
  xorgigin = ( min(vec,key=lambda item:item[4]) + max(vec,key=lambda item:item[4]) ) / 2
  return(xorigin, yorigin)

def acc_rules(acc_next):
  racc = acc_next[0]
  rnext = acc_next[1]
  if(rnext != ""):
    ar = apply_rules(rnext[0:])
    return(racc + ar[0], rnext[ar[1]:])
  return(racc,"")

def run_rules(s_str):
  acc = ""
  counter = 0
  res = (acc, s_str)
  while res[1] != "" and counter < 100:
    counter += 1
    res = acc_rules(res)
  return(res[0])
  

def iter_rules(s_str):
  counter = 0
  cyc = run_rules(s_str)
  while cyc.lower() != cyc and counter < 100:
    counter += 1
    cyc = run_rules(cyc)
  return(cyc)


def parse_result():
  global result
  global draw_result
  global seed
  global vec
  result = iter_rules(seed)
  draw_result = apply_draw_rules(vec, result)

  # keep only latest ones
  vec = vec[-10:]


# Animation randomization

def randomize_state():
  global vec
  global rules_selection
  #vec[-1][3] = random.randint(10, 54)
  #vec[-1][4] = random.randint(10, 38)
  rules_selection = random.randint(0, len(rulesets))
  draw_rule_text()
  display.flush()


# Drawing frames

def draw_frame():
  global draw_result
  draw_figure(draw_result)
  display.flush()

def animate_frame():
  global animate
  if animate:
    parse_result()
    draw_frame()


# Button presses

def DRAW_btn_press(pressed):
  if(pressed):
    parse_result()
    draw_frame()

def ANIM_btn_press(pressed):
  if(pressed):
    global animate
    animate = not animate
    draw_bottom_text()

    if animate:
      tim1.init(period=79, mode=Timer.PERIODIC, callback=lambda t:animate_frame())
      tim2.init(period=1151, mode=Timer.PERIODIC, callback=lambda t:randomize_state())
      tim3.init(period=71119, mode=Timer.PERIODIC, callback=lambda t:init_screen())
    else:
      tim1.deinit()
      tim2.deinit()
      tim3.deinit()
    display.flush()

def A_btn_press(pressed):
  if pressed:
    global seed
    parse_result()
    seed = seed + "A"
    draw_input_text()
    display.flush()

def B_btn_press(pressed):
  if pressed:
    global seed
    parse_result()
    seed = seed + "B"
    draw_input_text()
    display.flush()


def CLEAN_btn_press(pressed):
  if(pressed):
    init_screen()

def BCKSPC_btn_press(pressed):
  if(pressed):
    global seed
    seed = seed[0:-1]
    parse_result()
    display.drawRect(10 + display.getTextWidth(seed, "roboto_regular18"), 23, 13, 25, True, display.WHITE)
    display.drawText(10, 25, seed, display.BLUE, "roboto_regular18")
    display.flush()


def RULEUP_btn_press(pressed):
  if(pressed):
    global rules_selection
    global start_vec
    global vec
    rules_selection = (rules_selection + 1) % len(rulesets)
    parse_result()
    draw_rule_text()
    display.flush()

def RULEDOWN_btn_press(pressed):
  if(pressed):
    global rules_selection
    global start_vec
    global vec
    rules_selection = (rules_selection - 1) % len(rulesets)
    parse_result()
    draw_rule_text()
    display.flush()

def EXIT_btn_press(pressed):
  if(pressed):
    tim1.deinit()
    tim2.deinit()
    tim3.deinit()
    mch22.exit_python()


# Attach button functions
buttons.attach(buttons.BTN_UP, RULEUP_btn_press)
buttons.attach(buttons.BTN_DOWN, RULEDOWN_btn_press)
buttons.attach(buttons.BTN_LEFT, BCKSPC_btn_press)
buttons.attach(buttons.BTN_SELECT, CLEAN_btn_press)
buttons.attach(buttons.BTN_START, DRAW_btn_press)
buttons.attach(buttons.BTN_MENU, ANIM_btn_press)
buttons.attach(buttons.BTN_A, A_btn_press)
buttons.attach(buttons.BTN_B, B_btn_press)
buttons.attach(buttons.BTN_HOME, EXIT_btn_press)


# Init screen
display.drawFill(display.WHITE)
draw_input_text()
draw_rule_text()
draw_bottom_text()
display.flush()