{.experimental: "codeReordering".}
import ../drawable/[ shape_object
                  ]

, ../vk/[ vulkan_record
        , vulkan 
        ]

, std/[ math
      ]

, actions
, options 
, tables 
### state machine stuff
type
 # entry_action: Option[Callback]
 # exit_action: Option[Callback]
 State_Actions = tuple[ entry_action: Option[Callback]
                      , exit_action: Option[Callback]
                      ]

 Callback = proc(deshn_being: var Deshn_Being): void
 StateEvent[S,E] = tuple[state: S, event: E]
 Transition[S] = tuple[nextState: S, action: Option[Callback]]

 State_Machine*[S,E] =  ref object of RootObj
  initial_state*: S
  current_state*: Option[S]
  state_actions*: array[S, StateActions]
  transitions*: TableRef[StateEvent[S,E], Transition[S]]
  free_transitions*: TableRef[S, Transition[S]]
  default_transition*: Option[Transition[S]]

  #TransitionNotFoundException = object of Exception

proc current_state_is*[S]( being: var Deshn_Being
                          , nextState: S
                          ) =
  if being.main_state_machine.current_state.isSome:
   if being.main_state_machine.state_actions[being.main_state_machine.current_state.get].exit_action.isSome:
    get(being.main_state_machine.state_actions[being.main_state_machine.current_state.get].exit_action)(being)
  
  being.main_state_machine.current_state = some(nextState)
  if being.main_state_machine.state_actions[being.main_state_machine.current_state.get].entry_action.isSome:
   get(being.main_state_machine.state_actions[being.main_state_machine.current_state.get].entry_action)(being)

proc reset*[S,E]( being: var Deshn_Being
                ) =
 being.main_state_machine.current_state_is being.main_state_machine.initial_state

proc initial_state_is*[S,E]( m: State_Machine[S,E]
                           , state: S
                           ) =
 m.initialState = state

proc initial_state_is*[S,E]( being: var Deshn_Being
                          , state: S
                          ) =
 being.main_state_machine.initial_state = state

proc add_state_actions*[S]( being: var Deshn_Being
                            , state: S
                            , entry_action: Callback = nil
                            , exit_action: Callback = nil
                            ) =
 let 
  entry = if entry_action == nil: none(Callback) 
          else: some(entry_action)
  
  exit = if exit_action == nil: none(Callback) 
         else: some(exit_action)

 being.main_state_machine.state_actions[state] = (entry, exit)

proc a_state_machine*[S,E]( initial_state: S
                          ): State_Machine[S,E] =
 result = State_Machine[S,E]()
 result.transitions = newTable[ StateEvent[S,E]
                              , Transition[S]
                              ]()
 
 result.free_transitions = newTable[ S
                                   , Transition[S]
                                   ]()
 
 result.initial_state_is initial_state

proc add_free_transition*[S,E]( being: var Deshn_Being
                              , state: S, nextState: S
                              ) =
 being.main_state_machine.free_transitions[state] = (nextState, none(Callback))

proc addTransitionAny*[S,E]( being: var Deshn_Being
                           , state, nextState: S
                           , action: Callback
                           ) =
 being.main_state_machine.free_transitions[state] = (nextState, some(action))

proc add_transition*[S,E]( being: var Deshn_Being
                         , state: S
                         , event: E
                         , nextState: S
                         ) =
 being.main_state_machine.transitions[(state, event)] = (nextState, none(Callback))

proc add_transition*[S,E]( being: var Deshn_Being
                         , state: S
                         , event: E
                         , nextState: S
                         , action: Callback
                         ) =
 being.main_state_machine.transitions[(state, event)] = (nextState, some(action))

proc setDefaultTransition*[S,E]( being: var Deshn_Being
                               , state: S
                               ) =
 being.main_state_machine.default_transition = some((state, none(Callback)))

proc setDefaultTransition*[S,E]( being: var Deshn_Being
                               , state: S
                               , action: Callback
                               ) =
 being.main_state_machine.default_transition = some((state, some(action)))

proc the_transition*[S,E]( being: var Deshn_Being
                         , event: E
                         , state: S
                         ): Transition[S] =
 let map = (state, event)
 if being.main_state_machine.transitions.hasKey(map): result = being.main_state_machine.transitions[map]
 elif being.main_state_machine.free_transitions.hasKey(state): result = being.main_state_machine.free_transitions[state]
 elif being.main_state_machine.default_transition.isSome: result = being.main_state_machine.default_transition.get
 else: 
  echo map 
  quit "Transition not found"
                         

