// This ip2location package provides a fast lookup of country, region, city, latitude, longitude, ZIP code, time zone,
// ISP, domain name, connection type, IDD code, area code, weather station code, station name, MCC, MNC,
// mobile brand, elevation, and usage type from IP address by using IP2Location database.
package ip2location

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"math/big"
	"net"
	"os"
	"strconv"
)

type ip2locationmeta struct {
	databasetype      uint8
	databasecolumn    uint8
	databaseday       uint8
	databasemonth     uint8
	databaseyear      uint8
	ipv4databasecount uint32
	ipv4databaseaddr  uint32
	ipv6databasecount uint32
	ipv6databaseaddr  uint32
	ipv4indexbaseaddr uint32
	ipv6indexbaseaddr uint32
	ipv4columnsize    uint32
	ipv6columnsize    uint32
}

// The IP2Locationrecord struct stores all of the available
// geolocation info found in the IP2Location database.
type IP2Locationrecord struct {
	Country_short      string
	Country_long       string
	Region             string
	City               string
	Isp                string
	Latitude           float32
	Longitude          float32
	Domain             string
	Zipcode            string
	Timezone           string
	Netspeed           string
	Iddcode            string
	Areacode           string
	Weatherstationcode string
	Weatherstationname string
	Mcc                string
	Mnc                string
	Mobilebrand        string
	Elevation          float32
	Usagetype          string
}

type DB struct {
	f    *os.File
	meta ip2locationmeta

	country_position_offset            uint32
	region_position_offset             uint32
	city_position_offset               uint32
	isp_position_offset                uint32
	domain_position_offset             uint32
	zipcode_position_offset            uint32
	latitude_position_offset           uint32
	longitude_position_offset          uint32
	timezone_position_offset           uint32
	netspeed_position_offset           uint32
	iddcode_position_offset            uint32
	areacode_position_offset           uint32
	weatherstationcode_position_offset uint32
	weatherstationname_position_offset uint32
	mcc_position_offset                uint32
	mnc_position_offset                uint32
	mobilebrand_position_offset        uint32
	elevation_position_offset          uint32
	usagetype_position_offset          uint32

	country_enabled            bool
	region_enabled             bool
	city_enabled               bool
	isp_enabled                bool
	domain_enabled             bool
	zipcode_enabled            bool
	latitude_enabled           bool
	longitude_enabled          bool
	timezone_enabled           bool
	netspeed_enabled           bool
	iddcode_enabled            bool
	areacode_enabled           bool
	weatherstationcode_enabled bool
	weatherstationname_enabled bool
	mcc_enabled                bool
	mnc_enabled                bool
	mobilebrand_enabled        bool
	elevation_enabled          bool
	usagetype_enabled          bool

	metaok bool
}

var defaultDB = &DB{}

var country_position = [25]uint8{0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}
var region_position = [25]uint8{0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}
var city_position = [25]uint8{0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}
var isp_position = [25]uint8{0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9}
var latitude_position = [25]uint8{0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}
var longitude_position = [25]uint8{0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}
var domain_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10}
var zipcode_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7}
var timezone_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8}
var netspeed_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11}
var iddcode_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12}
var areacode_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13}
var weatherstationcode_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14}
var weatherstationname_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15}
var mcc_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16}
var mnc_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17}
var mobilebrand_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18}
var elevation_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19}
var usagetype_position = [25]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20}

const api_version string = "8.3.0"

var max_ipv4_range = big.NewInt(4294967295)
var max_ipv6_range = big.NewInt(0)
var from_v4mapped = big.NewInt(281470681743360)
var to_v4mapped = big.NewInt(281474976710655)
var from_6to4 = big.NewInt(0)
var to_6to4 = big.NewInt(0)
var from_teredo = big.NewInt(0)
var to_teredo = big.NewInt(0)
var last_32bits = big.NewInt(4294967295)

