import com.ip2location.*;  

import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;

import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Collections;
import java.util.Enumeration;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.handler.ContextHandler;

final public class FrostIP2Location extends ContextHandler {
    /** Default port */
    private int DEFAULT_PORT = 9999;
    private Server _server;
    private Connector _connector;

    /** HashSet to cache previous searches */
    private int MAX_CACHED_RESULTS = 4000;
    private Map<String,String> _cache;

    /** IP2Location component */
    IP2Location _loc;

    public void startService(int port) throws Exception {
	_cache = new HashMap<String,String>(20,(float) 0.40);

	_loc = new IP2Location();
	_loc.IPDatabasePath = "IP-COUNTRY-REGION-CITY-ISP.BIN";

        _server = new Server();
        _connector = new SelectChannelConnector();
        _connector.setPort(port);
        _server.setConnectors(new Connector[] {_connector});

        setContextPath("/");

        //Let's start with just one context handler for now.
        _server.setHandler(this);
	setServer(_server);
        _server.start();
        _server.join();
    } //startService

    public void startService() throws Exception {
        startService(DEFAULT_PORT);
    } //startService using default port


    /** The method that actually handles the HTTP Request */
    public void handle(String target,
                       HttpServletRequest request, 
                       HttpServletResponse response, 
                       int dispatch) throws IOException, ServletException {
	//get the request and standard response preparation
        Request base_request = (request instanceof Request) ? 
            (Request) request: HttpConnection.getCurrentConnection().getRequest();
        base_request.setHandled(true);
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);

	//Uncomment to debug the HTTP Request Headers
        //response.getWriter().println(base_request + "<hr>");

	//Depending on the paths sends a different response
	String path = base_request.getPathInfo();

        //Unless an IP is passed in the URL
	String remoteHost = base_request.getHeader("X-Forwarded-For");

	int commaOffset = -1;
	if (remoteHost != null && (commaOffset = remoteHost.indexOf(",")) >= 0) {
	    remoteHost = remoteHost.substring(0,commaOffset);
	} else if (remoteHost == null) {
	    remoteHost = base_request.getRemoteHost();
	}

	//Perform the IP Queries
	String result = null;

	try {
	    //Short Query on remote IP
	    if (path.equals("/")) {
		result = shortQuery(path,remoteHost);
	    }
            //Full Query on remote IP 
            else if (path.matches("^/all/?")) {
		result = allQuery(path,remoteHost);
	    }
	    //Short Query on arbitrary IP
	    else if (path.matches("^/\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3}/?")) {
		result = shortQuery(path,
				    path.substring(1,path.length()-1).replace("/",""));
	    }
            //Full Query on arbitrary IP 
            else if (path.matches("^/all/\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3}/?")) {
		result = allQuery(path,
				  path.substring(5,path.length()-1).replace("/",""));
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	    result=e.getMessage();
	}

	response.getWriter().println(result);
    } //handle

    private void tryPurgeCache() {
	//cache not full, don't do anything
	if (_cache.size() < MAX_CACHED_RESULTS) return;

	p("Cache Size Too Big! (" + _cache.size() + ")");

	//we wipe out 25%
	int elementsToRemove = (int) MAX_CACHED_RESULTS/4;

	for (int i =0; i < elementsToRemove; i++) {
	    String key = null;
	    try {
		key = _cache.keySet().iterator().next();
		_cache.remove(key);
	    } catch (Exception e) {
		System.out.println("Possibly a concurrency related issue has been caught.");
		e.printStackTrace();
	    }
	}
	
	p("Purged cache");
    }

    /** Returns only the Country name (Short and Long) */
    public String shortQuery(String path, String remoteHost) throws Exception {
	if (_cache.containsKey(path+":"+remoteHost)) {
	    p("+ cache hit shortQuery");
	    return _cache.get(path+":"+remoteHost);
	}

	IPResult result = _loc.IPQuery(remoteHost);
	String resultString = "country_short=" + result.getCountryShort() + "|" +
	    "country_name=" + result.getCountryLong();

        tryPurgeCache();
	try {
	    _cache.put(path+":"+remoteHost,resultString);
	} catch (Exception e) {
	}
	return resultString;
    }

    /** Returns all the information available about the remote host */
    public String allQuery(String path, String remoteHost) throws Exception{
	if (_cache.containsKey(path+":"+remoteHost)) {
	    p("+ cache hit allQuery");
	    return _cache.get(path+":"+remoteHost);
	}

	IPResult result = _loc.IPQuery(remoteHost);
	String resultString = "country_short=" + result.getCountryShort() + "|" +
	    "city=" + result.getCity() + "|" +
	    "region=" + result.getRegion() + "|" +
	    "country_name=" + result.getCountryLong() + "|" +
	    "isp_name=" + result.getISP() + "|" +
            "status=" + result.getStatus() ;
        tryPurgeCache();
	_cache.put(path+":"+remoteHost,resultString);
	return resultString;
    }


    static final public void p(Object o) {System.out.println(o);}

    static final public void testIP2LocationAverageSpeed(FrostIP2Location service) {
	//	    IP2Location loc = new IP2Location();
	//    loc.IPDatabasePath = "IP-COUNTRY-REGION-CITY-ISP.BIN";

	    int testCount = 0; //Only increased when there's no delay, and when the IP was actually found.
            long timeA=0;
            long timeB=0;
            long queryTime=0;
	    long totalTime=0;
            long avgTime=0;
            String ip = new String();
	    String queryResult=null;

            //Load IPs from Sample File.
	    try {
		BufferedReader br = new BufferedReader(new FileReader("random_ips"));
		//		IPResult rec = null;

		while ((ip=br.readLine())!=null) {
		    ip=ip.trim();
		    timeA = System.currentTimeMillis();
		    //rec = loc.IPQuery(ip);
		    queryResult = service.shortQuery("/",ip);
		    timeB = System.currentTimeMillis();

		    /*
		    if (!rec.getStatus().equals("OK") ||
			rec.getDelay()) {
			System.out.print("-");
			continue;
		    }
		    */

		    testCount++;
		    queryTime = timeB - timeA;
		    totalTime += queryTime;
		    avgTime = totalTime/testCount;

		    //p(queryResult);
		    p(avgTime);

		}//while
	    } catch (Exception e) {
		e.printStackTrace();
	    }

	    //	    loc.close();
    }

    static final public void main(String[] args) {
	System.out.println("IP2Location started...");
        
	try {
   	    FrostIP2Location ip2location = new FrostIP2Location();
            ip2location.startService();

	    //enable if you want to run a test on the console to see how fast ips are queried.
	    //testIP2LocationAverageSpeed(ip2location);
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }

}