/**
 * LookupService.java
 *
 * Copyright (C) 2003 MaxMind LLC.  All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Provenance note:
 * This file was extracted from the local JMule CVS donor tree by
 * tools/extract_jmule_backend.py and is now carried as WireShare-owned source.
 * The build must not depend on the donor jmule/ directory at compile time.
 */


package com.maxmind.geoip;

import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.lang.*;

import java.util.*;

import javax.naming.*;
import javax.naming.directory.*;


/**
 * Provides a lookup service for information based on an IP address. The location of
 * a database file is supplied when creating a lookup service instance. The edition of
 * the database determines what information is available about an IP address. See the
 * DatabaseInfo class for further details.<p>
 *
 * The following code snippet demonstrates looking up the country that an IP
 * address is from:
 * <pre>
 * // First, create a LookupService instance with the location of the database.
 * LookupService lookupService = new LookupService("c:\\geoip.dat");
 * // Assume we have a String ipAddress (in dot-decimal form).
 * Country country = lookupService.getCountry(ipAddress);
 * System.out.println("The country is: " + country.getName());
 * System.out.println("The country code is: " + country.getCode());
 * </pre>
 *
 * In general, a single LookupService instance should be created and then reused
 * repeatedly.<p>
 *
 * <i>Tip:</i> Those deploying the GeoIP API as part of a web application may find it
 * difficult to pass in a File to create the lookup service, as the location of the
 * database may vary per deployment or may even be part of the web-application. In this
 * case, the database should be added to the classpath of the web-app. For example, by
 * putting it into the WEB-INF/classes directory of the web application. The following code
 * snippet demonstrates how to create a LookupService using a database that can be found
 * on the classpath:
 *
 * <pre>
 * String fileName = getClass().getResource("/GeoIP.dat").toExternalForm().substring(6);
 * LookupService lookupService = new LookupService(fileName);</pre>
 *
 * @author Matt Tucker (matt@jivesoftware.com)
 */
public class LookupService {

    /**
     * Database file.
     */
    private RandomAccessFile file = null;
    private File databaseFile = null;

    /**
     * Information about the database.
     */
    private DatabaseInfo databaseInfo = null;

    /**
     * The database type. Default is the country edition.
     */
    byte databaseType = DatabaseInfo.COUNTRY_EDITION;

    int databaseSegments[];
    int recordLength;
    
    String licenseKey;
    int dnsService = 0;
    int dboptions;
    byte dbbuffer[];
    byte index_cache[];
    long mtime;
    private final static int US_OFFSET = 1;
    private final static int CANADA_OFFSET = 677;
    private final static int WORLD_OFFSET = 1353;
    private final static int FIPS_RANGE = 360;
    private final static int COUNTRY_BEGIN = 16776960;
    private final static int STATE_BEGIN_REV0 = 16700000;
    private final static int STATE_BEGIN_REV1 = 16000000;
    private final static int STRUCTURE_INFO_MAX_SIZE = 20;
    private final static int DATABASE_INFO_MAX_SIZE = 100;
    public final static int GEOIP_STANDARD = 0;
    public final static int GEOIP_MEMORY_CACHE = 1;
    public final static int GEOIP_CHECK_CACHE = 2;
    public final static int GEOIP_INDEX_CACHE = 4;
    public final static int GEOIP_UNKNOWN_SPEED = 0;
    public final static int GEOIP_DIALUP_SPEED = 1;
    public final static int GEOIP_CABLEDSL_SPEED = 2;
    public final static int GEOIP_CORPORATE_SPEED = 3;


    private final static int SEGMENT_RECORD_LENGTH = 3;
    private final static int STANDARD_RECORD_LENGTH = 3;
    private final static int ORG_RECORD_LENGTH = 4;
    private final static int MAX_RECORD_LENGTH = 4;

    private final static int MAX_ORG_RECORD_LENGTH = 300;
    private final static int FULL_RECORD_LENGTH = 60;

    private static final Country UNKNOWN_COUNTRY = new Country("--", "N/A");

