/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2006 Phex Development Group
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  --- CVS Information ---
 *  $Id: Ip2CountryManager.java 3362 2006-03-30 22:27:26Z gregork $
 */
package phex.common;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.collections.list.UnmodifiableList;

import phex.common.AbstractManager;
import phex.common.address.AddressUtils;
import phex.common.address.IpAddress;
import phex.utils.*;

/**
 * 
 */
public class Ip2CountryManager extends AbstractManager
{   
    /**
     * Indicates if the database is fully loaded or not.
     */
    private boolean isLoaded;
    private List ipCountryRangeList;
    
    

    private Ip2CountryManager()
    {
        isLoaded = false;
        ipCountryRangeList = new ArrayList();
    }

    static private class Holder
    {
       static protected final Ip2CountryManager manager = new Ip2CountryManager();
    }

    static public Ip2CountryManager getInstance()
    {
        return Ip2CountryManager.Holder.manager;
    }

    /**
     * This method is called in order to initialize the manager.  Inside
     * this method you can't rely on the availability of other managers.
     * @return true if initialization was successful, false otherwise.
     */
    public boolean initialize()
    {
        return true;
    }

    /**
     * This method is called in order to perform post initialization of the
     * manager. This method includes all tasks that must be done after initializing
     * all the several managers. Inside this method you can rely on the
     * availability of other managers.
     * @return true if initialization was successful, false otherwise.
     */
    public boolean onPostInitialization()
    {
        return true;
    }
    
    /**
     * This method is called after the complete application including GUI completed
     * its startup process. This notification must be used to activate runtime
     * processes that needs to be performed once the application has successfully
     * completed startup.
     */
    public void startupCompletedNotify()
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                loadIp2CountryDB();
            }
        };
        ThreadPool.getInstance().addJob( runnable, "IP2CountryLoader" );
    }

    /**
     * This method is called in order to cleanly shutdown the manager. It
     * should contain all cleanup operations to ensure a nice shutdown of Phex.
     */
    public void shutdown(){}
    
    /**
     * Returns the country code if found, empty string if not found, and null
     * if DB has not been loaded yet.
     * @param address
     * @return
     */
    public String getCountryCode( IpAddress address )
    {
        if ( !isLoaded )
        {
            return null;
        }
        IpCountryRange range = binarySearch( address.getHostIP() );
        if ( range == null )
        {
            return "";
        }
        return range.countryCode;
    }
    
    private void loadIp2CountryDB()
    {
        InputStream inStream = ClassLoader.getSystemResourceAsStream(
            "phex/resources/ip2country.csv" );
        if ( inStream == null )
        {
            Logger.logMessage( Logger.FINE, Logger.NETWORK,
                "Default GWebCache file not found." );
            return;
        }
        BufferedReader reader = new BufferedReader( new InputStreamReader( inStream ) );
        
        ArrayList initialList = new ArrayList( 5000 );
        IpCountryRange range;
        String line;
        try
        {
            line = reader.readLine();
            while( line != null )
            {
                range = new IpCountryRange( line );
                initialList.add( range );
                line = reader.readLine();
            }
        }
        catch (IOException exp)
        {
            NLogger.error( NLoggerNames.GLOBAL, exp, exp );
        }
        finally
        {
            IOUtil.closeQuietly(reader);
        }
        initialList.trimToSize();
        Collections.sort( initialList );
        ipCountryRangeList = UnmodifiableList.decorate( initialList );
        isLoaded = true;
    }
    
    private IpCountryRange binarySearch( byte[] hostIp )
    {
        int low = 0;
        int high = ipCountryRangeList.size() - 1;
    
        while (low <= high)
        {
            int mid = (low + high) >> 1;
            IpCountryRange midVal = (IpCountryRange)ipCountryRangeList.get( mid );
            int cmp = midVal.compareHostAddress( hostIp );
            if (cmp < 0)
            {
                low = mid + 1;
            }
            else if (cmp > 0)
            {
                high = mid - 1;
            }
            else
            {
                return midVal; // key found
            }
        }
        return null;  // key not found
    }
    
    private class IpCountryRange implements Comparable
    {
        byte[] from;
        byte[] to;
        String countryCode;
        
        public IpCountryRange( String line )
        {
            // "33996344","33996351","GB"
            int startIdx, endIdx;
            startIdx = 0;
            
            endIdx = line.indexOf( (int)',', startIdx );
            from = AddressUtils.parseIntIP( line.substring( startIdx, endIdx ) );
            
            startIdx = endIdx + 1;
            endIdx = line.indexOf( (int)',', startIdx );
            to = AddressUtils.parseIntIP( line.substring( startIdx, endIdx ) );
            
            startIdx = endIdx + 1;
            countryCode = line.substring( startIdx );            
        }
        
        public int compareHostAddress( byte[] hostIp )
        {
            long hostIpL;
            hostIpL = IOUtil.unsignedInt2Long(
                IOUtil.deserializeInt( hostIp, 0));
            long fromIpL = IOUtil.unsignedInt2Long( IOUtil.deserializeInt( from, 0 ) );
            long cmp = hostIpL - fromIpL;
            if ( cmp == 0 )
            {
                return 0;
            }
            if ( cmp < 0 )
            {// host Ip is lower..
                return 1;
            }
            
            // validate to range..
            long toIpL = IOUtil.unsignedInt2Long( IOUtil.deserializeInt( to, 0 ) );
            cmp = hostIpL - toIpL;
            if ( cmp == 0 || cmp < 0)
            {// we are between from and to
                return 0;
            }
            else
            {// host Ip is higher..
                return -1;
            }
        }
        
        public int compareTo(Object o)
        {
            if ( o == this )
            {
                return 0;
            }
            IpCountryRange range = (IpCountryRange)o;
            
            byte[] ip1 = (byte[])from;
            byte[] ip2 = (byte[])range.from;

            long ip1l = IOUtil.unsignedInt2Long( IOUtil.deserializeInt( ip1, 0 ) );
            long ip2l = IOUtil.unsignedInt2Long( IOUtil.deserializeInt( ip2, 0 ) );

            if ( ip1l < ip2l )
            {
                return -1;
            }
            // only if rate and object is equal return 0
            else
            {
                return 1;
            }
        }
    }
}