proc process_event*[E]( being: var Deshn_Being  
                      , event: E
                      ) =
 let transition = being.the_transition( event
                                      , being.main_state_machine.current_state.get
                                      )
 #echo "t0: ", transition[0]
 if transition[1].isSome: get(transition[1])(being)
 being.current_state_is transition[0]
 #echo event, " ", being.main_state_machine.current_state.get

### TODO: PLEASE SORT THIS OUT ^^^^^^^^^^^^^

type
 Main_Deshn_Being_Events* = enum 
  Moved 
  No_Action  
  Added_To_Deshn_Pool
  Removed_From_Deshn_Pool
  Added_To_Recovery_Pool
  Removed_From_Recovery_Pool
  Filled_Deshn_Pool
  Exhausted_Deshn_Pool 
  Filled_Recovery_Pool
  Exhausted_Recovery_Pool 
  Stopped

 Main_Deshn_Being_States* = enum 
  Moving
  Not_Moving 
  Recovering 
  Idle
  Deshn_Pool_Full 
  #Deshn_Pool_Not_Full 
  Deshn_Pool_Empty 
  Deshn_Pool_Emptying 
  Deshn_Pool_Filling 
  Deshn_Pool_Exhausted 
   
 Deshn_Pool_Value* = range[0.0..30.0]
 Deshn_Recovery_Pool_Value* = range[0.0..2.0]

 Deshn_Being_Obj = object of RootObj
  shape*: Shape 
  current_deshn_pool*: float32 = 30
  current_deshn_pool_max_size*: Deshn_Pool_Value = Deshn_Pool_Value.high
  current_deshn_pool_recovery_pool*: Deshn_Recovery_Pool_Value = Deshn_Recovery_Pool_Value.high
  current_deshn_pool_recovery_pool_max*: Deshn_Recovery_Pool_Value = Deshn_Recovery_Pool_Value.high
  recovering*: bool 
  name*: string 
  id*: int
  main_state_machine*: State_Machine[ Main_Deshn_Being_States
                                    , Main_Deshn_Being_Events
                                    ]
                                    

 Deshn_Being* = ref object of Deshn_Being_Obj

proc deshn_pool_is_full*( being: Deshn_Being
                        ): bool = 
 return being.shape.ubo.deshn_pool_is_full 

proc deshn_pool_is_empty*( being: Deshn_Being
                        ): bool = 
 return being.shape.ubo.deshn_pool_is_empty 

proc can_move*( being: var Deshn_Being
              ): bool = 

 return ( not being.deshn_pool_is_empty() and 
          being.current_deshn_pool_recovery_pool == Deshn_Recovery_Pool_Value.high
        ) 

proc add_amount_to_deshn_pool*( being: var Deshn_Being
                              , amount: float32 
                              ) = 

 being.shape.ubo.deshn_pool += amount
 being.shape.ubo.deshn_pool_is_empty = false
 echo being.shape.ubo.deshn_pool 
 being.process_event Main_Deshn_Being_Events.Added_To_Deshn_Pool

 if being.shape.ubo.deshn_pool >= being.current_deshn_pool_max_size: 
  being.main_state_machine.current_state = some Deshn_Pool_Full
  being.shape.ubo.deshn_pool_is_full = true 
  
  #[ #if not being.shape.ubo.deshn_pool_is_full:
 if not (being.main_state_machine.current_state_is Pool_Full)
  being.shape.ubo.deshn_pool += amount 
 
 #if being.shape.ubo.deshn_pool_is_empty:
 if (being.main_state_machine.current_state_is Pool)
  being.shape.ubo.deshn_pool_is_empty = false
  being.shape.ubo.deshn_pool += amount 
 
 
 if being.shape.ubo.deshn_pool >= being.current_deshn_pool_max_size: 
  being.shape.ubo.deshn_pool_is_full = true
  being.shape.ubo.deshn_pool = Deshn_Pool_Value.high  ]#

proc remove_amount_from_deshn_pool*( being: var Deshn_Being
                                   , amount: float32 
                                   ) = 
 being.shape.ubo.deshn_pool -= amount 
 being.process_event Main_Deshn_Being_Events.Removed_From_Deshn_Pool
 being.shape.ubo.deshn_pool_is_full = false 
 if being.shape.ubo.deshn_pool <= Deshn_Pool_Value.low:
  being.recovering = true 
  being.shape.ubo.deshn_pool_is_empty = true
  being.process_event Main_Deshn_Being_Events.Exhausted_Deshn_Pool
 
