module DBViewCTI
  module Model
    module CTI
      extend ActiveSupport::Concern
      
      def specialize
        class_name, id = type(true)
        return self if class_name == self.class.name
        class_name.constantize.find(id)
      end
      
      # Return the 'true' (i.e. most specialized) classname of this object
      # When return_id is true, the 'specialized' database id is also returned
      def type(return_id = false)
        query, levels = self.class.cti_outer_join_sql(id)
        result = self.class.connection.execute(query).first
        # replace returned ids with the levels corresponding to their classes
        result_levels = result.inject({}) do |hash, (k,v)|
          hash[k] = levels[k] unless v.nil?
          hash
        end
        # find class with maximum level value
        foreign_key = result_levels.max_by { |k,v| v }.first
        class_name = DBViewCTI::Names.table_to_class_name(foreign_key[0..-4])
        if return_id
          id_ = result[foreign_key].to_i
          [class_name, id_]
        else
          class_name
        end
      end
      
      def convert_to(type)
        type_string = type.to_s
        type_string = type_string.camelize if type.is_a?(Symbol)
        return self if type_string == self.class.name
        query = self.class.cti_inner_join_sql(id, type_string)
        # query is nil when we try to cenvert to an descendant class (instead of an ascendant),
        # or when we try to convert to a class outside of the hierarchy
        if query.nil?
          specialized = specialize
          return nil if specialized == self
          return specialized.convert_to(type_string)
        end
        result = self.class.connection.execute(query).first
        id = result[ DBViewCTI::Names.foreign_key(type.to_s) ]
        return nil if id.nil?
        type_string.constantize.find(id.to_i)
      end
      
      # change destroy and delete methods to operate on most specialized obect
      included do
        alias_method_chain :destroy, :specialize
        alias_method_chain :delete, :specialize
        # destroy! seems te be defined in Rails 4
        alias_method_chain :destroy!, :specialize if self.method_defined?(:destroy!) 
      end
      
      def destroy_with_specialize
        specialize.destroy_without_specialize
      end
      
      def destroy_with_specialize!
        specialize.destroy_without_specialize!
      end
      
      def delete_with_specialize
        specialize.delete_without_specialize
      end
      
      module ClassMethods
        
        def cti_base_class?
          !!@cti_base_class
        end
  
        def cti_derived_class?
          !!@cti_derived_class
        end
        
        attr_accessor :cti_descendants, :cti_ascendants
        
        # registers a derived class and its descendants in the current class
        # class_name: name of derived class (the one calling cti_register_descendants on this class)
        # descendants: the descendants of the derived class
        def cti_register_descendants(class_name, descendants = {})
          @cti_descendants ||= {}
          @cti_descendants[class_name] = descendants
          if cti_derived_class?
            # call up the chain. This will also cause the register_ascendants callbacks
            self.superclass.cti_register_descendants(self.name, @cti_descendants)
          end
          # call back to calling class
          @cti_ascendants ||= []
          class_name.constantize.cti_register_ascendants(@cti_ascendants + [ self.name ])
        end
        
        # registers the ascendants of the current class. Called on this class by the parent class.
        # ascendants: array of ascendants. The first element is the highest level class, derived
        # classes follow, the last element is the parent of this class.
        def cti_register_ascendants(ascendants)
          @cti_ascendants = ascendants
        end
        
        # returns a list of all descendants
        def cti_all_descendants
          result = []
          block = Proc.new do |klass, descendants|
            result << klass
            descendants.each(&block)
          end
          @cti_descendants ||= {}
          @cti_descendants.each(&block)
          result
        end      
        
        include DBViewCTI::SQLGeneration::Model
        
        # this method is only used in testing. It returns the number of rows present in the real database
        # table, not the number of rows present in the view (as returned by count)
        def cti_table_count
          result = connection.execute("SELECT COUNT(*) FROM #{DBViewCTI::Names.table_name(self)};")
          result[0]['count'].to_i
        end
        
      end
    end
  end
end