const countryshort uint32 = 0x00001
const countrylong uint32 = 0x00002
const region uint32 = 0x00004
const city uint32 = 0x00008
const isp uint32 = 0x00010
const latitude uint32 = 0x00020
const longitude uint32 = 0x00040
const domain uint32 = 0x00080
const zipcode uint32 = 0x00100
const timezone uint32 = 0x00200
const netspeed uint32 = 0x00400
const iddcode uint32 = 0x00800
const areacode uint32 = 0x01000
const weatherstationcode uint32 = 0x02000
const weatherstationname uint32 = 0x04000
const mcc uint32 = 0x08000
const mnc uint32 = 0x10000
const mobilebrand uint32 = 0x20000
const elevation uint32 = 0x40000
const usagetype uint32 = 0x80000

const all uint32 = countryshort | countrylong | region | city | isp | latitude | longitude | domain | zipcode | timezone | netspeed | iddcode | areacode | weatherstationcode | weatherstationname | mcc | mnc | mobilebrand | elevation | usagetype

const invalid_address string = "Invalid IP address."
const missing_file string = "Invalid database file."
const not_supported string = "This parameter is unavailable for selected data file. Please upgrade the data file."

// get IP type and calculate IP number; calculates index too if exists
func (d *DB) checkip(ip string) (iptype uint32, ipnum *big.Int, ipindex uint32) {
	iptype = 0
	ipnum = big.NewInt(0)
	ipnumtmp := big.NewInt(0)
	ipindex = 0
	ipaddress := net.ParseIP(ip)

	if ipaddress != nil {
		v4 := ipaddress.To4()

		if v4 != nil {
			iptype = 4
			ipnum.SetBytes(v4)
		} else {
			v6 := ipaddress.To16()

			if v6 != nil {
				iptype = 6
				ipnum.SetBytes(v6)

				if ipnum.Cmp(from_v4mapped) >= 0 && ipnum.Cmp(to_v4mapped) <= 0 {
					// ipv4-mapped ipv6 should treat as ipv4 and read ipv4 data section
					iptype = 4
					ipnum.Sub(ipnum, from_v4mapped)
				} else if ipnum.Cmp(from_6to4) >= 0 && ipnum.Cmp(to_6to4) <= 0 {
					// 6to4 so need to remap to ipv4
					iptype = 4
					ipnum.Rsh(ipnum, 80)
					ipnum.And(ipnum, last_32bits)
				} else if ipnum.Cmp(from_teredo) >= 0 && ipnum.Cmp(to_teredo) <= 0 {
					// Teredo so need to remap to ipv4
					iptype = 4
					ipnum.Not(ipnum)
					ipnum.And(ipnum, last_32bits)
				}
			}
		}
	}
	if iptype == 4 {
		if d.meta.ipv4indexbaseaddr > 0 {
			ipnumtmp.Rsh(ipnum, 16)
			ipnumtmp.Lsh(ipnumtmp, 3)
			ipindex = uint32(ipnumtmp.Add(ipnumtmp, big.NewInt(int64(d.meta.ipv4indexbaseaddr))).Uint64())
		}
	} else if iptype == 6 {
		if d.meta.ipv6indexbaseaddr > 0 {
			ipnumtmp.Rsh(ipnum, 112)
			ipnumtmp.Lsh(ipnumtmp, 3)
			ipindex = uint32(ipnumtmp.Add(ipnumtmp, big.NewInt(int64(d.meta.ipv6indexbaseaddr))).Uint64())
		}
	}
	return
}

// read byte
func (d *DB) readuint8(pos int64) (uint8, error) {
	var retval uint8
	data := make([]byte, 1)
	_, err := d.f.ReadAt(data, pos-1)
	if err != nil {
		return 0, err
	}
	retval = data[0]
	return retval, nil
}

// read unsigned 32-bit integer from slices
func (d *DB) readuint32_row(row []byte, pos uint32) uint32 {
	var retval uint32
	data := row[pos : pos+4]
	retval = binary.LittleEndian.Uint32(data)
	return retval
}

// read unsigned 32-bit integer
func (d *DB) readuint32(pos uint32) (uint32, error) {
	pos2 := int64(pos)
	var retval uint32
	data := make([]byte, 4)
	_, err := d.f.ReadAt(data, pos2-1)
	if err != nil {
		return 0, err
	}
	buf := bytes.NewReader(data)
	err = binary.Read(buf, binary.LittleEndian, &retval)
	if err != nil {
		fmt.Printf("binary read failed: %v", err)
	}
	return retval, nil
}

