module Fattr
  require_relative 'fattr/_lib.rb'

  SEMAPHORE = Mutex.new

  class List < ::Array
    attr_accessor :object

    def initialize(*args, &block)
      super(*args, &block)
    end

    def << element
      super
      self
    ensure
      uniq!
      index!
    end

    def index!
      @index ||= Hash.new
      each{|element| @index[element.to_s] = true}
    end

    def include?(element)
      @index ||= Hash.new
      @index[element.to_s] ? true : false
    end

    def initializers
      @initializers ||= Hash.new
    end

    def to_hash
      if @object
        list = @object.class.fattrs + @object.fattrs
        list.inject(Hash.new){|hash, fattr| hash.update(fattr => @object.send(fattr))}
      end
    end

    def to_h
      to_hash
    end

    def for(object)
      @object = object
      self
    end
  end

  class Result < ::Hash
    attr_accessor :object

    def for(object)
      @object = object
      self
    end
  end

  def fattrs(*args, &block)
    unless args.empty?
      returned = Fattr::Result.new

      args.flatten!
      args.compact!

      all_hashes = args.all?{|arg| Hash===arg}

      names_and_configs = {}

      if all_hashes
        args.each do |hash|
          hash.each do |key, val|
            name = key.to_s
            config = Hash===val ? val : {:default => val}
            names_and_configs[name] = config
          end
        end
      else
        config = Hash===args.last ? args.pop : {}
        names = args.select{|arg| Symbol===arg or String===arg}.map{|arg| arg.to_s}
        names.each do |name|
          names_and_configs[name] = config
        end
      end

      #initializers = __fattrs__.initializers

      names_and_configs.each do |name, _config|
        raise(NameError, "bad instance variable name '@#{ name }'") if("@#{ name }" =~ %r/[!?=]$/o)

        name = name.to_s

        default = nil
        default = _config[:default] if _config.has_key?(:default)
        default = _config['default'] if _config.has_key?('default')

        inheritable = false
        if Module===self
          inheritable = _config[:inheritable] if _config.has_key?(:inheritable)
          inheritable = _config['inheritable'] if _config.has_key?('inheritable')
        end

        initialize = (
          if inheritable
            lambda do |*ignored|
              parents = ancestors[1..-1]
              catch(:value) do
                parents.each do |parent|
                  throw(:value, parent.send(name)) if parent.respond_to?(name)
                end
                block ? block.call : default
              end
            end
          else
            block || lambda{|*ignored| default }
          end
        )

        initializer = lambda do |this|
          Object.instance_method('instance_eval').bind(this).call(&initialize)
        end

        #initializer_id = initializer.object_id

        __fattrs__.initializers[name] = initializer

        compile = lambda do |code|
          begin
            module_eval(code)
          rescue SyntaxError
            raise(SyntaxError, "\n#{ code }\n")
          end
        end

      # setter, block invocation caches block
        code = <<-code
          def #{ name }=(*value, &block)
            value.unshift block if block
            @#{ name } = value.first
          end
        code
        compile[code]

      # getter, providing a value or block causes it to acts as setter
        code = <<-code
          def #{ name }(*value, &block)
            value.unshift block if block
            return self.send('#{ name }=', value.first) unless value.empty?
            #SEMAPHORE.synchronize{ #{ name }! unless defined? @#{ name } }
            #{ name }! unless defined? @#{ name }
            @#{ name }
          end
        code
        compile[code]

      # bang method re-calls any initializer given at declaration time
=begin
        code = <<-code
          def #{ name }!
            initializer = ObjectSpace._id2ref #{ initializer_id }
            self.#{ name } = initializer.call(self)
            @#{ name }
          end
        code
        compile[code]
=end

        module_eval do
          define_method :"#{ name }!" do
            self.send :"#{ name }=", initializer.call(self)
            self.instance_variable_get :"@#{name}"
          end
        end

      # query simply defers to getter - cast to bool
        code = <<-code
          def #{ name }?
            self.#{ name }
          end
        code
        compile[code]

        fattrs << name
        returned[name] = initializer
      end

      returned
    else
      begin
        __fattr_list__
      rescue NameError, TypeError
        singleton_class =
          class << self
            self
          end
        klass = self
        singleton_class.module_eval do
          fattr_list = List.new
          define_method('fattr_list'){ klass == self ? fattr_list : raise(NameError) }
          alias_method '__fattr_list__', 'fattr_list'
        end
        __fattr_list__
      end
    end
  end

  %w( __fattrs__ __fattr__ fattr ).each{|dst| alias_method(dst, 'fattrs')}
end

class Module
  include Fattr

  def Fattrs(*args, &block)
    class << self
      self
    end.module_eval{ __fattrs__(*args, &block) }.for(self)
  end

  def Fattr(*args, &block)
    class << self
      self
    end.module_eval{ __fattr__(*args, &block) }.for(self)
  end
end

class Object
  def fattrs(*args, &block)
    class << self
      self
    end.__fattrs__(*args, &block).for(self)
  end
  %w( __fattrs__ __fattr__ fattr ).each{|dst| alias_method(dst, 'fattrs')}
end