    private final static HashMap hashmapcountryCodetoindex = new HashMap(512);
    private final static HashMap hashmapcountryNametoindex = new HashMap(512);
    private final static String[] countryCode = {
	"--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR",
	"AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ",
	"BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF",
	"CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ",
	"DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI",
	"FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL",
	"GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR",
	"HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP",
	"KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC",
	"LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK",
	"ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY",
	"MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM",
	"PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY",
	"QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ",
	"SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG",
	"TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG",
	"UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE",
	"YT","RS","ZA","ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE","BL",
	"MF"};

    private final static String[] countryName = {
	"N/A","Asia/Pacific Region","Europe","Andorra","United Arab Emirates",
	"Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia",
	"Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa",
	"Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina",
	"Barbados","Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain",
	"Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas",
	"Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada",
	"Cocos (Keeling) Islands","Congo, The Democratic Republic of the",
	"Central African Republic","Congo","Switzerland","Cote D'Ivoire",
	"Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba",
	"Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany",
	"Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador",
	"Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland",
	"Fiji","Falkland Islands (Malvinas)","Micronesia, Federated States of",
	"Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom",
	"Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia",
	"Guinea","Guadeloupe","Equatorial Guinea","Greece",
	"South Georgia and the South Sandwich Islands","Guatemala","Guam",
	"Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands",
	"Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India",
	"British Indian Ocean Territory","Iraq","Iran, Islamic Republic of",
	"Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia",
	"Kiribati","Comoros","Saint Kitts and Nevis",
	"Korea, Democratic People's Republic of","Korea, Republic of","Kuwait",
	"Cayman Islands","Kazakstan","Lao People's Democratic Republic","Lebanon",
	"Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania",
	"Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco",
	"Moldova, Republic of","Madagascar","Marshall Islands",
	"Macedonia","Mali","Myanmar","Mongolia",
	"Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat",
	"Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique",
	"Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua",
	"Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama",
	"Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan",
	"Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","" +
	"Palestinian Territory","Portugal","Palau","Paraguay","Qatar",
	"Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia",
	"Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena",
	"Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino",
	"Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador",
	"Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad",
	"French Southern Territories","Togo","Thailand","Tajikistan","Tokelau",
	"Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago",
	"Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda",
	"United States Minor Outlying Islands","United States","Uruguay","Uzbekistan",
	"Holy See (Vatican City State)","Saint Vincent and the Grenadines",
	"Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam",
	"Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia",
	"South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy",
	"Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey",
	"Saint Barthelemy","Saint Martin"};


    /**
     * Create a new distributed lookup service using the license key
     *
     * @param databaseFile String representation of the database file.
     * @param licenseKey license key provided by Maxmind to access distributed service
     */
    public LookupService(String databaseFile,String licenseKey) throws IOException {
        this(new File(databaseFile));
        this.licenseKey = licenseKey;
        dnsService = 1;
    }
    /**
     * Create a new distributed lookup service using the license key
     *
     * @param databaseFile the database file.
     * @param licenseKey license key provided by Maxmind to access distributed service
     */
    public LookupService(File databaseFile,String licenseKey) throws IOException {
        this(databaseFile);
        this.licenseKey = licenseKey;
        dnsService = 1;
    }
    /**
     * Create a new distributed lookup service using the license key
     *
     * @param options  Resevered for future use
     * @param licenseKey license key provided by Maxmind to access distributed service
     */
    public LookupService(int options,String licenseKey) throws IOException {
        this.licenseKey = licenseKey;
        dnsService = 1;
        init();
    }
    /**
     * Create a new lookup service using the specified database file.
     *
     * @param databaseFile String representation of the database file.
     * @throws java.io.IOException if an error occured creating the lookup service
     *      from the database file.
     */
    public LookupService(String databaseFile) throws IOException {
        this(new File(databaseFile));
    }

    /**
     * Create a new lookup service using the specified database file.
     *
     * @param databaseFile the database file.
     * @throws java.io.IOException if an error occured creating the lookup service
     *      from the database file.
     */
    public LookupService(File databaseFile) throws IOException {
        this.databaseFile = databaseFile;
        this.file = new RandomAccessFile(databaseFile, "r");
        init();
    }