// read unsigned 128-bit integer
func (d *DB) readuint128(pos uint32) (*big.Int, error) {
	pos2 := int64(pos)
	retval := big.NewInt(0)
	data := make([]byte, 16)
	_, err := d.f.ReadAt(data, pos2-1)
	if err != nil {
		return nil, err
	}

	// little endian to big endian
	for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
		data[i], data[j] = data[j], data[i]
	}
	retval.SetBytes(data)
	return retval, nil
}

// read string
func (d *DB) readstr(pos uint32) (string, error) {
	pos2 := int64(pos)
	var retval string
	lenbyte := make([]byte, 1)
	_, err := d.f.ReadAt(lenbyte, pos2)
	if err != nil {
		return "", err
	}
	strlen := lenbyte[0]
	data := make([]byte, strlen)
	_, err = d.f.ReadAt(data, pos2+1)
	if err != nil {
		return "", err
	}
	retval = string(data[:strlen])
	return retval, nil
}

// read float from slices
func (d *DB) readfloat_row(row []byte, pos uint32) float32 {
	var retval float32
	data := row[pos : pos+4]
	bits := binary.LittleEndian.Uint32(data)
	retval = math.Float32frombits(bits)
	return retval
}

// read float
func (d *DB) readfloat(pos uint32) (float32, error) {
	pos2 := int64(pos)
	var retval float32
	data := make([]byte, 4)
	_, err := d.f.ReadAt(data, pos2-1)
	if err != nil {
		return 0, err
	}
	buf := bytes.NewReader(data)
	err = binary.Read(buf, binary.LittleEndian, &retval)
	if err != nil {
		fmt.Printf("binary read failed: %v", err)
	}
	return retval, nil
}

func fatal(db *DB, err error) (*DB, error) {
	_ = db.f.Close()
	return nil, err
}

