require 'thinking_sphinx/source/internal_properties'
require 'thinking_sphinx/source/sql'

module ThinkingSphinx
  class Source
    include ThinkingSphinx::Source::InternalProperties
    include ThinkingSphinx::Source::SQL
    
    attr_accessor :model, :fields, :attributes, :conditions, :groupings,
      :options
    attr_reader :base, :index
    
    def initialize(index, options = {})
      @index        = index
      @model        = index.model
      @fields       = []
      @attributes   = []
      @conditions   = []
      @groupings    = []
      @options      = options
      @associations = {}
      
      @base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
        @model, [], nil
      )
      
      unless @model.descends_from_active_record?
        stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
        @conditions << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'"
      end
      
      add_internal_attributes_and_facets
    end
    
    def name
      @model.name.underscore.tr(':/\\', '_')
    end
    
    def to_riddle_for_core(offset, index)
      if ThinkingSphinx::Configuration.instance.type == "xml"
        source = Riddle::Configuration::XMLSource.new(
          "#{name}_core_#{index}", "xmlpipe2"
        )
        set_source_xml_settings       source, offset
        set_source_attributes         source, offset
        set_source_settings           source
        source
      else
        source = Riddle::Configuration::SQLSource.new(
          "#{name}_core_#{index}", adapter.sphinx_identifier
        )
        set_source_database_settings  source
        set_source_attributes         source, offset
        set_source_sql                source, offset
        set_source_settings           source
        source
      end
    end
    
    def to_riddle_for_delta(offset, index)
      if ThinkingSphinx::Configuration.instance.type == "xml"
        source = Riddle::Configuration::XMLSource.new(
          "#{name}_delta_#{index}", "xmlpipe2"
        )
        set_source_xml_settings       source, offset
        set_source_attributes         source, offset
        source
      else
        source = Riddle::Configuration::SQLSource.new(
          "#{name}_delta_#{index}", adapter.sphinx_identifier
        )
        source.parent = "#{name}_core_#{index}"
        set_source_database_settings  source
        set_source_attributes         source, offset, true
        set_source_sql                source, offset, true
        source
      end
    end
    
    def delta?
      !@index.delta_object.nil?
    end
    
    # Gets the association stack for a specific key.
    # 
    def association(key)
      @associations[key] ||= Association.children(@model, key)
    end
    
    private
    
    def adapter
      @adapter ||= @model.sphinx_database_adapter
    end
    
    def set_source_database_settings(source)
      config = @model.connection.instance_variable_get(:@config)
      return unless config
      
      source.sql_host = config[:host]           || "localhost"
      source.sql_user = config[:username]       || config[:user] || 'root'
      source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
      source.sql_db   = config[:database]
      source.sql_port = config[:port]
      source.sql_sock = config[:socket]
    end
    
    def set_source_attributes(source, offset, delta = false)
      attributes.each do |attrib|
        source.send(attrib.type_to_config) << attrib.config_value(offset, delta)
      end
    end
    
    def set_source_sql(source, offset, delta = false)
      source.sql_query        = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
      source.sql_query_range  = to_sql_query_range(:delta => delta)
      source.sql_query_info   = to_sql_query_info(offset)
      
      source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
      
      if @index.local_options[:group_concat_max_len]
        source.sql_query_pre << "SET SESSION group_concat_max_len = #{@index.local_options[:group_concat_max_len]}"
      end
      
      source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
    end
    
    def set_source_settings(source)
      config = ThinkingSphinx::Configuration.instance
      config.source_options.each do |key, value|
        source.send("#{key}=".to_sym, value)
      end
      
      source_options = ThinkingSphinx::Configuration::SourceOptions
      @options.each do |key, value|
        if source_options.include?(key.to_s) && !value.nil?
          source.send("#{key}=".to_sym, value)
        end
      end
    end
    
    def set_source_xml_settings(source, offset)
      env = ThinkingSphinx::Configuration.instance.environment
      database = defined?(Database) ? Database : nil
      source.xmlpipe_command = "rake -s ts:xml NAME=#{source.name} OFFSET=#{offset} RAILS_ENV=#{env} MERB_ENV=#{env} DATABASE=#{database}"
      @fields.each do |field|
        source.xmlpipe_field << field.unique_name
      end
    end
    
    # Returns all associations used amongst all the fields and attributes.
    # This includes all associations between the model and what the actual
    # columns are from.
    # 
    def all_associations
      @all_associations ||= (
        # field associations
        @fields.collect { |field|
          field.associations.values
        }.flatten +
        # attribute associations
        @attributes.collect { |attrib|
          attrib.associations.values if attrib.include_as_association?
        }.compact.flatten
      ).uniq.collect { |assoc|
        # get ancestors as well as column-level associations
        assoc.ancestors
      }.flatten.uniq
    end
    
    def utf8?
      @index.options[:charset_type] == "utf-8"
    end
  end
end