class MaxMindManager < BaseManager
  
  def initialize(options={})
    super(options)
  end
  
  
  # Returns a hash with location information. Fields are nil if they can't be figured.
  # This is a class method because it doesn't need to be in a transaction.
  def self.lookup(ip_address)
    
    city = state = country = nil
    
    unless ip_address.nil? || ip_address !~ /^\d+\.\d+\.\d+\.\d+$/
      ActiveRecord::Base.connection_pool.with_connection do |connection|
        pg_conn = connection.instance_variable_get("@connection")
        ip_as_int = ip_address_to_int(ip_address)
        pg_conn.exec("SELECT country, region, city FROM max_mind_geo WHERE ip_bottom <= $1 AND ip_top >= $2", [ip_as_int, ip_as_int]) do |result|
          if !result.nil? && result.ntuples > 0
            country = result.getvalue(0, 0)
            state = result[0]['region']
            city = result[0]['city']
          end
        end
      end
    end

    {
      :city => city,
      :state => state,
      :country => country
    }
    
  end

  def self.lookup_isp(ip_address)

    isp = nil

    unless ip_address.nil? || ip_address !~ /^\d+\.\d+\.\d+\.\d+$/
      ActiveRecord::Base.connection_pool.with_connection do |connection|
        pg_conn = connection.instance_variable_get("@connection")
        ip_as_int = ip_address_to_int(ip_address)
        pg_conn.exec("SELECT isp FROM max_mind_isp WHERE ip_bottom <= $1 AND ip_top >= $2", [ip_as_int, ip_as_int]) do |result|
          if !result.nil? && result.ntuples > 0
            isp = result.getvalue(0, 0)
          end
        end
      end
    end

    return isp
  end

  def self.countries()
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      pg_conn = connection.instance_variable_get("@connection")
      pg_conn.exec("SELECT DISTINCT country FROM max_mind_geo ORDER BY country ASC").map do |tuple|
        tuple["country"]
      end
    end
  end


  def self.regions(country)
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      pg_conn = connection.instance_variable_get("@connection")
      pg_conn.exec("SELECT DISTINCT region FROM max_mind_geo WHERE country = $1 ORDER BY region ASC", [country]).map do |tuple|
        tuple["region"]
      end
    end
  end  
  
  
  def self.cities(country, region)
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      pg_conn = connection.instance_variable_get("@connection")
      pg_conn.exec("SELECT DISTINCT city FROM max_mind_geo WHERE country = $1 AND region = $2 ORDER BY city ASC", [country, region]).map do |tuple|
        tuple["city"]
      end
    end
  end


  def self.isps(country)
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      pg_conn = connection.instance_variable_get("@connection")
      pg_conn.exec("SELECT DISTINCT isp FROM max_mind_isp WHERE country = $1 ORDER BY isp ASC", [country]).map do |tuple|
        tuple["isp"]
      end
    end
  end

  # Note that there's one big country, and then two cities in each region.
  def create_phony_database()
    clear_location_table
    (0..255).each do |top_octet|
      @pg_conn.exec("INSERT INTO max_mind_geo (ip_bottom, ip_top, country, region, city) VALUES ($1, $2, $3, $4, $5)",
        [
          self.class.ip_address_to_int("#{top_octet}.0.0.0"),
          self.class.ip_address_to_int("#{top_octet}.255.255.255"),
          "US",
          "Region #{(top_octet / 2).floor}",
          "City #{top_octet}"
        ]).clear        
    end

    clear_isp_table
    (0..255).each do |top_octet|
      @pg_conn.exec("INSERT INTO max_mind_isp (ip_bottom, ip_top, isp, country) VALUES ($1, $2, $3, $4)",
        [
            self.class.ip_address_to_int("#{top_octet}.0.0.0"),
            self.class.ip_address_to_int("#{top_octet}.255.255.255"),
            "ISP #{top_octet}",
            "US"
        ]).clear
    end


  end
    
  private
  
  # Make an IP address fit in a signed int. Just divide it by 2, as the least significant part
  # just can't possibly matter. We can verify this if needed. My guess is the entire bottom octet is
  # actually irrelevant
  def self.ip_address_to_int(ip)
    ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i} / 2
  end

  def clear_location_table
    @pg_conn.exec("DELETE FROM max_mind_geo").clear
  end

  def clear_isp_table
    @pg_conn.exec("DELETE FROM max_mind_isp").clear
  end

end

