require 'thread'
require 'singleton'

module Mauve

  class MauveThread

    attr_reader :poll_every

    def initialize
      @thread = nil
    end

    def logger
      @logger ||= Log4r::Logger.new(self.class.to_s) 
    end

    def poll_every=(i)
      raise ArgumentError.new("poll_every must be numeric") unless i.is_a?(Numeric)
      # 
      # Set the minimum poll frequency.
      #
      if i.to_f < 0
        logger.debug "Increasing thread polling interval to 0s from #{i}"
        i = 0 
      end

      @poll_every = i
    end

    def run_thread(interval = 5.0)
      #
      # Good to go.
      #
      @thread = Thread.current
      self.state = :starting

      @poll_every ||= interval
      #
      # Make sure we get a number.
      #
      @poll_every = 5 unless @poll_every.is_a?(Numeric)

      rate_limit = 0.1

      while self.state != :stopping do

        self.state = :started if self.state == :starting

        #
        # Schtop!
        #
        if self.state == :freezing
          self.state = :frozen
          Thread.stop
          self.state = :started
        end

        yield_start = Time.now.to_f

        yield

        #
        # Ah-ha! Sleep with a break clause.  Make sure we poll every @poll_every seconds.
        #
        ((@poll_every.to_f - Time.now.to_f + yield_start.to_f)/rate_limit).
          round.to_i.times do

          break if self.should_stop?

          #
          # This is a rate-limiting step
          #
          Kernel.sleep rate_limit
        end
      end

      self.state = :stopped
    end

    def should_stop?
      [:freezing, :stopping].include?(self.state)
    end

    def state
      if self.alive?
        @thread.key?(:state) ?  @thread[:state] : :unknown
      else
        :stopped
      end
    end

    def state=(s)
      raise "Bad state for mauve_thread #{s.inspect}" unless [:stopped, :starting, :started, :freezing, :frozen, :stopping, :killing, :killed].include?(s)
      raise "Thread not ready yet." unless @thread.is_a?(Thread)

      unless @thread[:state] == s
        @thread[:state] = s
        @thread[:last_state_change] = Time.now
        logger.debug(s.to_s.capitalize) 
      end

      @thread[:state]
    end

    def last_state_change
      if self.alive? and @thread.key?(:last_state_change)
        return @thread[:last_state_change]
      else
        return nil
      end
    end

    def freeze
      self.state = :freezing
      
      20.times { Kernel.sleep 0.2 ; break if @thread.stop? }

      logger.warn("Thread has not frozen!") unless @thread.stop?
    end

    def frozen?
      self.stop? and self.state == :frozen
    end

    def run
      if self.alive? 
        # Wake up if we're stopped.
        if self.stop?
          @thread.wakeup 
        end
      else
        @logger = nil
        Thread.new do
          self.run_thread { self.main_loop } 
        end
      end
    end

    alias start run
    alias thaw  run

    def alive?
      @thread.is_a?(Thread) and @thread.alive?
    end

    def stop?
      self.alive? and @thread.stop?
    end

    def join(ok_exceptions=[])
      @thread.join if @thread.is_a?(Thread)
    end

#    def raise(ex)
#      @thread.raise(ex)
#    end

    def backtrace
      @thread.backtrace if @thread.is_a?(Thread)
    end

    def restart
      self.stop
      self.start
    end
    
    def stop
      self.state = :stopping

      10.times do 
        break unless self.alive?
        Kernel.sleep 1 if self.alive? 
      end

      #
      # OK I've had enough now.
      #
      self.kill if self.alive?

      self.join 
    end

    alias exit stop

    def kill
      self.state = :killing
      @thread.kill
      self.state = :killed
    end

    def thread
      @thread
    end

  end

end