proc add_amount_to_recovery_pool*( being: var Deshn_Being
                                 , amount: float32 
                                 ) = 

 if (being.current_deshn_pool_recovery_pool + amount > Deshn_Recovery_Pool_Value.high) or 
    being.current_deshn_pool_recovery_pool >= Deshn_Recovery_Pool_Value.high: 
  being.current_deshn_pool_recovery_pool = Deshn_Recovery_Pool_Value.high 
  being.process_event Added_To_Recovery_Pool
  being.process_event Filled_Recovery_Pool 
  
 else: 
  being.current_deshn_pool_recovery_pool += amount
  being.process_event Added_To_Recovery_Pool

proc remove_amount_from_recovery_pool*( being: var Deshn_Being
                                      , amount: float32 
                                      ) = 
 being.current_deshn_pool_recovery_pool -= amount
 if being.current_deshn_pool_recovery_pool <= Deshn_Recovery_Pool_Value.low:
  being.current_deshn_pool_recovery_pool = Deshn_Recovery_Pool_Value.low
  being.recovering = true 

proc idle_and_moved_cb( deshn_being: var Deshn_Being) =
 echo "idle and moved"

proc not_moving_and_moved_cb( deshn_being: var Deshn_Being) =
 echo "stopped and moved"

proc moving_and_no_action_cb( deshn_being: var Deshn_Being) =
 echo "moving and no action"

proc stopped_and_no_action_cb( deshn_being: var Deshn_Being) =
 echo "stopped and no action"

proc deshn_pool_full_and_taken_from_cb( deshn_being: var Deshn_Being) =
 echo "full pool taken from"
 deshn_being.shape.ubo.deshn_pool_is_full = false 

proc deshn_pool_empty_and_added_to_cb( deshn_being: var Deshn_Being) =
 echo "empty pool added to"
 deshn_being.shape.ubo.deshn_pool_is_empty = false 

proc deshn_pool_filling_and_added_to_cb( deshn_being: var Deshn_Being) =
 echo "pool filling added to"

proc deshn_pool_filling_and_full_cb( deshn_being: var Deshn_Being) =
 echo "pool added to and now full"
 deshn_being.shape.ubo.deshn_pool_is_full = true 

proc deshn_pool_full_and_moved_cb( deshn_being: var Deshn_Being) =
 echo "pool full and moved"
 deshn_being.shape.ubo.deshn_pool_is_full = false 

proc deshn_pool_empyting_and_stopped( deshn_being: var Deshn_Being) =
 echo "pool emptying and now stopped"

proc moving_and_exhausted_pool_cb( deshn_being: var Deshn_Being) =
 echo "moving and exhausted pool"

proc exhausted_pool_and_no_action_cb( deshn_being: var Deshn_Being) =
 echo "exhausted pool and no action"

proc deshn_pool_emptying_and_taken_from_cb( deshn_being: var Deshn_Being) =
 echo "pool emptying and taken from"
 if deshn_being.shape.ubo.deshn_pool <= Deshn_Pool_Value.low: 
  deshn_being.shape.ubo.deshn_pool = Deshn_Pool_Value.low
  deshn_being.current_deshn_pool = Deshn_Pool_Value.low
  deshn_being.shape.ubo.deshn_pool_is_empty = true
  
  

proc deshn_pool_empty_and_taken_from_cb( deshn_being: var Deshn_Being) =
 echo "pool empty and taken from"

proc deshn_pool_exhausted_and_no_action_cb( deshn_being: var Deshn_Being) =
 echo "pool exhausted, no action"

proc deshn_pool_exhausted_and_added_to_cb( deshn_being: var Deshn_Being) =
 echo "deshn_pool_exhausted_and_added_to_cb"
 if deshn_being.current_deshn_pool_recovery_pool >= Deshn_Recovery_Pool_Value.high:
  deshn_being.current_deshn_pool_recovery_pool = Deshn_Recovery_Pool_Value.high
  deshn_being.recovering = false 

proc recovering_and_added_to_recovery_pool_cb( deshn_being: var Deshn_Being) =
 echo "recovering_and_added_to_recovery_pool_cb"