    /**
     * Create a new lookup service using the specified database file.
     *
     * @param databaseFile String representation of the database file.
     * @param options database flags to use when opening the database
     * GEOIP_STANDARD read database from disk
     * GEOIP_MEMORY_CACHE cache the database in RAM and read it from RAM
     * @throws java.io.IOException if an error occured creating the lookup service
     *      from the database file.
     */
    public LookupService(String databaseFile, int options) throws IOException{
        this(new File(databaseFile),options);
    }

    /**
     * Create a new lookup service using the specified database file.
     *
     * @param databaseFile the database file.
     * @param options database flags to use when opening the database
     * GEOIP_STANDARD read database from disk
     * GEOIP_MEMORY_CACHE cache the database in RAM and read it from RAM
     * @throws java.io.IOException if an error occured creating the lookup service
     *      from the database file.
     */
    public LookupService(File databaseFile, int options) throws IOException{
        this.databaseFile = databaseFile;
	this.file = new RandomAccessFile(databaseFile, "r");
	dboptions = options;
	init();
    }
    /**
     * Reads meta-data from the database file.
     *
     * @throws java.io.IOException if an error occurs reading from the database file.
     */
    private void init() throws IOException {
        int i, j;
        byte [] delim = new byte[3];
        byte [] buf = new byte[SEGMENT_RECORD_LENGTH];

	if (file == null) {
	    // distributed service only
	    for (i = 0; i < 233;i++){
		hashmapcountryCodetoindex.put(countryCode[i],new Integer(i));
		hashmapcountryNametoindex.put(countryName[i],new Integer(i));
	    }
	    return;
	}
	if ((dboptions & GEOIP_CHECK_CACHE) != 0) {
            mtime = databaseFile.lastModified();
	}
	file.seek(file.length() - 3);
        for (i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) {
            file.read(delim);
            if (delim[0] == -1 && delim[1] == -1 && delim[2] == -1) {
                databaseType = file.readByte();
                if (databaseType >= 106) {
                    // Backward compatibility with databases from April 2003 and earlier
                    databaseType -= 105;
                }
                // Determine the database type.
                if (databaseType == DatabaseInfo.REGION_EDITION_REV0) {
                    databaseSegments = new int[1];
                    databaseSegments[0] = STATE_BEGIN_REV0;
                    recordLength = STANDARD_RECORD_LENGTH;
                }else if (databaseType == DatabaseInfo.REGION_EDITION_REV1){
                    databaseSegments = new int[1];
                    databaseSegments[0] = STATE_BEGIN_REV1;
                    recordLength = STANDARD_RECORD_LENGTH;
		}
                else if (databaseType == DatabaseInfo.CITY_EDITION_REV0 ||
			 databaseType == DatabaseInfo.CITY_EDITION_REV1 ||
			 databaseType == DatabaseInfo.ORG_EDITION ||
			 databaseType == DatabaseInfo.ISP_EDITION ||
			 databaseType == DatabaseInfo.ASNUM_EDITION) {
			databaseSegments = new int[1];
			databaseSegments[0] = 0;
			if (databaseType == DatabaseInfo.CITY_EDITION_REV0 ||
			    databaseType == DatabaseInfo.CITY_EDITION_REV1 ||
			    databaseType == DatabaseInfo.ASNUM_EDITION) {
			    recordLength = STANDARD_RECORD_LENGTH;
			}
			else {
			    recordLength = ORG_RECORD_LENGTH;
			}
			file.read(buf);
			for (j = 0; j < SEGMENT_RECORD_LENGTH; j++) {
			    databaseSegments[0] += (unsignedByteToInt(buf[j]) << (j * 8));
			}
		    }
                break;
            }
            else {
                file.seek(file.getFilePointer() - 4);
            }
        }
        if ((databaseType == DatabaseInfo.COUNTRY_EDITION) |
	    (databaseType == DatabaseInfo.PROXY_EDITION) |
	    (databaseType == DatabaseInfo.NETSPEED_EDITION)) {
            databaseSegments = new int[1];
            databaseSegments[0] = COUNTRY_BEGIN;
            recordLength = STANDARD_RECORD_LENGTH;
        }
        if ((dboptions & GEOIP_MEMORY_CACHE) == 1) {
	    int l = (int) file.length();
	    dbbuffer = new byte[l];
	    file.seek(0);
	    file.read(dbbuffer,0,l);
	    databaseInfo = this.getDatabaseInfo();
	    file.close();
	}
        if ((dboptions & GEOIP_INDEX_CACHE) != 0) {
          int l = databaseSegments[0] * recordLength * 2;
          index_cache = new byte[l];
          if (index_cache != null){
            file.seek(0);
            file.read(index_cache,0,l);     
          }          
        } else {
          index_cache = null;
        }
     }

