package org.wikiwebserver.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.ParseException;
import java.util.LinkedHashMap;
import java.util.Map;

public class IPToCountry {
    
    // "http://software77.net/cgi-bin/ip-country/geo-ip.pl?action=download";

    public static String getCountryName(String address) {
        try {
            IPToCountryBlock b = getIPToCountryBlock(address);
            if (b == null) return null;
            return b.countryName;
        } catch (ParseException ex) {
            return "Unknown";
        }
    }

    public static IPToCountryBlock getIPToCountryBlock(String address) throws ParseException {
        
        if (address == null) return null;
        
        long search = getLongFromIP(address.trim());
        IPToCountryBlock b = null;
        synchronized (memCache) {
            b = memCache.get(address);
        }
        if (b == null) {
            try {
                b = binaryFileSeek(search);
            } catch (FileNotFoundException ex) {
                return null;
            }
            catch (IOException ex) {
                ex.printStackTrace();
                return null;
            }                
            if (b != null) {
                synchronized (memCache) {
                    memCache.put(address, b);
                }
            }
        }
        return b;
        
    }
    
    public static long getLongFromIP(String ip) throws ParseException {

        String[] parts = ip.split("\\.");

        return (long)parseIPSegment(parts[0]) * 256 * 256 * 256 +
               parseIPSegment(parts[1]) * 256 * 256 +
               parseIPSegment(parts[2]) * 256 +
               parseIPSegment(parts[3]);
    }
    
    public static String getIPFromLong(long l) throws ParseException {

        return ((l & 0xFF000000) >> 24) + "." +
               ((l & 0x00FF0000) >> 16) + "." +
               ((l & 0x0000FF00) >> 8) +  "." +
               (l & 0x000000FF);
    }    
    
    private static int parseIPSegment(String segment) throws ParseException {
        try {
            int i = Integer.parseInt(segment.trim());
            if (i >= 0 && i < 256) return i;
        } catch (Exception ex) {
            ParseException pe = new ParseException("Not an IPv4 address", 0);
            pe.initCause(ex);
            throw pe;
        }
        
        throw new ParseException("Not an IPv4 address", 0);
    }
    
    private static IPToCountryBlock binaryFileSeek(long search) throws IOException {
        File file = new File("lib/IpToCountry.csv");
        if (!file.exists()) return null;
            
        RandomAccessFile f = new RandomAccessFile(file, "r");
        try {
            
            // Seek to start of real data
            String line = readLine(f);
            while (line != null && (line.length() == 0 || line.startsWith("#"))) {
                line = readLine(f);
            }
            
            long start = f.getFilePointer()-line.length()-1;
            long low = start;
            long high = f.length();
            
            while (low <= high) {
                long mid = (low + high) / 2;
                line = readLineAfter(f, start, mid);
                IPToCountryBlock b = new IPToCountryBlock(line);
                if (b.from > search) high = mid - 1;
                else if (b.to < search) low = mid + 1;
                else return b;
            }
        } finally {
            f.close();
        }
            
        return null;
    }
    
    private static String readLineAfter(RandomAccessFile f, long start, long index) throws IOException {
        
        f.seek(index-1);
        
        byte[] buffer = new byte[2*MAX_LINE_LENGTH];
        String s = new String(buffer, 0, f.read(buffer));
        int i0 = 0;
        i0 = s.indexOf('\n') + 1;
        int i1 = s.indexOf('\n', i0); 
        
        return s.substring(i0, i1);
    }    
    
    private static String readLine(RandomAccessFile f) throws IOException {
       
        long start = f.getFilePointer();
        byte[] buffer = new byte[MAX_LINE_LENGTH];
        String s = new String(buffer, 0, f.read(buffer));
        String line = s.substring(0, s.indexOf('\n'));
        f.seek(start+line.length()+1);
        
        return line;
    }
    
    public static class IPToCountryBlock {
        
        public long from, to;
        public String registry;
        public long timeAssigned;
        public String countryCode2, countryCode3, countryName;
        
        IPToCountryBlock(String csv) {
            csv = csv.replace("\"", "");
            String[] data = csv.split(",");
            
            int i = 0;
            from = Long.parseLong(data[i++]);
            to = Long.parseLong(data[i++]);
            registry = data[i++];
            timeAssigned = 1000 * Long.parseLong(data[i++]);
            countryCode2 = data[i++];
            countryCode3 = data[i++];
            countryName = data[i++];
            
            // If a country name contained commas, initial replace and split
            // would remove them. Put them back.
            while (data.length > i+1) {
                countryName += "," + data[i++];
            }
        }
    }   
    
    private static final Map<String, IPToCountryBlock> memCache 
                            = new LRUMap<String, IPToCountryBlock>(1000);
    
    private static final int MAX_LINE_LENGTH = 100;    
    
    private static final class LRUMap<K, L> extends LinkedHashMap<K, L> {
        
        private static final long serialVersionUID = 1l;  
        
        private int maxEntries;
        
        public LRUMap(int maxEntries) {
            super(100, 0.75f, true);
            this.maxEntries = maxEntries;
        }

        protected boolean removeEldestEntry(Map.Entry<K, L> eldest) {
           return size() > this.maxEntries;
        }
    }     
}