proc moving_and_taken_from_pool_cb( deshn_being: var Deshn_Being) =
 echo "moving, taken X from pool"
 
 if deshn_being.shape.ubo.deshn_pool <= Deshn_Pool_Value.low: 
  deshn_being.shape.ubo.deshn_pool = Deshn_Pool_Value.low
  deshn_being.current_deshn_pool_recovery_pool = Deshn_Recovery_Pool_Value.low
  deshn_being.shape.ubo.deshn_pool_is_empty = true
 

proc moving_and_moved_cb( deshn_being: var Deshn_Being) =
 echo "moving, and moved"

proc not_moving_and_added_to_pool_cb( deshn_being: var Deshn_Being) =
 echo "not_moving_and_added_to_pool"

proc moving_and_added_to_pool_cb( deshn_being: var Deshn_Being) =
 echo "moving_and_added_to_pool"

proc deshn_pool_filling_and_moved_cb( deshn_being: var Deshn_Being) =
 echo "deshn_pool_filling_and_moved_cb"

proc filled_recovery_pool_and_now_idle_cb( deshn_being: var Deshn_Being) =
 echo "filled_recovery_pool_and_now_idle_cb"
 deshn_being.recovering = false 


proc idle_and_added_to_deshn_pool_cb( deshn_being: var Deshn_Being) =
 echo "idle_and_added_to_deshn_pool_cb"

proc when_moving_state_action( deshn_being: var Deshn_Being) = discard 
 
proc after_moving_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_not_moving_state_action( deshn_being: var Deshn_Being) = discard 
 
proc after_not_moving_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_recovering_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_recovering_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_stopped_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_stopped_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_idle_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_idle_state_action( deshn_being: var Deshn_Being
                            ) = discard

proc when_deshn_pool_full_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_deshn_pool_full_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_deshn_pool_empty_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_deshn_pool_empty_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_deshn_pool_emptying_state_action( deshn_being: var Deshn_Being
                              ) = discard
proc after_deshn_pool_emptying_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_deshn_pool_filling_state_action( deshn_being: var Deshn_Being
                                   ) = discard
proc after_deshn_pool_filling_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc when_deshn_pool_exhausted_state_action( deshn_being: var Deshn_Being
                              ) = 
                              
 discard 

proc after_deshn_pool_exhausted_state_action( deshn_being: var Deshn_Being
                              ) = discard

