module EnumeratedAttribute

	class MethodDefinition
    attr_accessor :method_name, :negated, :argument    
    
    def initialize(name, arg, negated=false)
      @method_name, @negated, @argument = name, negated, arg
    end
    
    def is_predicate_method?; @method_name[-1, 1] == '?'; end
    def has_method_name?; !!@method_name; end
    def has_argument?; !!@argument; end
  end
  
  class MethodDefinitionDSL
    attr_reader :initial_value, :pluralized_name, :decrementor_name, :incrementor_name
    
    def initialize(class_obj, descriptor)
      @class_obj = class_obj
      @attr_name = descriptor.name
      @attr_descriptor = descriptor #this is the enums array
    end
    
    #we'll by pass this - they can use it if it helps make code more readable - not enforced - should it be??
    def define
    end
    
    def is_not(*args)
      arg = args.first if args.length > 0
      MethodDefinition.new(nil, arg, true)
    end
    alias :isnt :is_not
    
    def is(*args)
      arg = args.first if args.length > 0
      MethodDefinition.new(nil, arg)
    end

    def method_missing(methId, *args, &block)
      meth_name = methId.id2name
      
      meth_def = nil
      if args.size > 0
        arg = args.first
        if arg.instance_of?(EnumeratedAttribute::MethodDefinition)
          if arg.has_method_name?
            raise_method_syntax_error(meth_name, arg.method_name)
          end
          meth_def = arg
          meth_def.method_name = meth_name
        else
          meth_def = MethodDefinition.new(meth_name, arg)
        end
      elsif block_given?
        meth_def = MethodDefinition.new(meth_name, block)
      else
        raise_method_syntax_error(meth_name)
      end
      evaluate_method_definition(meth_def)
    end              
     
    def init(value)
      if (!@attr_descriptor.empty? && !@attr_descriptor.include?(value.to_sym))
        raise(InvalidDefinition, "'#{value}' in method 'init' is not an enumeration value for :#{@attr_name} attribute", caller) 
      end
      @initial_value = value
    end

    def decrementor(value); @decrementor_name = value; end
    def incrementor(value); @incrementor_name = value; end    
    def enums_accessor(value); @pluralized_name = value; end
    alias_method :inc, :incrementor
    alias_method :dec, :decrementor
    alias_method :enums, :enums_accessor
    alias_method :plural, :enums_accessor
		
		def label(hash)
			raise(InvalidDefinition, "label or labels keyword should be followed by a hash of :enum_value=>'label'", caller) unless hash.is_a?(Hash)
			hash.each do |k,v|
				raise(InvalidDefinition, "#{k} is not an enumeration value for :#{@attr_name} attribute", caller) unless (k.is_a?(Symbol) && @attr_descriptor.include?(k))
				raise(InvalidDefinition, "#{v} is not a string. Labels should be strings", caller) unless v.is_a?(String)
				@attr_descriptor.set_label(k, v)
			end
		end
		alias_method :labels, :label
    
    private
    
    def raise_method_syntax_error(meth_name, offending_token=nil)
      suffix = offending_token ? "found '#{offending_token}'" : "found nothing" 
      followed_by = (meth_name[-1,1] == '?' ? "is_not, an enumeration value, an array of enumeration values, " : "") + "a proc, lambda or code block"
      raise InvalidDefinition, "'#{meth_name}' should be followed by #{followed_by} -- but #{suffix}"
    end
        
    def evaluate_method_definition(mdef)
      unless mdef.has_argument?
        return raise_method_syntax_error(mdef.method_name)
      end
      
      if mdef.is_predicate_method?
        case mdef.argument
          when String
            return create_custom_method_for_symbol_or_string(mdef)
          when Symbol
            return create_custom_method_for_symbol_or_string(mdef)
          when Array
            return create_custom_method_for_array_of_enums(mdef)
          when Proc
            return create_custom_method_for_proc(mdef)            
        end
      else #action method
        if mdef.argument.instance_of?(Proc)
          return create_custom_method_for_proc(mdef)
        end
      end
      raise_method_syntax_error(mdef.method_name, mdef.argument)
    end
    
    def create_custom_method_for_proc(mdef)
      @class_obj.send(:define_method, mdef.method_name, mdef.argument)
    end
    
    def create_custom_method_for_symbol_or_string(mdef)
      if (!@attr_descriptor.empty? && !@attr_descriptor.include?(mdef.argument.to_sym))
        raise(InvalidDefinition, "'#{mdef.argument}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller) 
      end
      @class_obj.class_eval("def #{mdef.method_name}; self.#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end") 
    end

    def create_custom_method_for_array_of_enums(mdef)
      if !@attr_descriptor.empty?
        mdef.argument.each do |m|
          if !@attr_descriptor.include?(m.to_sym)
            raise(InvalidDefinition, "'#{m}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller) 
          end
        end
      end
      @class_obj.class_eval("def #{mdef.method_name}; #{mdef.negated ? '!' : ''}[:#{mdef.argument.join(',:')}].include?(self.#{@attr_name}); end")
    end
  end
	
end