// Open takes the path to the IP2Location BIN database file. It will read all the metadata required to
// be able to extract the embedded geolocation data, and return the underlining DB object.
func OpenDB(dbpath string) (*DB, error) {
	var db = &DB{}

	max_ipv6_range.SetString("340282366920938463463374607431768211455", 10)
	from_6to4.SetString("42545680458834377588178886921629466624", 10)
	to_6to4.SetString("42550872755692912415807417417958686719", 10)
	from_teredo.SetString("42540488161975842760550356425300246528", 10)
	to_teredo.SetString("42540488241204005274814694018844196863", 10)

	var err error
	db.f, err = os.Open(dbpath)
	if err != nil {
		return nil, err
	}

	db.meta.databasetype, err = db.readuint8(1)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.databasecolumn, err = db.readuint8(2)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.databaseyear, err = db.readuint8(3)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.databasemonth, err = db.readuint8(4)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.databaseday, err = db.readuint8(5)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv4databasecount, err = db.readuint32(6)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv4databaseaddr, err = db.readuint32(10)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv6databasecount, err = db.readuint32(14)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv6databaseaddr, err = db.readuint32(18)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv4indexbaseaddr, err = db.readuint32(22)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv6indexbaseaddr, err = db.readuint32(26)
	if err != nil {
		return fatal(db, err)
	}
	db.meta.ipv4columnsize = uint32(db.meta.databasecolumn << 2)              // 4 bytes each column
	db.meta.ipv6columnsize = uint32(16 + ((db.meta.databasecolumn - 1) << 2)) // 4 bytes each column, except IPFrom column which is 16 bytes

	dbt := db.meta.databasetype

	// since both IPv4 and IPv6 use 4 bytes for the below columns, can just do it once here
	// if country_position[dbt] != 0 {
	// country_position_offset = uint32(country_position[dbt] - 1) << 2
	// country_enabled = true
	// }
	// if region_position[dbt] != 0 {
	// region_position_offset = uint32(region_position[dbt] - 1) << 2
	// region_enabled = true
	// }
	// if city_position[dbt] != 0 {
	// city_position_offset = uint32(city_position[dbt] - 1) << 2
	// city_enabled = true
	// }
	// if isp_position[dbt] != 0 {
	// isp_position_offset = uint32(isp_position[dbt] - 1) << 2
	// isp_enabled = true
	// }
	// if domain_position[dbt] != 0 {
	// domain_position_offset = uint32(domain_position[dbt] - 1) << 2
	// domain_enabled = true
	// }
	// if zipcode_position[dbt] != 0 {
	// zipcode_position_offset = uint32(zipcode_position[dbt] - 1) << 2
	// zipcode_enabled = true
	// }
	// if latitude_position[dbt] != 0 {
	// latitude_position_offset = uint32(latitude_position[dbt] - 1) << 2
	// latitude_enabled = true
	// }
	// if longitude_position[dbt] != 0 {
	// longitude_position_offset = uint32(longitude_position[dbt] - 1) << 2
	// longitude_enabled = true
	// }
	// if timezone_position[dbt] != 0 {
	// timezone_position_offset = uint32(timezone_position[dbt] - 1) << 2
	// timezone_enabled = true
	// }
	// if netspeed_position[dbt] != 0 {
	// netspeed_position_offset = uint32(netspeed_position[dbt] - 1) << 2
	// netspeed_enabled = true
	// }
	// if iddcode_position[dbt] != 0 {
	// iddcode_position_offset = uint32(iddcode_position[dbt] - 1) << 2
	// iddcode_enabled = true
	// }
	// if areacode_position[dbt] != 0 {
	// areacode_position_offset = uint32(areacode_position[dbt] - 1) << 2
	// areacode_enabled = true
	// }
	// if weatherstationcode_position[dbt] != 0 {
	// weatherstationcode_position_offset = uint32(weatherstationcode_position[dbt] - 1) << 2
	// weatherstationcode_enabled = true
	// }
	// if weatherstationname_position[dbt] != 0 {
	// weatherstationname_position_offset = uint32(weatherstationname_position[dbt] - 1) << 2
	// weatherstationname_enabled = true
	// }
	// if mcc_position[dbt] != 0 {
	// mcc_position_offset = uint32(mcc_position[dbt] - 1) << 2
	// mcc_enabled = true
	// }
	// if mnc_position[dbt] != 0 {
	// mnc_position_offset = uint32(mnc_position[dbt] - 1) << 2
	// mnc_enabled = true
	// }
	// if mobilebrand_position[dbt] != 0 {
	// mobilebrand_position_offset = uint32(mobilebrand_position[dbt] - 1) << 2
	// mobilebrand_enabled = true
	// }
	// if elevation_position[dbt] != 0 {
	// elevation_position_offset = uint32(elevation_position[dbt] - 1) << 2
	// elevation_enabled = true
	// }
	// if usagetype_position[dbt] != 0 {
	// usagetype_position_offset = uint32(usagetype_position[dbt] - 1) << 2
	// usagetype_enabled = true
	// }
	if country_position[dbt] != 0 {
		db.country_position_offset = uint32(country_position[dbt]-2) << 2
		db.country_enabled = true
	}
	if region_position[dbt] != 0 {
		db.region_position_offset = uint32(region_position[dbt]-2) << 2
		db.region_enabled = true
	}
	if city_position[dbt] != 0 {
		db.city_position_offset = uint32(city_position[dbt]-2) << 2
		db.city_enabled = true
	}
	if isp_position[dbt] != 0 {
		db.isp_position_offset = uint32(isp_position[dbt]-2) << 2
		db.isp_enabled = true
	}
	if domain_position[dbt] != 0 {
		db.domain_position_offset = uint32(domain_position[dbt]-2) << 2
		db.domain_enabled = true
	}
	if zipcode_position[dbt] != 0 {
		db.zipcode_position_offset = uint32(zipcode_position[dbt]-2) << 2
		db.zipcode_enabled = true
	}
	if latitude_position[dbt] != 0 {
		db.latitude_position_offset = uint32(latitude_position[dbt]-2) << 2
		db.latitude_enabled = true
	}
	if longitude_position[dbt] != 0 {
		db.longitude_position_offset = uint32(longitude_position[dbt]-2) << 2
		db.longitude_enabled = true
	}
	if timezone_position[dbt] != 0 {
		db.timezone_position_offset = uint32(timezone_position[dbt]-2) << 2
		db.timezone_enabled = true
	}
	if netspeed_position[dbt] != 0 {
		db.netspeed_position_offset = uint32(netspeed_position[dbt]-2) << 2
		db.netspeed_enabled = true
	}
	if iddcode_position[dbt] != 0 {
		db.iddcode_position_offset = uint32(iddcode_position[dbt]-2) << 2
		db.iddcode_enabled = true
	}
	if areacode_position[dbt] != 0 {
		db.areacode_position_offset = uint32(areacode_position[dbt]-2) << 2
		db.areacode_enabled = true
	}
	if weatherstationcode_position[dbt] != 0 {
		db.weatherstationcode_position_offset = uint32(weatherstationcode_position[dbt]-2) << 2
		db.weatherstationcode_enabled = true
	}
	if weatherstationname_position[dbt] != 0 {
		db.weatherstationname_position_offset = uint32(weatherstationname_position[dbt]-2) << 2
		db.weatherstationname_enabled = true
	}
	if mcc_position[dbt] != 0 {
		db.mcc_position_offset = uint32(mcc_position[dbt]-2) << 2
		db.mcc_enabled = true
	}
	if mnc_position[dbt] != 0 {
		db.mnc_position_offset = uint32(mnc_position[dbt]-2) << 2
		db.mnc_enabled = true
	}
	if mobilebrand_position[dbt] != 0 {
		db.mobilebrand_position_offset = uint32(mobilebrand_position[dbt]-2) << 2
		db.mobilebrand_enabled = true
	}
	if elevation_position[dbt] != 0 {
		db.elevation_position_offset = uint32(elevation_position[dbt]-2) << 2
		db.elevation_enabled = true
	}
	if usagetype_position[dbt] != 0 {
		db.usagetype_position_offset = uint32(usagetype_position[dbt]-2) << 2
		db.usagetype_enabled = true
	}

	db.metaok = true

	return db, nil
}

