module MooseX
  module AttributeModifiers
    module ThirdParty
    end
    module AttrBaseModifier
      def initialize(attr)
        @attr=attr
        @attr_symbol = attr.attr_symbol
      end
      
      def prepare(options) 
        # TODO! move logic to here
      end
      
      def process(options)
        local_options = { 
          name => default 
        }.merge(options)
        
        unless local_options.has_key?(name)
          @attr.attribute_map[name]=nil 
          return
        end
        options.delete(name)

        attr = local_options[name]
        attr = coerce(attr, @attr_symbol)
        validate(attr, @attr_symbol)

        attr = update_options(options, name, attr)

        @attr.attribute_map[name] = attr
      end

      def name; self.class.name.downcase.split("::").last.to_sym ; end
      def default; nil; end
      def coerce(x,f); x ; end
      def validate(x,f);  end
      def update_options(options, name, attr); attr; end      
    end

    module AttrCoerceToBoolean
      def coerce(x, f)
        !! x
      end
    end

    module AttrCoerceToSymbol
      def coerce(x, f)
        x.to_sym
      end  
    end

    module AttrCoerceMethodToLambda
      def coerce(x, field_name)
        unless x.is_a? Proc
          x_name = x.to_sym
          x = ->(object, *value) do
            object.__send__(x_name,*value)
          end
        end

        x       
      end
    end

    module AttrCoerceToString
      def coerce(x, f)
        x.to_s
      end  
    end

    class Is 
      include AttrBaseModifier
      include AttrCoerceToSymbol
      def default; :rw ; end
      def validate(is, field_name) 
        unless [:rw, :rwp, :ro, :lazy, :private].include?(is)
          raise InvalidAttributeError, "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy"  
        end
      end
      def update_options(options, name, attr)
        if attr == :lazy
          attr = :ro
          options[:lazy] = true
        end

        if attr == :ro
          options[:writter] = nil
        end

        attr
      end         
    end

    class Isa 
      include AttrBaseModifier
      def default; MooseX::Attribute.isAny ; end
      def coerce(isa, field_name); MooseX::Attribute.isType(isa); end
    end

    class Default
      include AttrBaseModifier
      def coerce(default, field_name)
        if default.is_a?(Proc) || default.nil?
          return default 
        end  
        return -> { default }         
      end
    end

    class Required
      include AttrBaseModifier
      include AttrCoerceToBoolean
    end

    module AttrCoerceToMethodBasedOnFieldName
      def coerce(x, field_name) 
        if ! x
          return x
        elsif x.is_a? TrueClass
          return method_name(field_name)
        end
    
        begin
          x.to_sym
        rescue => e
          # create a nested exception here
          raise InvalidAttributeError, "cannot coerce field #{name} to a symbol for #{field_name}: #{e}"
        end
      end
    end

    class Predicate
      include AttrBaseModifier
      include AttrCoerceToMethodBasedOnFieldName

      def method_name(field_name); "has_#{field_name}?".to_sym ; end
    end       

    class Clearer
      include AttrBaseModifier
      include AttrCoerceToMethodBasedOnFieldName

      def method_name(field_name); "clear_#{field_name}!".to_sym ; end
    end

    class Handles
      include AttrBaseModifier
      def default; {} ; end

      def populate_handles(handles, field_name)
        array_of_handles = handles

        unless array_of_handles.is_a? Array
          array_of_handles = [ array_of_handles ]
        end

        handles = array_of_handles.map do |handle|

          if handle == BasicObject
            
            raise InvalidAttributeError, "ops, should not use BasicObject for handles in #{field_name}"
          
          elsif handle.is_a? Class

            handle = handle.public_instance_methods - handle.superclass.public_instance_methods
          
          elsif handle.is_a? Module
            
            handle = handle.public_instance_methods

          end
          
          handle

        end.flatten.reduce({}) do |hash, method_name|
          hash.merge({ method_name => method_name })
        end

        handles
      end

      def coerce(handles, field_name)

        unless handles.is_a? Hash 
          handles = populate_handles(handles, field_name)
        end

        handles.map do |key,value|
          if value.is_a? Hash
            raise "ops! Handle should accept only one map / currying" unless value.count == 1
            
            original, currying = value.shift
  
            { key.to_sym => [original.to_sym, currying] }
          else
            { key.to_sym => value.to_sym }
          end
        end.reduce({}) do |hash,e| 
          hash.merge(e)
        end
      end
    end

    class Lazy 
      include AttrBaseModifier
      include AttrCoerceToBoolean
    end

    class Reader
      include AttrBaseModifier
      include AttrCoerceToSymbol 
      def default
        @attr_symbol
      end
    end

    class Writter
      include AttrBaseModifier
      
      def default
        @attr_symbol.to_s.concat("=").to_sym
      end
      def coerce(writter, field_name)
        return writter if writter.nil?

        writter.to_sym
      end
    end

    class Builder
      include AttrBaseModifier
      include AttrCoerceMethodToLambda

      def default
        "build_#{@attr_symbol}".to_sym
      end
    end

    class Init_arg
      include AttrBaseModifier
      include AttrCoerceToSymbol

      def default
        @attr_symbol
      end
    end
      

    class Trigger
      include AttrBaseModifier
      include AttrCoerceMethodToLambda
      
      def default
        ->(object, value) { }
      end
    end

    class Coerce 
      include AttrBaseModifier
      include AttrCoerceMethodToLambda

      def default
        ->(object) {object}
      end

      def update_options(options, name, attr)
        if options[:weak]
          old_coerce = attr
          attr = ->(value) do
            WeakRef.new old_coerce.call(value)
          end
        end

        attr
      end
    end

    class Weak
      include AttrBaseModifier
      include AttrCoerceToBoolean
    end

    class Doc
      include AttrBaseModifier
      include AttrCoerceToString
    end

    class Override
      include AttrBaseModifier
      include AttrCoerceToBoolean
    end

    class Traits
      include AttrBaseModifier
      def default; [] ; end
      def coerce(traits,f)
        original = ->(value) { value }

        [traits].flatten.inject(original) do |inner, trait|
          ->(value) { trait.new inner.call value  }
        end
      end
      def validate(traits,f)
        # TODO
      end
    end
  end
end