proc add_default_transitions*(  being: var Deshn_Being
                             ) = 
 being.add_transition( Idle  
                     , Moved 
                     , Moving 
                     , idle_and_moved_cb
                     ) 
 
 being.add_transition( Not_Moving 
                     , Moved 
                     , Moving 
                     , not_moving_and_moved_cb
                     ) 
 
 being.add_transition( Moving  
                     , No_Action
                     , Not_Moving 
                     , moving_and_no_action_cb
                     ) 
 
 being.add_transition( Not_Moving
                     , No_Action
                     , Idle   
                     , stopped_and_no_action_cb
                     ) 
 
 being.add_transition( Deshn_Pool_Full 
                     , Removed_From_Deshn_Pool
                     , Deshn_Pool_Emptying 
                     , deshn_pool_full_and_taken_from_cb
                     ) 
 
 being.add_transition( Deshn_Pool_Empty  
                     , Added_To_Deshn_Pool
                     , Deshn_Pool_Filling 
                     , deshn_pool_empty_and_added_to_cb
                     ) 
 
 being.add_transition( Deshn_Pool_Filling   
                     , Added_To_Deshn_Pool
                     , Deshn_Pool_Filling  
                     , deshn_pool_filling_and_added_to_cb
                     ) 
 
 being.add_transition( Deshn_Pool_Emptying   
                     , Removed_From_Deshn_Pool
                     , Deshn_Pool_Empty 
                     , deshn_pool_emptying_and_taken_from_cb
                     )  
 
 being.add_transition( Deshn_Pool_Exhausted    
                     , No_Action
                     , Recovering 
                     , deshn_pool_exhausted_and_no_action_cb
                     ) 
 
 being.add_transition( Deshn_Pool_Exhausted    
                     , Added_To_Recovery_Pool
                     , Recovering 
                     , deshn_pool_exhausted_and_added_to_cb
                     ) 
 
 being.add_transition( Moving     
                     , Removed_From_Deshn_Pool
                     , Moving  
                     , moving_and_taken_from_pool_cb
                     ) 
 
 being.add_transition( Moving     
                     , Moved 
                     , Moving   
                     , moving_and_moved_cb
                     ) 

 being.add_transition( Moving     
                     , Stopped 
                     , Not_Moving   
                     , moving_and_moved_cb
                     ) 

 being.add_transition( Not_Moving     
                     , Added_To_Deshn_Pool
                     , Deshn_Pool_Filling
                     , not_moving_and_added_to_pool_cb
                     ) 

 being.add_transition( Deshn_Pool_Full
                     , Moved 
                     , Moving 
                     , deshn_pool_full_and_moved_cb
                     ) 
 
 being.add_transition( Moving     
                     , Added_To_Deshn_Pool
                     , Moving
                     , moving_and_added_to_pool_cb
                     ) 

 #[ being.add_transition( Deshn_Pool_Full 
                     , Moved 
                     , Deshn_Pool_Not_Full 
                     , deshn_pool_full_and_moved_cb
                     )  ]#
 
 #[ being.add_transition( Deshn_Pool_Not_Full 
                     , Removed_From_Deshn_Pool 
                     , Deshn_Pool_Emptying
                     , deshn_pool_not_full_and_removed_from_cb
                     )  ]#

 being.add_transition( Deshn_Pool_Emptying  
                     , Stopped 
                     , Not_Moving
                     , deshn_pool_empyting_and_stopped
                     ) 
 
 being.add_transition( Deshn_Pool_Filling
                     , Moved 
                     , Moving
                     , deshn_pool_filling_and_moved_cb
                     ) 
 
 being.add_transition( Moving   
                     , Exhausted_Deshn_Pool 
                     , Deshn_Pool_Exhausted 
                     , moving_and_exhausted_pool_cb
                     ) 
   
 being.add_transition( Deshn_Pool_Exhausted   
                     , No_Action 
                     , Recovering 
                     , exhausted_pool_and_no_action_cb
                     )  
 
 being.add_transition( Recovering   
                     , Added_To_Recovery_Pool
                     , Recovering 
                     , exhausted_pool_and_no_action_cb 
                     ) 
 
 being.add_transition( Idle   
                     , Added_To_Deshn_Pool
                     , Deshn_Pool_Filling
                     , idle_and_added_to_deshn_pool_cb
                     ) 
 
 being.add_transition( Recovering    
                     , Filled_Recovery_Pool
                     , Idle 
                     , filled_recovery_pool_and_now_idle_cb
                     ) 



proc add_default_state_actions*( being: var Deshn_Being
                               ) = 
 being.add_state_actions( Moving 
                        , entry_action = when_moving_state_action
                        , exit_action = after_moving_state_action
                        )
 
 being.add_state_actions( Recovering 
                        , entry_action = when_recovering_state_action
                        , exit_action = after_recovering_state_action
                        )
 
 being.add_state_actions( Not_Moving 
                        , entry_action = when_stopped_state_action
                        , exit_action = after_stopped_state_action
                        )
 
 being.add_state_actions( Idle 
                        , entry_action = when_idle_state_action
                        , exit_action = after_idle_state_action
                        )
 
#[  being.add_state_actions( Deshn_Pool_Exhausted
                        , entry_action = when_deshn_pool_exhausted_state_action
                        , exit_action = after_deshn_pool_exhausted_state_action
                        ) ]#
 
 being.add_state_actions( Deshn_Pool_Empty 
                        , entry_action = when_deshn_pool_empty_state_action
                        , exit_action = after_deshn_pool_empty_state_action
                        )
 
 being.add_state_actions( Deshn_Pool_Emptying
                        , entry_action = when_deshn_pool_emptying_state_action
                        , exit_action =  after_deshn_pool_emptying_state_action
                        )
 
 being.add_state_actions( Deshn_Pool_Filling
                        , entry_action = when_deshn_pool_filling_state_action
                        , exit_action = after_deshn_pool_filling_state_action
                        )
 
 being.add_state_actions( Deshn_Pool_Full 
                        , entry_action = when_deshn_pool_full_state_action
                        , exit_action = after_deshn_pool_full_state_action
                        )

 being.add_state_actions( Not_Moving 
                        , entry_action = when_not_moving_state_action
                        , exit_action = after_not_moving_state_action
                        )


proc a_deshn_being*( shape: Shape 
                   , name: string 
                   ): Deshn_Being = 

 result = Deshn_Being()
 result.shape = shape
 result.name = name  
 result.main_state_machine = a_state_machine[ Main_Deshn_Being_States
                                            , Main_Deshn_Being_Events
                                            ](Idle)
 
 result.add_default_transitions()
 result.add_default_state_actions()