text: var fs = require('fs');
  var net = require('net');
  var path = require('path');
  
  fs.existsSync = fs.existsSync || path.existsSync;
  
  var utils = require('./utils');
  var async = require('async');
  
  var geodatadir;
  
  if (typeof global.geodatadir === 'undefined'){
  	geodatadir = path.join(__dirname, '/../data/');
  } else {
  	geodatadir = global.geodatadir;
  }
  
  var dataFiles = {
  	country: path.join(geodatadir, 'geoip-country.dat'),
  	country6: path.join(geodatadir, 'geoip-country6.dat')
  };
  
  var privateRange4 = [
  		[utils.aton4('10.0.0.0'), utils.aton4('10.255.255.255')],
  		[utils.aton4('172.16.0.0'), utils.aton4('172.31.255.255')],
  		[utils.aton4('192.168.0.0'), utils.aton4('192.168.255.255')]
  	]
  
  var cache4 = {
  	firstIP: null,
  	lastIP: null,
  	lastLine: 0,
  	locationBuffer: null,
  	locationRecordSize: 64,
  	mainBuffer: null,
  	recordSize: 12
  };
  
  var cache6 = {
  	firstIP: null,
  	lastIP: null,
  	lastLine: 0,
  	mainBuffer: null,
  	recordSize: 58
  };
  
  var RECORD_SIZE = 10;
  var RECORD_SIZE6 = 34
  
  function lookup4(ip) {
  	var fline = 0;
  	var floor = cache4.lastIP;
  	var cline = cache4.lastLine;
  	var ceil = cache4.firstIP;
  	var line;
  	var locId;
  
  	var buffer = cache4.mainBuffer;
  	var locBuffer = cache4.locationBuffer;
  	var privateRange = privateRange4;
  	var recordSize = cache4.recordSize;
  	var locRecordSize = cache4.locationRecordSize;
  
  	var i;
  
  	var geodata = {
  		country: ''
  	};
  
  	// outside IPv4 range
  	if (ip > cache4.lastIP || ip < cache4.firstIP) {
  		return null;
  	}
  
  	// private IP
  	for (i = 0; i < privateRange.length; i++) {
  		if (ip >= privateRange[i][0] && ip <= privateRange[i][1]) {
  			return null;
  		}
  	}
  
  	do {
  		line = Math.round((cline - fline) / 2) + fline;
  		floor = buffer.readUInt32BE(line * recordSize);
  		ceil  = buffer.readUInt32BE((line * recordSize) + 4);
  
  		if (floor <= ip && ceil >= ip) {
  			if (recordSize === RECORD_SIZE) {
  				geodata.country = buffer.toString('utf8', (line * recordSize) + 8, (line * recordSize) + 10);
  			} else {
  				locId = buffer.readUInt32BE((line * recordSize) + 8) - 1;
  
  				geodata.country = locBuffer.toString('utf8', (locId * locRecordSize) + 0, (locId * locRecordSize) + 2).replace(/\u0000.*/, '');
  			}
  
  			return geodata;
  		} else if (fline === cline) {
  			return null;
  		} else if (fline === (cline - 1)) {
  			if (line === fline) {
  				fline = cline;
  			} else {
  				cline = fline;
  			}
  		} else if (floor > ip) {
  			cline = line;
  		} else if (ceil < ip) {
  			fline = line;
  		}
  	} while(1);
  }
  
  function lookup6(ip) {
  	var buffer = cache6.mainBuffer;
  	var recordSize = cache6.recordSize;
  
  	var geodata = {
  		country: ''
  	};
  
  	// XXX We only use the first 8 bytes of an IPv6 address
  	// This identifies the network, but not the host within
  	// the network.  Unless at some point of time we have a
  	// global peace treaty and single subnets span multiple
  	// countries, this should not be a problem.
  	function readip(line, offset) {
  		var ii = 0;
  		var ip = [];
  
  		for (ii = 0; ii < 2; ii++) {
  			ip.push(buffer.readUInt32BE((line * recordSize) + (offset * 16) + (ii * 4)));
  		}
  
  		return ip;
  	}
  
  	cache6.lastIP = readip(cache6.lastLine, 1);
  	cache6.firstIP = readip(0, 0);
  
  	var fline = 0;
  	var floor = cache6.lastIP;
  	var cline = cache6.lastLine;
  	var ceil = cache6.firstIP;
  	var line;
  
  	if (utils.cmp6(ip, cache6.lastIP) > 0 || utils.cmp6(ip, cache6.firstIP) < 0) {
  		return null;
  	}
  
  	do {
  		line = Math.round((cline - fline) / 2) + fline;
  		floor = readip(line, 0);
  		ceil  = readip(line, 1);
  
  		if (utils.cmp6(floor, ip) <= 0 && utils.cmp6(ceil, ip) >= 0) {
  			if (recordSize === RECORD_SIZE6) {
  				geodata.country = buffer.toString('utf8', (line * recordSize) + 32, (line * recordSize) + 34).replace(/\u0000.*/, '');
  			} else {
  				geodata.country = buffer.toString('utf8', (line * recordSize) + 32, (line * recordSize) + 34).replace(/\u0000.*/, '');
  			}
  
  			// We do not currently have detailed region/city info for IPv6, but finally have coords
  			return geodata;
  		} else if (fline === cline) {
  			return null;
  		} else if (fline === (cline - 1)) {
  			if (line === fline) {
  				fline = cline;
  			} else {
  				cline = fline;
  			}
  		} else if (utils.cmp6(floor, ip) > 0) {
  			cline = line;
  		} else if (utils.cmp6(ceil, ip) < 0) {
  			fline = line;
  		}
  	} while(1);
  }
  
  function get4mapped(ip) {
      var ipv6 = ip.toUpperCase();
      var v6prefixes = ['0:0:0:0:0:FFFF:', '::FFFF:'];
      for (var i = 0; i < v6prefixes.length; i++) {
          var v6prefix = v6prefixes[i];
          if (ipv6.indexOf(v6prefix) == 0) {
              return ipv6.substring(v6prefix.length);
          }
      }
      return null;
  }
  
  function preload() {
  	var datFile;
  	var datSize;
  	var asyncCache = {
  		firstIP: null,
  		lastIP: null,
  		lastLine: 0,
  		locationBuffer: null,
  		locationRecordSize: 64,
  		mainBuffer: null,
  		recordSize: 12
  	};
  
  	datFile = fs.openSync(dataFiles.country, 'r');
  	datSize = fs.fstatSync(datFile).size;
  	cache4.recordSize = RECORD_SIZE;
  
  	cache4.mainBuffer = new Buffer(datSize);
  	fs.readSync(datFile, cache4.mainBuffer, 0, datSize, 0);
  
  	fs.closeSync(datFile);
  
  	cache4.lastLine = (datSize / cache4.recordSize) - 1;
  	cache4.lastIP = cache4.mainBuffer.readUInt32BE((cache4.lastLine * cache4.recordSize) + 4);
  	cache4.firstIP = cache4.mainBuffer.readUInt32BE(0);
  }
  
  function preload6() {
  	var datFile;
  	var datSize;
  	var asyncCache6 = {
  		firstIP: null,
  		lastIP: null,
  		lastLine: 0,
  		mainBuffer: null,
  		recordSize: 58
  	};
  
  
  	datFile = fs.openSync(dataFiles.country6, 'r');
  	datSize = fs.fstatSync(datFile).size;
  	cache6.recordSize = RECORD_SIZE6;
  
  	cache6.mainBuffer = new Buffer(datSize);
  	fs.readSync(datFile, cache6.mainBuffer, 0, datSize, 0);
  
  	fs.closeSync(datFile);
  
  	cache6.lastLine = (datSize / cache6.recordSize) - 1;
  }
  
  module.exports = {
  	cmp: utils.cmp,
  
  	lookup: function(ip) {
  		if (!ip) {
  			return null;
  		} else if (typeof ip === 'number') {
  			return lookup4(ip);
  		} else if (net.isIP(ip) === 4) {
  			return lookup4(utils.aton4(ip));
  		} else if (net.isIP(ip) === 6) {
  			var ipv4 = get4mapped(ip);
  			if (ipv4) {
  				return lookup4(utils.aton4(ipv4));
  			} else {
  				return lookup6(utils.aton6(ip));
  			}
  		}
  
  		return null;
  	}
  };
  
  preload();
  preload6();
  