    /**
     * Closes the lookup service.
     */
    public void close() {
	try {
	    if (file != null){
		file.close();
	    }
            file = null;
        }
        catch (Exception e) { }
    }

    /**
     * Returns the country the IP address is in.
     *
     * @param ipAddress String version of an IP address, i.e. "127.0.0.1"
     * @return the country the IP address is from.
     */
    public Country getCountry(String ipAddress) {
	InetAddress addr;
	try {
	    addr = InetAddress.getByName(ipAddress);
	}
	catch (UnknownHostException e) {
            return UNKNOWN_COUNTRY;
	}
	return getCountry(bytesToLong(addr.getAddress()));
    }

    /**
     * Returns the country the IP address is in.
     *
     * @param ipAddress the IP address.
     * @return the country the IP address is from.
     */
    public Country getCountry(InetAddress ipAddress) {
        return getCountry(bytesToLong(ipAddress.getAddress()));
    }

    /**
     * Returns the country the IP address is in.
     *
     * @param ipAddress the IP address in long format.
     * @return the country the IP address is from.
     */
    public Country getCountry(long ipAddress) {
        if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) {
            throw new IllegalStateException("Database has been closed.");
        }
        int ret = seekCountry(ipAddress) - COUNTRY_BEGIN;
        if (ret == 0) {
            return UNKNOWN_COUNTRY;
        }
        else {
            return new Country(countryCode[ret], countryName[ret]);
        }
    }

    public int getID(String ipAddress) {
        InetAddress addr;
        try {
            addr = InetAddress.getByName(ipAddress);
        }
        catch (UnknownHostException e) {
            return 0;
        }
        return getID(bytesToLong(addr.getAddress()));
    }

    public int getID(InetAddress ipAddress) {
        return getID(bytesToLong(ipAddress.getAddress()));
    }

    public int getID(long ipAddress) {
        if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) {
            throw new IllegalStateException("Database has been closed.");
        }
	int ret = seekCountry(ipAddress) - databaseSegments[0];
	return ret;
    }

    /**
     * Returns information about the database.
     *
     * @return database info.
     */
    public DatabaseInfo getDatabaseInfo() {
        if (databaseInfo != null) {
            return databaseInfo;
        }
        try {
            // Synchronize since we're accessing the database file.
            synchronized (this) {
            _check_mtime();
                boolean hasStructureInfo = false;
                byte [] delim = new byte[3];
                // Advance to part of file where database info is stored.
                file.seek(file.length() - 3);
                for (int i=0; i<STRUCTURE_INFO_MAX_SIZE; i++) {
                    file.read(delim);
                    if (delim[0] == 255 && delim[1] == 255 && delim[2] == 255) {
                        hasStructureInfo = true;
                        break;
                    }
                }
                if (hasStructureInfo) {
                    file.seek(file.getFilePointer() - 3);
                }
                else {
                    // No structure info, must be pre Sep 2002 database, go back to end.
                    file.seek(file.length() - 3);
                }
                // Find the database info string.
                for (int i=0; i<DATABASE_INFO_MAX_SIZE; i++) {
                    file.read(delim);
                    if (delim[0]==0 && delim[1]==0 && delim[2]==0) {
                        byte[] dbInfo = new byte[i];
                        file.read(dbInfo);
                        // Create the database info object using the string.
                        this.databaseInfo = new DatabaseInfo(new String(dbInfo));
                        return databaseInfo;
                    }
                    file.seek(file.getFilePointer() -4);
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return new DatabaseInfo("");
    }

    synchronized void _check_mtime(){
      try {
        if ((dboptions & GEOIP_CHECK_CACHE) != 0){
          long t = databaseFile.lastModified();
          if (t != mtime){
            /* GeoIP Database file updated */
            /* refresh filehandle */
            file.close();
            file = new RandomAccessFile(databaseFile,"r");
	    databaseInfo = null;
	    init();
          }
        }
      } catch (IOException e) {
        System.out.println("file not found");
      }
    }

    // for GeoIP City only
    public Location getLocation(InetAddress addr) {
        return getLocation(bytesToLong(addr.getAddress()));
    }

    // for GeoIP City only
    public Location getLocation(String str) {
        if (dnsService == 0) {
            InetAddress addr;
            try {
                addr = InetAddress.getByName(str);
            }
            catch (UnknownHostException e) {
                return null;
            }

            return getLocation(addr);
        } else {
            String str2 = getDnsAttributes(str);
 	    return getLocationwithdnsservice(str2);
	    // TODO if DNS is not available, go to local file as backup
	}
    }

    String getDnsAttributes(String ip) {
        try {
            Hashtable env = new Hashtable();
            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
	    // TODO don't specify ws1, instead use ns servers for s.maxmind.com
            env.put("java.naming.provider.url","dns://ws1.maxmind.com/");

            DirContext ictx = new InitialDirContext(env);
            Attributes attrs = ictx.getAttributes(licenseKey + "." + ip + ".s.maxmind.com", new String[] {"txt"});
            //System.out.println(attrs.get("txt").get());
            String str = attrs.get("txt").get().toString();
            return str;
        }
        catch(NamingException e) {
	    // TODO fix this to handle exceptions
            System.out.println("DNS error");
            return null;
        }

    }

    public Location getLocationwithdnsservice(String str) {
        Location record = new Location();
        String key;
        String value;
        StringTokenizer st = new StringTokenizer(str,";=\""); 
        while (st.hasMoreTokens()) {
	    key = st.nextToken();
            if (st.hasMoreTokens()) {
                value = st.nextToken();
            } else {
	        value = "";}
	    if (key.equals("co")) {
		Integer i = (Integer)hashmapcountryCodetoindex.get(value);
		record.countryCode = value;
		record.countryName = countryName[i.intValue()];
	    }
	    if (key.equals("ci")) {
		record.city = value;
	    }
	    if (key.equals("re")) {
		record.region = value;
	    }
	    if (key.equals("zi")) {
	        record.postalCode = value;
	    }
	    // TODO, ISP and Organization
	    //if (key.equals("or")) {
	    //record.org = value;
	    //}
	    //if (key.equals("is")) {
	    //record.isp = value;
	    //}
	    if (key.equals("la")) {
		try{
		    record.latitude = Float.parseFloat(value);
		} catch(NumberFormatException e) {
		    record.latitude = 0;
		}
	    }
	    if (key.equals("lo")) {
		try{
		    record.longitude = Float.parseFloat(value);
		} catch(NumberFormatException e) {
		    record.latitude = 0;
		}
	    }
	    if (key.equals("dm")) {
		try{
		    record.dma_code = Integer.parseInt(value);
		} catch(NumberFormatException e) {
		    record.dma_code = 0;
		}
	    }
	    if (key.equals("ac")) {
		try{
		    record.area_code = Integer.parseInt(value);
		} catch(NumberFormatException e) {
		    record.area_code = 0;
		}
	    }
	}
        return record;
    }

    public synchronized Region getRegion(String str) {
	InetAddress addr;
	try {
	    addr = InetAddress.getByName(str);
	}
	catch (UnknownHostException e) {
	    return null;
	}

	return getRegion(bytesToLong(addr.getAddress()));
    }

    public synchronized Region getRegion(long ipnum) {
        Region record = new Region();
        int seek_region = 0;
        if (databaseType == DatabaseInfo.REGION_EDITION_REV0) {
            seek_region = seekCountry(ipnum) - STATE_BEGIN_REV0;
            char ch[] = new char[2];
            if (seek_region >= 1000) {
                record.countryCode = "US";
                record.countryName = "United States";
                ch[0] = (char)(((seek_region - 1000)/26) + 65);
                ch[1] = (char)(((seek_region - 1000)%26) + 65);
	        record.region = new String(ch);
            } else {
                record.countryCode = countryCode[seek_region];
                record.countryName = countryName[seek_region];
                record.region = "";
            }
        } else if (databaseType == DatabaseInfo.REGION_EDITION_REV1) {
            seek_region = seekCountry(ipnum) - STATE_BEGIN_REV1;
            char ch[] = new char[2];
            if (seek_region < US_OFFSET) {
                record.countryCode = "";
                record.countryName = "";
	        record.region = "";
            } else if (seek_region < CANADA_OFFSET) {
                record.countryCode = "US";
                record.countryName = "United States";
                ch[0] = (char)(((seek_region - US_OFFSET)/26) + 65);
                ch[1] = (char)(((seek_region - US_OFFSET)%26) + 65);
	        record.region = new String(ch);
            } else if (seek_region < WORLD_OFFSET) {
                record.countryCode = "CA";
                record.countryName = "Canada";
                ch[0] = (char)(((seek_region - CANADA_OFFSET)/26) + 65);
                ch[1] = (char)(((seek_region - CANADA_OFFSET)%26) + 65);
	        record.region = new String(ch);
            } else {
                record.countryCode = countryCode[(seek_region - WORLD_OFFSET) / FIPS_RANGE];
                record.countryName = countryName[(seek_region - WORLD_OFFSET) / FIPS_RANGE];
                record.region = "";
            }
	}
	return record;
    }

    public synchronized Location getLocation(long ipnum) {
        int record_pointer;
        byte record_buf[] = new byte[FULL_RECORD_LENGTH];
        int record_buf_offset = 0;
        Location record = new Location();
        int str_length = 0;
        int j, seek_country;
        double latitude = 0, longitude = 0;

        try {
            seek_country = seekCountry(ipnum);
            if (seek_country == databaseSegments[0]) {
                return null;
            }
            record_pointer = seek_country + (2 * recordLength - 1) * databaseSegments[0];

            if ((dboptions & GEOIP_MEMORY_CACHE) == 1) {
                //read from memory
                for (int i = 0; i < FULL_RECORD_LENGTH; i++) {
                    record_buf[i] = dbbuffer[i+record_pointer];
	        }
            } else {
                //read from disk
                file.seek(record_pointer);
                file.read(record_buf);
            }

            // get country
            record.countryCode = countryCode[unsignedByteToInt(record_buf[0])];
            record.countryName = countryName[unsignedByteToInt(record_buf[0])];
            record_buf_offset++;

            // get region
            while (record_buf[record_buf_offset + str_length] != '\0')
                str_length++;
            if (str_length > 0) {
                record.region = new String(record_buf, record_buf_offset, str_length);
            }
            record_buf_offset += str_length + 1;
            str_length = 0;

            // get city
            while (record_buf[record_buf_offset + str_length] != '\0')
                str_length++;
            if (str_length > 0) {
                record.city = new String(record_buf, record_buf_offset, str_length, "ISO-8859-1");
            }
            record_buf_offset += str_length + 1;
            str_length = 0;

            // get postal code
            while (record_buf[record_buf_offset + str_length] != '\0')
                str_length++;
            if (str_length > 0) {
                record.postalCode = new String(record_buf, record_buf_offset, str_length);
            }
            record_buf_offset += str_length + 1;

            // get latitude
            for (j = 0; j < 3; j++)
                latitude += (unsignedByteToInt(record_buf[record_buf_offset + j]) << (j * 8));
            record.latitude = (float) latitude/10000 - 180;
            record_buf_offset += 3;

            // get longitude
            for (j = 0; j < 3; j++)
                longitude += (unsignedByteToInt(record_buf[record_buf_offset + j]) << (j * 8));
	    record.longitude = (float) longitude/10000 - 180;

	    record.dma_code = 0;
	    record.area_code = 0;
	    if (databaseType == DatabaseInfo.CITY_EDITION_REV1) {
		// get DMA code
		int dmaarea_combo = 0;
		if (record.countryCode == "US") {
		    record_buf_offset += 3;
		    for (j = 0; j < 3; j++)
			dmaarea_combo += (unsignedByteToInt(record_buf[record_buf_offset + j]) << (j * 8));
		    record.dma_code = dmaarea_combo/1000;
		    record.area_code = dmaarea_combo % 1000;
		}
            }
	}
	catch (IOException e) {
            System.err.println("IO Exception while seting up segments");
        }
        return record;
    }

    public String getOrg(InetAddress addr) {
        return getOrg(bytesToLong(addr.getAddress()));
    }

    public String getOrg(String str) {
        InetAddress addr;
	try {
	    addr = InetAddress.getByName(str);
	}
	catch (UnknownHostException e) {
            return null;
	}
	return getOrg(addr);
    }

    // GeoIP Organization and ISP Edition methods
    public synchronized String getOrg(long ipnum) {
        int seek_org;
        int record_pointer;
        int str_length = 0;
        byte [] buf = new byte[MAX_ORG_RECORD_LENGTH];
        String org_buf;

        try {
            seek_org = seekCountry(ipnum);
            if (seek_org == databaseSegments[0]) {
		return null;
            }

            record_pointer = seek_org + (2 * recordLength - 1) * databaseSegments[0];
            if ((dboptions & GEOIP_MEMORY_CACHE) == 1) {
                //read from memory
                for (int i = 0;i < FULL_RECORD_LENGTH;i++) {
                    buf[i] = dbbuffer[i+record_pointer];
	        }
            } else {
		//read from disk
                file.seek(record_pointer);
                file.read(buf);
            }
            while (buf[str_length] != '\0') {
		str_length++;
            }
            org_buf = new String(buf, 0, str_length, "ISO-8859-1");
            return org_buf;
        }
        catch (IOException e) {
            System.out.println("IO Exception");
            return null;
        }
    }

    /**
     * Finds the country index value given an IP address.
     *
     * @param ipAddress the ip address to find in long format.
     * @return the country index.
     */
    private synchronized int seekCountry(long ipAddress) {
	byte [] buf = new byte[2 * MAX_RECORD_LENGTH];
	int [] x = new int[2];
        int offset = 0;
        _check_mtime();
        for (int depth = 31; depth >= 0; depth--) {
            if ((dboptions & GEOIP_MEMORY_CACHE) == 1) {
		//read from memory
                for (int i = 0;i < 2 * MAX_RECORD_LENGTH;i++) {
		    buf[i] = dbbuffer[(2 * recordLength * offset)+i];
		}
            } else if ((dboptions & GEOIP_INDEX_CACHE) != 0) {
                //read from index cache
                for (int i = 0;i < 2 * MAX_RECORD_LENGTH;i++) {
		    buf[i] = index_cache[(2 * recordLength * offset)+i];
		}            
            } else {
		//read from disk 
		try {
                    file.seek(2 * recordLength * offset);
                    file.read(buf);
                }
                catch (IOException e) {
                    System.out.println("IO Exception");
                }
            }
            for (int i = 0; i<2; i++) {
                x[i] = 0;
                for (int j = 0; j<recordLength; j++) {
                    int y = buf[i*recordLength+j];
                    if (y < 0) {
                        y+= 256;
                    }
                    x[i] += (y << (j * 8));
                }
            }

            if ((ipAddress & (1 << depth)) > 0) {
                if (x[1] >= databaseSegments[0]) {
                    return x[1];
                }
                offset = x[1];
            }
            else {
                if (x[0] >= databaseSegments[0]) {
                    return x[0];
                }
                offset = x[0];
	    }
	}

        // shouldn't reach here
        System.err.println("Error seeking country while seeking " + ipAddress);
        return 0;
    }

    /**
     * Returns the long version of an IP address given an InetAddress object.
     *
     * @param address the InetAddress.
     * @return the long form of the IP address.
     */
    private static long bytesToLong(byte [] address) {
        long ipnum = 0;
        for (int i = 0; i < 4; ++i) {
            long y = address[i];
            if (y < 0) {
                y+= 256;
            }
            ipnum += y << ((3-i)*8);
        }
        return ipnum;
    }

    private static int unsignedByteToInt(byte b) {
        return (int) b & 0xFF;
    }
}
