require 'singleton'

module Ultrasphinx

  class Fields
    include Singleton
    
    TYPE_MAP = {
      'string' => 'text', 
      'text' => 'text', 
      'integer' => 'numeric', 
      'date' => 'date', 
      'datetime' => 'date'
    }    
    
    attr_accessor :classes, :types
    
    def initialize
      @types = {}
      @classes = Hash.new([])
      @groups = []
    end
    
    def groups
      @groups.compact.sort_by do |string| 
        string[/= (.*)/, 1]
      end
    end
  
    def save_and_verify_type(field, new_type, string_sortable, klass)
      # Smoosh fields together based on their name in the Sphinx query schema
      field, new_type = field.to_s, TYPE_MAP[new_type.to_s]

      if types[field]
        # Existing field name; verify its type
        raise ConfigurationError, "Column type mismatch for #{field.inspect}; was already #{types[field].inspect}, but is now #{new_type.inspect}." unless types[field] == new_type
        classes[field] = (classes[field] + [klass]).uniq

      else
        # New field      
        types[field] = new_type
        classes[field] = [klass]

        @groups << case new_type
          when 'numeric'
            "sql_group_column = #{field}"
          when 'date'
            "sql_date_column = #{field}"
          when 'text' 
            "sql_str2ordinal_column = #{field}" if string_sortable
        end
      end
    end
    
    def cast(source_string, field)
      if types[field] == "date"
        "UNIX_TIMESTAMP(#{source_string})"
      elsif source_string =~ /GROUP_CONCAT/
        "CAST(#{source_string} AS CHAR)"
      else
        source_string              
      end + " AS #{field}"
    end    
      
    def null(field)
      case types[field]
        when 'text'
          "''"
        when 'numeric'
          "0"
        when 'date'
          "UNIX_TIMESTAMP('1970-01-01 00:00:00')"
        else
          raise "Field #{field} does not have a valid type."
      end + " AS #{field}"
    end
    
    def configure(configuration)

      configuration.each do |model, options|        

        klass = model.constantize        
        save_and_verify_type('class_id', 'integer', nil, klass)
        save_and_verify_type('class', 'string', nil, klass)
                
        begin
        
          # Fields are from the model. We destructively canonicize them back onto the configuration hash.
          options['fields'] = options['fields'].to_a.map do |entry|
            
            entry = {'field' => entry} unless entry.is_a? Hash
            entry['as'] = entry['field'] unless entry['as']
            
            unless klass.columns_hash[entry['field']]
              ActiveRecord::Base.logger.warn "ultrasphinx: WARNING: field #{entry['field']} is not present in #{model}"
            else
              save_and_verify_type(entry['as'], klass.columns_hash[entry['field']].type, entry['sortable'], klass)
            end
            
            if entry['facet']
              save_and_verify_type(entry['as'], 'text', nil, klass) # source must be a string
              save_and_verify_type("#{entry['as']}_facet", 'integer', nil, klass)
            end
            
            entry
          end  
          
          # Joins are whatever they are in the target       
          options['include'].to_a.each do |entry|
            save_and_verify_type(entry['as'] || entry['field'], entry['class_name'].constantize.columns_hash[entry['field']].type, entry['sortable'], klass)
          end  
          
          # Regular concats are CHAR (I think), group_concats are BLOB and need to be cast to CHAR, e.g. :text
          options['concatenate'].to_a.each do |entry|
            save_and_verify_type(entry['as'], 'text', entry['sortable'], klass)
          end          
        rescue ActiveRecord::StatementInvalid
          ActiveRecord::Base.logger.warn "ultrasphinx: WARNING: model #{model} does not exist in the database yet"
        end  
      end
      
      self
    end
    
  end
end