module Setup
  module Bindings
    extend ActiveSupport::Concern

    included do
      after_destroy :clear_bindings
    end

    def bind_bindings
      self.class.binds.each do |metadata|
        Setup::Binding.bind(self, send(metadata.name), metadata.klass)
      end
      true
    end

    def clear_bindings
      Setup::Binding.clear(self)
    end

    module ClassMethods
      def instantiate(attrs = nil, selected_fields = nil, &block)
        super(attrs, selected_fields) do |doc|
          binds.each do |metadata|
            key = metadata.foreign_key
            doc.instance_variable_set(:"@_binding_shadow_#{key}", doc.attributes[key])
            bind, id = Setup::Binding.bind_bind_id_for(doc, metadata.klass)
            if bind
              doc.instance_variable_set(:"@_#{key}_binding", bind)
              doc.attributes[key] = bind[id]
            end
          end
          block.call(doc) if block
        end
      end

      def local_binds
        @binds ||= []
      end

      def binds
        if superclass < Bindings
          superclass.binds
        else
          []
        end + local_binds
      end

      def binding_belongs_to(name, *options)
        relation = belongs_to(name, *options)
        local_binds << relation
        relation
      end

      def where(expression)
        ids = nil
        if expression.is_a?(Hash)
          binds.each do |metadata|
            c_str = expression.delete(metadata.name.to_s)
            c_sym = expression.delete(metadata.name.to_sym)
            if (c = c_sym || c_str).is_a?(metadata.klass)
              ids ||= Set.new
              ids.merge Setup::Binding.where(Setup::Binding.bind_id(metadata.klass) => c.id).collect(&Setup::Binding.binder_id(self).to_sym)
            end
            c_str = expression.delete(metadata.foreign_key.to_s)
            c_sym = expression.delete(metadata.foreign_key.to_sym)
            if (c = c_sym || c_str)
              ids ||= Set.new
              ids.merge Setup::Binding.where(Setup::Binding.bind_id(metadata.klass) => c).collect(&Setup::Binding.binder_id(self).to_sym)
            end
          end
        end
        q = super
        if ids
          q = q.and(:id.in => ids.to_a)
        end
        q
      end
    end
  end
end