// Open takes the path to the IP2Location BIN database file. It will read all the metadata required to
// be able to extract the embedded geolocation data.
//
// Deprecated: No longer being updated.
func Open(dbpath string) {
	db, err := OpenDB(dbpath)
	if err != nil {
		return
	}

	defaultDB = db
}

// Close will close the file handle to the BIN file.
//
// Deprecated: No longer being updated.
func Close() {
	defaultDB.Close()
}

// Api_version returns the version of the component.
func Api_version() string {
	return api_version
}

// populate record with message
func loadmessage(mesg string) IP2Locationrecord {
	var x IP2Locationrecord

	x.Country_short = mesg
	x.Country_long = mesg
	x.Region = mesg
	x.City = mesg
	x.Isp = mesg
	x.Domain = mesg
	x.Zipcode = mesg
	x.Timezone = mesg
	x.Netspeed = mesg
	x.Iddcode = mesg
	x.Areacode = mesg
	x.Weatherstationcode = mesg
	x.Weatherstationname = mesg
	x.Mcc = mesg
	x.Mnc = mesg
	x.Mobilebrand = mesg
	x.Usagetype = mesg

	return x
}

func handleError(rec IP2Locationrecord, err error) IP2Locationrecord {
	if err != nil {
		fmt.Print(err)
	}
	return rec
}

// Get_all will return all geolocation fields based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_all(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, all))
}

// Get_country_short will return the ISO-3166 country code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_country_short(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, countryshort))
}

// Get_country_long will return the country name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_country_long(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, countrylong))
}

// Get_region will return the region name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_region(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, region))
}

// Get_city will return the city name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_city(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, city))
}

// Get_isp will return the Internet Service Provider name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_isp(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, isp))
}

// Get_latitude will return the latitude based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_latitude(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, latitude))
}

// Get_longitude will return the longitude based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_longitude(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, longitude))
}

// Get_domain will return the domain name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_domain(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, domain))
}

// Get_zipcode will return the postal code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_zipcode(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, zipcode))
}

// Get_timezone will return the time zone based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_timezone(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, timezone))
}

// Get_netspeed will return the Internet connection speed based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_netspeed(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, netspeed))
}

// Get_iddcode will return the International Direct Dialing code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_iddcode(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, iddcode))
}

// Get_areacode will return the area code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_areacode(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, areacode))
}

// Get_weatherstationcode will return the weather station code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_weatherstationcode(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, weatherstationcode))
}

// Get_weatherstationname will return the weather station name based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_weatherstationname(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, weatherstationname))
}

