
require 'thread'
require 'observer'

module PrintfDebugger
    attr :enable_printf_debug
    def pdbg(str)
        p(str) if @enable_printf_debug
    end
end

class MachineState

    include Observable
    include PrintfDebugger

    DEFAULT_ACTION  = proc{|evt,state| print "default action\n"}
    NO_ACTION       = proc{|evt,state| }

    def initialize(name)
        @name = name
        @listeners = []
        @on_entry  = proc {|state|  }
        @on_exit   = proc {|state|  }
        # all events in format [[string,action],[str2,action2]...]
        @all_events= [ ["default", DEFAULT_ACTION] ]
        
        # for now, enable tracing
        @enable_printf_debug = true
    end

    # The following methods are called by the state machine
    def on_entry
        pdbg "Entering state #{name}"
        @on_entry.call(self)
        changed()
        notify_observers("entered state #{name}")
    end
    def on_exit
        pdbg "Exiting state #{name}"
        @on_exit.call(self)
    end
    def on_event(evt)
        ea = @all_events.assoc(evt)
        ea[1].call(evt,self)  if  ea != nil
    end

    # This state cannot change the current state of the state machine.
    # It has to send an event to the machine.
    def gen_event(evt)
        @machine.on_event(evt)
    end

    attr_writer  :on_exit, :on_entry    #proc objects
    attr_accessor:all_events, :name, 
                :machine,       # set by the machine that owns this state
                :debug
end




class StateMachine

    include PrintfDebugger

    def initialize(name,states)
        @name       = name
        @states     = states
        # Tell all states who runs the machine, so that they can send events too
        @states.each{|state| state.machine = self }
        @current_state = @states[0]
        @terminate = false
        @queue      = Queue.new
        @thread     = nil
        
        # for now, enable tracing
        @enable_printf_debug = true
    end
    
    def on_event(event)
        pdbg "StateMachine.on_event(#{event})"
        @queue.push(event)
    end
    
    def terminate
        @terminate = true
    end
    def run
        @terminate = false
        if @thread == nil
            @thread = Thread.new() { run_proc }
        else
            @thread.run
        end
        self
    end
    
    def run_proc
        pdbg "StateMachine #{@name} run_proc"
        @current_state.on_entry()
        while ! @terminate do
            msg = @queue.pop
            next_state = @current_state.on_event(msg)
            if next_state 
                @current_state.on_exit()
                @current_state.notify_observers("exiting")
                
                @current_state = next_state
                print "swithed to state #{@current_state.name}\n"
                
                @current_state.on_entry()
                @current_state.notify_observers("entering")
            end
        end
        @current_state.on_exit()
        Thread.stop
    end

    
    attr_reader :thread
end

