module EnumeratedAttribute
  module Attribute
    module InstanceMethods
      def self.included(base)

        method_missing_suffix = "enumerated_attribute_#{base.name}_#{base.hash}".to_sym
        define_method("method_missing_with_#{method_missing_suffix}") do |methId, *args|
          return self.__send__(methId) if define_enumerated_attribute_dynamic_method(methId)
          self.__send__("method_missing_without_#{method_missing_suffix}", methId, *args)
        end

        respond_to_suffix = "enumerated_attribute_#{base.name}_#{base.hash}".to_sym
        base.class_eval %{
          def respond_to_with_#{respond_to_suffix}?(method, include_private=false)
            self.respond_to_without_#{respond_to_suffix}?(method, include_private) ||
              (!!parse_dynamic_method_parts!(method.to_s) rescue false)
          end
        }

        base.safe_alias_method_chain :method_missing, method_missing_suffix
        base.safe_alias_method_chain :respond_to?, respond_to_suffix

      end

      def enums(attr)
        self.class.enumerated_attributes[attr.to_sym]
      end


      def initialize_enumerated_attributes(only_if_nil = false)
        self.class.enumerated_attributes.each do |k,v|
          self.write_enumerated_attribute(k, v.init_value) unless (only_if_nil && read_enumerated_attribute(k) != nil)
        end
      end

      private

      def parse_dynamic_method_parts!(meth_name)
        return(nil) unless meth_name[-1, 1] == '?'

        middle = meth_name.chop #remove the ?

        attr = nil
        self.class.enumerated_attributes.keys.each do |name|
          if middle.sub!(Regexp.new("^"+name.to_s), "")
            attr = name; break
          end
        end

        value = nil
        attr_sym = attr ? attr.to_sym : nil
        if (descriptor = self.class.enumerated_attributes[attr_sym])
          descriptor.enums.each do |v|
            if middle.sub!(Regexp.new(v.to_s+"$"), "")
              value = v; break
            end
          end
        else
          #search through enum values one at time and identify any ambiguities
          self.class.enumerated_attributes.each do |attr_key,descriptor|
            descriptor.enums.each do|v|
              if middle.match(v.to_s+"$")
                raise(AmbiguousMethod, meth_name+" is ambiguous, use something like "+attr_sym.to_s+(middle[0,1]=='_'? '' : '_')+middle+"? or "+attr_key.to_s+(middle[0,1]=='_'? '' : '_')+middle+"?", caller) if attr_sym
                attr_sym = attr_key
                value = v
              end
            end
          end
          return (nil) unless attr_sym
          attr = attr_sym.to_s
          middle.sub!(Regexp.new(value.to_s+"$"), "")
        end

        unless value #check for nil?
          return (nil) unless middle.sub!(Regexp.new('nil$'), "")
          value = nil
        end

        [attr, middle, value]
      end

      def define_enumerated_attribute_dynamic_method(methId)
        meth_name = methId.id2name
        parts = parse_dynamic_method_parts!(meth_name)
        return(false) unless parts

        negated = !!parts[1].downcase.match(/(^|_)not_/)
        value = parts[2] ? parts[2].to_sym : nil
        self.class.define_enumerated_attribute_custom_method(methId, parts[0], value, negated)

        true
      end

    end
  end
end