// Get_mcc will return the mobile country code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_mcc(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, mcc))
}

// Get_mnc will return the mobile network code based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_mnc(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, mnc))
}

// Get_mobilebrand will return the mobile carrier brand based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_mobilebrand(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, mobilebrand))
}

// Get_elevation will return the elevation in meters based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_elevation(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, elevation))
}

// Get_usagetype will return the usage type based on the queried IP address.
//
// Deprecated: No longer being updated.
func Get_usagetype(ipaddress string) IP2Locationrecord {
	return handleError(defaultDB.query(ipaddress, usagetype))
}

// Get_all will return all geolocation fields based on the queried IP address.
func (d *DB) Get_all(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, all)
}

// Get_country_short will return the ISO-3166 country code based on the queried IP address.
func (d *DB) Get_country_short(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, countryshort)
}

// Get_country_long will return the country name based on the queried IP address.
func (d *DB) Get_country_long(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, countrylong)
}

// Get_region will return the region name based on the queried IP address.
func (d *DB) Get_region(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, region)
}

// Get_city will return the city name based on the queried IP address.
func (d *DB) Get_city(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, city)
}

// Get_isp will return the Internet Service Provider name based on the queried IP address.
func (d *DB) Get_isp(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, isp)
}

// Get_latitude will return the latitude based on the queried IP address.
func (d *DB) Get_latitude(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, latitude)
}

// Get_longitude will return the longitude based on the queried IP address.
func (d *DB) Get_longitude(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, longitude)
}

// Get_domain will return the domain name based on the queried IP address.
func (d *DB) Get_domain(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, domain)
}

// Get_zipcode will return the postal code based on the queried IP address.
func (d *DB) Get_zipcode(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, zipcode)
}

// Get_timezone will return the time zone based on the queried IP address.
func (d *DB) Get_timezone(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, timezone)
}

// Get_netspeed will return the Internet connection speed based on the queried IP address.
func (d *DB) Get_netspeed(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, netspeed)
}

// Get_iddcode will return the International Direct Dialing code based on the queried IP address.
func (d *DB) Get_iddcode(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, iddcode)
}

// Get_areacode will return the area code based on the queried IP address.
func (d *DB) Get_areacode(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, areacode)
}

// Get_weatherstationcode will return the weather station code based on the queried IP address.
func (d *DB) Get_weatherstationcode(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, weatherstationcode)
}

// Get_weatherstationname will return the weather station name based on the queried IP address.
func (d *DB) Get_weatherstationname(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, weatherstationname)
}

// Get_mcc will return the mobile country code based on the queried IP address.
func (d *DB) Get_mcc(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, mcc)
}

// Get_mnc will return the mobile network code based on the queried IP address.
func (d *DB) Get_mnc(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, mnc)
}

// Get_mobilebrand will return the mobile carrier brand based on the queried IP address.
func (d *DB) Get_mobilebrand(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, mobilebrand)
}

// Get_elevation will return the elevation in meters based on the queried IP address.
func (d *DB) Get_elevation(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, elevation)
}

// Get_usagetype will return the usage type based on the queried IP address.
func (d *DB) Get_usagetype(ipaddress string) (IP2Locationrecord, error) {
	return d.query(ipaddress, usagetype)
}

