# encoding: utf-8
require 'bindata'
require 'ipaddr'
require 'ip2location_ruby/ip2location_config'
require 'ip2location_ruby/database_config'
require 'ip2location_ruby/i2l_float_data'
require 'ip2location_ruby/i2l_string_data'
require 'ip2location_ruby/i2l_ip_data'
require 'ip2location_ruby/ip2location_record'

class Ip2location
  attr_accessor :record_class, :v4, :file, :db_index, :count, :base_addr, :ipno, :count, :record, :database, :columns, :ip_version, :ipv4databasecount, :ipv4databaseaddr, :ipv6databasecount, :ipv6databaseaddr
  
  def open(url)
    self.file = File.open(File.expand_path url, 'rb')
    i2l = Ip2locationConfig.read(file)
    self.db_index = i2l.databasetype
    self.columns = i2l.databasecolumn + 0
    self.database = DbConfig.setup_database(self.db_index)
    self.ipv4databasecount = i2l.ipv4databasecount
    self.ipv4databaseaddr = i2l.ipv4databaseaddr
    self.ipv6databasecount = i2l.ipv6databasecount
    self.ipv6databaseaddr = i2l.ipv6databaseaddr
    self
  end
  
  def get_all(ip)
    ipno = IPAddr.new(ip, Socket::AF_UNSPEC)
    self.ip_version = ipno.ipv4? ? 4 : 6
    self.v4 = ipno.ipv4?
    self.count = ipno.ipv4? ? self.ipv4databasecount + 0 : self.ipv6databasecount + 0
    self.base_addr = (ipno.ipv4? ? self.ipv4databaseaddr - 1 : self.ipv6databaseaddr - 1)
    self.record_class = (Ip2LocationRecord.init database, self.ip_version)
    
    ipnum = ipno.to_i + 0
    col_length = columns * 4

    return self.record = bsearch(0, self.count, ipnum, self.base_addr, col_length)
  end
  
  def get_from_to(mid, base_addr, col_length)
    from_base = ( base_addr + mid * (col_length + (v4 ? 0 : 12)))
    file.seek(from_base)
    ip_from =  v4 ? BinData::Uint32le.read(file) : BinData::Uint128le.read(file)
    file.seek(from_base + col_length + (v4 ? 0 : 12))
    ip_to = v4 ? BinData::Uint32le.read(file) : BinData::Uint128le.read(file)
    [ip_from, ip_to]
  end
  
  def bsearch(low, high, ipnum, base_addr, col_length) 
    mid = (high + low)/2
    return nil if low == mid
    ip_from, ip_to = get_from_to(mid, base_addr, col_length)
    if ipnum < ip_from
      low = low
      high = mid
      return bsearch(low, high, ipnum, base_addr, col_length)
    elsif ipnum >= ip_to
      low = mid
      high = high
      return bsearch(low, high, ipnum, base_addr, col_length) 
    else
      from_base = ( base_addr + mid * (col_length + (self.v4 ? 0 : 12)))
      file.seek(from_base)  
      return self.record_class.read(file)
    end
  end
    
end