(*  Finite State Machine for a Door 
        * A Door can have 4 states: Closed, Open, Opening and Closing
        * When a Door is Closed, it remains Closed until a mouse button is clicked.
        * Then it starts Opening. It takes 3 seconds to open fully.
        * When the Door is Open, it remains open for 5 seconds
        * then it starts Closing. It takes 3 seconds to close fully.
*)

type tstate = OPEN | CLOSED | OPENING | CLOSING

(*         General helper function: returns a countdown-timer function for a given duration.
        This timer function starts running at the moment of creation (and not at the
        moment of first invocation, so it should be created just prior to invocation.
        It returns a number in the interval [0 .. 1] : 1 immediately after creation,
        a fraction between 0 and 1 proportional to the elapsed time if the duration
        has not been exceeded, and 0 thereafter.
*)
let make_timer duration =
        let t_end = duration +. Unix.gettimeofday () in
        fun () -> let t = Unix.gettimeofday () in
                if t < t_end then (t_end -. t) /. duration else 0.0  
        
(*        The first type of input: an automatic countdown timer.
        It returns true when the timer has reached 0, false otherwise
*)        
let input_from_timer timer =
        fun () -> if timer () = 0.0 then true else false        

(*        The second type of input : IO
        It returns true when a mouse button has been pressed, false otherwise
*)
let input_from_mouse () =
        Graphics.button_down ()

(*        To display a given state we need an output function.
        For this we use the degree to which the Door is open.
        A completely open door corresponds to 1.0, a completely 
        closed door to 0.0, and opening or closing doors take
        on proportionally intermediate values. For the intermediate
        values we can conveniently use the timer functions,
        because the extent to which a door is opened or closed
        depends on how much time has elapsed during opening or closing.

        The first output function is the timer itself.
        We can use the timer's output to represent a closing door.
        Both timer and closing door progress from 1.0 to 0.0
        
        opening_door () is the complement of the output of the timer.
        We can use this value to represent an opening door
        We need the complement because the timer progresses from 1.0 to 0.0,
        while a door opening progresses from 0.0 to 1.0
        
        closed_door () represents a closed door.
        This is 0.0
        
        open_door () represents an open door.
        This is 1.0
*)
let opening_door timer =
        fun () -> 1.0 -. timer ()

let closed_door () = 0.0

let open_door () = 1.0

(* The FSM transition function for a Door *)
let transition (state, input, output) = 
        match (state, input ()) with
        | OPEN, true                 -> let timer = make_timer 3.0 in (CLOSING, input_from_timer timer, timer)
        | CLOSING, true          -> (CLOSED, input_from_mouse, closed_door)
        | CLOSED, true                 -> let timer = make_timer 3.0 in (OPENING, input_from_timer timer, opening_door timer) 
        | OPENING, true                -> let timer = make_timer 5.0 in (OPEN, input_from_timer timer, open_door)
        | _        , _                                -> (state, input, output)                                


(******************** test FSM of Door **********************)

let rec loop s f =
        let (state, input, output) = transition s in
        let _ = f output in
        loop (state, input, output) f

let draw_door output =
        let trunc x = if x < 0.0 then 0.0 else if x > 1.0 then 1.0 else x in
        let gap = trunc (output ()) in
        let width = Graphics.size_x () and height = Graphics.size_y() in
        let w = width / 4 and h = height / 4 in
        let w2 = (truncate ((1.0 -. gap) *. (float (w*2)))) in
        let _ = Graphics.auto_synchronize false in
        let _ = Graphics.set_color Graphics.red in
        let _ = Graphics.fill_rect w h w2 (h*2) in
        let _ = Graphics.set_color Graphics.white in
        let _ = Graphics.fill_rect (w + w2) h (2*w - w2) (h*2) in 
        let _ = Graphics.set_color Graphics.black in
        let _ = Graphics.draw_rect w h (w*2) (h*2) in
        let _ = Graphics.draw_segments [|(0,h,width,h); (w + w2, h, w+w2, h*3)|] in
        let _ = Graphics.auto_synchronize true in 
        ()        
        

let width = 640
let height = 480

let main () =
        Graphics.open_graph (Printf.sprintf " %dx%d" width height);
        let _ = loop (CLOSED, input_from_mouse, closed_door) draw_door in
        ()
        
let _ = main ()