// main query
func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) {
	x := loadmessage(not_supported) // default message

	// read metadata
	if !d.metaok {
		x = loadmessage(missing_file)
		return x, nil
	}

	// check IP type and return IP number & index (if exists)
	iptype, ipno, ipindex := d.checkip(ipaddress)

	if iptype == 0 {
		x = loadmessage(invalid_address)
		return x, nil
	}

	var err error
	var colsize uint32
	var baseaddr uint32
	var low uint32
	var high uint32
	var mid uint32
	var rowoffset uint32
	var rowoffset2 uint32
	ipfrom := big.NewInt(0)
	ipto := big.NewInt(0)
	maxip := big.NewInt(0)

	if iptype == 4 {
		baseaddr = d.meta.ipv4databaseaddr
		high = d.meta.ipv4databasecount
		maxip = max_ipv4_range
		colsize = d.meta.ipv4columnsize
	} else {
		baseaddr = d.meta.ipv6databaseaddr
		high = d.meta.ipv6databasecount
		maxip = max_ipv6_range
		colsize = d.meta.ipv6columnsize
	}

	// reading index
	if ipindex > 0 {
		low, err = d.readuint32(ipindex)
		if err != nil {
			return x, err
		}
		high, err = d.readuint32(ipindex + 4)
		if err != nil {
			return x, err
		}
	}

	if ipno.Cmp(maxip) >= 0 {
		ipno.Sub(ipno, big.NewInt(1))
	}

	for low <= high {
		mid = ((low + high) >> 1)
		rowoffset = baseaddr + (mid * colsize)
		rowoffset2 = rowoffset + colsize

		if iptype == 4 {
			ipfrom32, err := d.readuint32(rowoffset)
			if err != nil {
				return x, err
			}
			ipfrom = big.NewInt(int64(ipfrom32))

			ipto32, err := d.readuint32(rowoffset2)
			if err != nil {
				return x, err
			}
			ipto = big.NewInt(int64(ipto32))

		} else {
			ipfrom, err = d.readuint128(rowoffset)
			if err != nil {
				return x, err
			}

			ipto, err = d.readuint128(rowoffset2)
			if err != nil {
				return x, err
			}
		}

		if ipno.Cmp(ipfrom) >= 0 && ipno.Cmp(ipto) < 0 {
			var firstcol uint32 = 4 // 4 bytes for ip from
			if iptype == 6 {
				firstcol = 16 // 16 bytes for ipv6
				// rowoffset = rowoffset + 12 // coz below is assuming all columns are 4 bytes, so got 12 left to go to make 16 bytes total
			}

			row := make([]byte, colsize-firstcol) // exclude the ip from field
			_, err := d.f.ReadAt(row, int64(rowoffset+firstcol-1))
			if err != nil {
				return x, err
			}

			if mode&countryshort == 1 && d.country_enabled {
				// x.Country_short = readstr(readuint32(rowoffset + country_position_offset))
				if x.Country_short, err = d.readstr(d.readuint32_row(row, d.country_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&countrylong != 0 && d.country_enabled {
				// x.Country_long = readstr(readuint32(rowoffset + country_position_offset) + 3)
				if x.Country_long, err = d.readstr(d.readuint32_row(row, d.country_position_offset) + 3); err != nil {
					return x, err
				}
			}

			if mode&region != 0 && d.region_enabled {
				// x.Region = readstr(readuint32(rowoffset + region_position_offset))
				if x.Region, err = d.readstr(d.readuint32_row(row, d.region_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&city != 0 && d.city_enabled {
				// x.City = readstr(readuint32(rowoffset + city_position_offset))
				if x.City, err = d.readstr(d.readuint32_row(row, d.city_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&isp != 0 && d.isp_enabled {
				// x.Isp = readstr(readuint32(rowoffset + isp_position_offset))
				if x.Isp, err = d.readstr(d.readuint32_row(row, d.isp_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&latitude != 0 && d.latitude_enabled {
				// x.Latitude = readfloat(rowoffset + latitude_position_offset)
				x.Latitude = d.readfloat_row(row, d.latitude_position_offset)
			}

			if mode&longitude != 0 && d.longitude_enabled {
				// x.Longitude = readfloat(rowoffset + longitude_position_offset)
				x.Longitude = d.readfloat_row(row, d.longitude_position_offset)
			}

			if mode&domain != 0 && d.domain_enabled {
				// x.Domain = readstr(readuint32(rowoffset + domain_position_offset))
				if x.Domain, err = d.readstr(d.readuint32_row(row, d.domain_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&zipcode != 0 && d.zipcode_enabled {
				// x.Zipcode = readstr(readuint32(rowoffset + zipcode_position_offset))
				if x.Zipcode, err = d.readstr(d.readuint32_row(row, d.zipcode_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&timezone != 0 && d.timezone_enabled {
				// x.Timezone = readstr(readuint32(rowoffset + timezone_position_offset))
				if x.Timezone, err = d.readstr(d.readuint32_row(row, d.timezone_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&netspeed != 0 && d.netspeed_enabled {
				// x.Netspeed = readstr(readuint32(rowoffset + netspeed_position_offset))
				if x.Netspeed, err = d.readstr(d.readuint32_row(row, d.netspeed_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&iddcode != 0 && d.iddcode_enabled {
				// x.Iddcode = readstr(readuint32(rowoffset + iddcode_position_offset))
				if x.Iddcode, err = d.readstr(d.readuint32_row(row, d.iddcode_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&areacode != 0 && d.areacode_enabled {
				// x.Areacode = readstr(readuint32(rowoffset + areacode_position_offset))
				if x.Areacode, err = d.readstr(d.readuint32_row(row, d.areacode_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&weatherstationcode != 0 && d.weatherstationcode_enabled {
				// x.Weatherstationcode = readstr(readuint32(rowoffset + weatherstationcode_position_offset))
				if x.Weatherstationcode, err = d.readstr(d.readuint32_row(row, d.weatherstationcode_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&weatherstationname != 0 && d.weatherstationname_enabled {
				// x.Weatherstationname = readstr(readuint32(rowoffset + weatherstationname_position_offset))
				if x.Weatherstationname, err = d.readstr(d.readuint32_row(row, d.weatherstationname_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&mcc != 0 && d.mcc_enabled {
				// x.Mcc = readstr(readuint32(rowoffset + mcc_position_offset))
				if x.Mcc, err = d.readstr(d.readuint32_row(row, d.mcc_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&mnc != 0 && d.mnc_enabled {
				// x.Mnc = readstr(readuint32(rowoffset + mnc_position_offset))
				if x.Mnc, err = d.readstr(d.readuint32_row(row, d.mnc_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&mobilebrand != 0 && d.mobilebrand_enabled {
				// x.Mobilebrand = readstr(readuint32(rowoffset + mobilebrand_position_offset))
				if x.Mobilebrand, err = d.readstr(d.readuint32_row(row, d.mobilebrand_position_offset)); err != nil {
					return x, err
				}
			}

			if mode&elevation != 0 && d.elevation_enabled {
				// f, _ := strconv.ParseFloat(readstr(readuint32(rowoffset + elevation_position_offset)), 32)
				res, err := d.readstr(d.readuint32_row(row, d.elevation_position_offset))
				if err != nil {
					return x, err
				}

				f, _ := strconv.ParseFloat(res, 32)
				x.Elevation = float32(f)
			}

			if mode&usagetype != 0 && d.usagetype_enabled {
				// x.Usagetype = readstr(readuint32(rowoffset + usagetype_position_offset))
				if x.Usagetype, err = d.readstr(d.readuint32_row(row, d.usagetype_position_offset)); err != nil {
					return x, err
				}
			}

			return x, nil
		} else {
			if ipno.Cmp(ipfrom) < 0 {
				high = mid - 1
			} else {
				low = mid + 1
			}
		}
	}
	return x, nil
}

func (d *DB) Close() {
	_ = d.f.Close()
}

// Printrecord is used to output the geolocation data for debugging purposes.
func Printrecord(x IP2Locationrecord) {
	fmt.Printf("country_short: %s\n", x.Country_short)
	fmt.Printf("country_long: %s\n", x.Country_long)
	fmt.Printf("region: %s\n", x.Region)
	fmt.Printf("city: %s\n", x.City)
	fmt.Printf("isp: %s\n", x.Isp)
	fmt.Printf("latitude: %f\n", x.Latitude)
	fmt.Printf("longitude: %f\n", x.Longitude)
	fmt.Printf("domain: %s\n", x.Domain)
	fmt.Printf("zipcode: %s\n", x.Zipcode)
	fmt.Printf("timezone: %s\n", x.Timezone)
	fmt.Printf("netspeed: %s\n", x.Netspeed)
	fmt.Printf("iddcode: %s\n", x.Iddcode)
	fmt.Printf("areacode: %s\n", x.Areacode)
	fmt.Printf("weatherstationcode: %s\n", x.Weatherstationcode)
	fmt.Printf("weatherstationname: %s\n", x.Weatherstationname)
	fmt.Printf("mcc: %s\n", x.Mcc)
	fmt.Printf("mnc: %s\n", x.Mnc)
	fmt.Printf("mobilebrand: %s\n", x.Mobilebrand)
	fmt.Printf("elevation: %f\n", x.Elevation)
	fmt.Printf("usagetype: %s\n", x.Usagetype)
}
