%w[lifecycle actions creator state transition].each { |lib| require "hobo/lifecycles/#{lib}" }

module Hobo

  module Lifecycles

    class LifecycleError < RuntimeError; end

    class LifecycleKeyError < LifecycleError; end

    ModelExtensions = classy_module do

      attr_writer :lifecycle

      def self.lifecycle(*args, &block)
        options = args.extract_options!
        options = options.reverse_merge(:state_field => :state,
                                        :key_timestamp_field => :key_timestamp)

        if defined? self::Lifecycle
          lifecycle = self::Lifecycle
        else
          module_eval "class ::#{name}::Lifecycle < Hobo::Lifecycles::Lifecycle; end"
          lifecycle = self::Lifecycle
          lifecycle.init(self, options)
        end

        dsl = DeclarationDSL.new(lifecycle)
        dsl.instance_eval(&block)

        default = lifecycle.initial_state ? { :default => lifecycle.initial_state.name } : {}
        declare_field(options[:state_field], :string, default)

        declare_field(options[:key_timestamp_field], :datetime)

        never_show      options[:state_field], options[:key_timestamp_field]
        attr_protected  options[:state_field], options[:key_timestamp_field]
      end


      def self.has_lifecycle?
        defined?(self::Lifecycle)
      end


      def lifecycle
        @lifecycle ||= self.class::Lifecycle.new(self)
      end


      def become(state)
        self.lifecycle.state = state
      end

    end


    class DeclarationDSL

      def initialize(lifecycle)
        @lifecycle = lifecycle
      end

      def state(*names, &block)
        names.map {|name| @lifecycle.def_state(name, block) }
      end

      def initial_state(name, &block)
        s = @lifecycle.def_state(name, block)
        @lifecycle.initial_state = s
      end

      def create(who, name, options={}, &block)
        @lifecycle.def_creator(name, who, block, options)
      end

      def transition(who, name, change, options={}, &block)
        @lifecycle.def_transition(name, who,
                                  Array(change.keys.first), change.values.first,
                                  block, options)
      end

      def invariant(&block)
        @lifecycle.invariants << block
      end

      def precondition(&block)
        @lifecycle.preconditions << block
      end

    end

  end
end