require 'active_record'
require 'geo_ruby'
require 'common_spatial_adapter'

include GeoRuby::SimpleFeatures

#add a method to_yaml to the Geometry class which will transform a geometry in a form suitable to be used in a YAML file (such as in a fixture)
GeoRuby::SimpleFeatures::Geometry.class_eval do
  def to_fixture_format
    "!binary | #{[(255.chr * 4) + as_wkb].pack('m').gsub(/\s+/,"")}"
  end
end

ActiveRecord::Base.class_eval do
  require 'active_record/version'

  #For Rails < 1.2
  if ActiveRecord::VERSION::STRING < "1.15.1"
    def self.construct_conditions_from_arguments(attribute_names, arguments)
      conditions = []
      attribute_names.each_with_index do |name, idx| 
        if columns_hash[name].is_a?(SpatialColumn)
          #when the discriminating column is spatial, always use the MBRIntersects (bounding box intersection check) operator : the user can pass either a geometric object (which will be transformed to a string using the quote method of the database adapter) or an array with the corner points of a bounding box
          if arguments[idx].is_a?(Array)
            #using some georuby utility : The multipoint has a bbox whose corners are the 2 points passed as parameters : [ pt1, pt2]
            arguments[idx]= MultiPoint.from_coordinates(arguments[idx])
          elsif arguments[idx].is_a?(Envelope)
            arguments[idx]= MultiPoint.from_points([arguments[idx].lower_corner,arguments[idx].upper_corner])
          end
          conditions << "MBRIntersects(?, #{table_name}.#{connection.quote_column_name(name)}) " 
        else
          conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " 
        end
      end
      [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
    end

  else
    def self.get_conditions(attrs)
      attrs.map do |attr, value|
        if columns_hash[attr].is_a?(SpatialColumn)
          if value.is_a?(Array)
            #using some georuby utility : The multipoint has a bbox whose corners are the 2 points passed as parameters : [ pt1, pt2]
            attrs[attr]= MultiPoint.from_coordinates(value)
          elsif value.is_a?(Envelope)
            attrs[attr]= MultiPoint.from_points([value.lower_corner,value.upper_corner])
          end
          "MBRIntersects(?, #{table_name}.#{connection.quote_column_name(attr)}) " 
        else
          #original stuff
          "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
        end
      end.join(' AND ')
    end
    def self.get_rails2_conditions(attrs)
      attrs.map do |attr, value|
        attr = attr.to_s
        if columns_hash[attr].is_a?(SpatialColumn)
         if value.is_a?(Array)
            #using some georuby utility : The multipoint has a bbox whose corners are the 2 points passed as parameters : [ pt1, pt2]
            attrs[attr.to_sym]=MultiPoint.from_coordinates(value)
          elsif value.is_a?(Envelope)
            attrs[attr.to_sym]=MultiPoint.from_points([value.lower_corner,value.upper_corner])
          end
          "MBRIntersects(?, #{table_name}.#{connection.quote_column_name(attr)}) " 
        else
          #original stuff
          # Extract table name from qualified attribute names.
          if attr.include?('.')
            table_name, attr = attr.split('.', 2)
            table_name = connection.quote_table_name(table_name)
          else
            table_name = quoted_table_name
          end
          "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
        end
      end.join(' AND ')
    end
    if ActiveRecord::VERSION::STRING == "1.15.1"
      def self.sanitize_sql_hash(attrs)
        conditions = get_conditions(attrs)
        replace_bind_variables(conditions, attrs.values)
      end
    elsif ActiveRecord::VERSION::STRING.starts_with?("1.15")
      #For Rails >= 1.2
      def self.sanitize_sql_hash(attrs)
        conditions = get_conditions(attrs)
        replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
      end
    else
      #For Rails >= 2
      def self.sanitize_sql_hash_for_conditions(attrs)
        conditions = get_rails2_conditions(attrs)
        replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
      end
    end
  end
end


ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
  
  include SpatialAdapter

  alias :original_native_database_types :native_database_types
  def native_database_types
    original_native_database_types.merge!(geometry_data_types)
  end
 
  alias :original_quote :quote
  #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
  def quote(value, column = nil)
    if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
      "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
    else
      original_quote(value,column)
    end
  end
  
  #Redefinition of columns to add the information that a column is geometric
  def columns(table_name, name = nil)#:nodoc:
    sql = "SHOW FIELDS FROM #{table_name}"
    columns = []
    execute(sql, name).each do |field| 
      if field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i
        #to note that the column is spatial
        columns << ActiveRecord::ConnectionAdapters::SpatialMysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
      else
        columns << ActiveRecord::ConnectionAdapters::MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
      end
    end
    columns
  end


  #operations relative to migrations

  #Redefines add_index to support the case where the index is spatial
  #If the :spatial key in the options table is true, then the sql string for a spatial index is created
  def add_index(table_name,column_name,options = {})
    index_name = options[:name] || index_name(table_name,:column => Array(column_name))
    
    if options[:spatial]
      execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
    else
      index_type = options[:unique] ? "UNIQUE" : ""
      #all together
      execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
    end
  end

  #Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb)
  def indexes(table_name, name = nil)#:nodoc:
    indexes = []
    current_index = nil
    execute("SHOW KEYS FROM #{table_name}", name).each do |row|
      if current_index != row[2]
        next if row[2] == "PRIMARY" # skip the primary key
        current_index = row[2]
        indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", row[10] == "SPATIAL",[])
      end
      indexes.last.columns << row[4]
    end
    indexes
  end
        
  #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
  def options_for(table)
    result = execute("show table status like '#{table}'")
    engine = result.fetch_row[1]
    if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
      "ENGINE=#{engine}" 
    else
      nil
    end
  end
end


module ActiveRecord
  module ConnectionAdapters
    class SpatialMysqlColumn < MysqlColumn

      include SpatialColumn
      
      #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
      def self.string_to_geometry(string)
        return string unless string.is_a?(String)
        begin
          GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
        rescue Exception => exception
          nil
        end
      end
    end
  end
end
