/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2007 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
 * 
 *  --- SVN Information ---
 *  $Id: Server.java 3859 2007-07-01 20:15:19Z gregork $
 */
package phex.net;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.TimerTask;

import phex.common.Environment;
import phex.common.ThreadPool;
import phex.common.address.IpAddress;
import phex.host.HostManager;
import phex.host.NetworkHostsContainer;
import phex.msg.MsgManager;
import phex.prefs.core.ConnectionPrefs;
import phex.prefs.core.NetworkPrefs;
import phex.utils.NLogger;

/**
 *
 */
public abstract class Server implements Runnable
{
    protected ServerSocket serverSocket;

    protected boolean isRunning;
    /**
     * Indicates if a incomming connection was seen
     */
    protected boolean hasConnectedIncomming;
    /**
     * The time the last incomming connection was seen.
     */
    protected long lastInConnectionTime;
    /**
     * The last time a TCP connect back reqest check was sent.
     */
    protected long lastFirewallCheckTime;
    
    public Server()
    {
        hasConnectedIncomming = ConnectionPrefs.HasConnectedIncomming.get().booleanValue();
        lastInConnectionTime = -1;
        isRunning = false;
        
        Environment.getInstance().scheduleTimerTask( 
            new FirewallCheckTimer(), FirewallCheckTimer.TIMER_PERIOD,
            FirewallCheckTimer.TIMER_PERIOD );
    }

    public synchronized void startup() throws IOException
    {
        if (isRunning)
        {
            return;
        }
        NLogger.debug( Server.class, "Starting listener");
        isRunning = true;
        
        bind();
        
        ThreadPool.getInstance().addJob(this,
            "IncommingListener-" + Integer.toHexString(hashCode()));
    }
    
    protected abstract void bind() throws IOException;
    protected abstract void closeServer();

    public synchronized void restart() throws IOException
    {
        shutdown( true );
        startup();
    }

    public synchronized void shutdown(boolean waitForCompleted)
    {
        // not running, already dead or been requested to die.
        if (!isRunning)
        {
            return;
        }
        NLogger.debug( Server.class, "Shutting down listener");
        
        ConnectionPrefs.HasConnectedIncomming.set( new Boolean( hasConnectedIncomming ) );
        
        closeServer();
        
        if (waitForCompleted)
        {
            // Wait until the thread is dead.
            while (isRunning)
            {
                try
                {
                    wait();
                }
                catch (InterruptedException e)
                {
                    break;
                }
            }
        }
    }

    public boolean getRunning()
    {
        return isRunning;
    }

    public boolean hasConnectedIncoming()
    {
        return hasConnectedIncomming;
    }
    
    public IpAddress resolveLocalHostIP()
    {
        byte[] ip = null;
        InetAddress addr = serverSocket.getInetAddress();
        ip = addr.getAddress();
        if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0)
        {
            ip = IpAddress.LOCAL_HOST_IP;
        }
        IpAddress ipAddress = new IpAddress( ip );
        return ipAddress;
    }
    
    public int getListeningLocalPort()
    {
        if ( serverSocket != null )
        {
            return serverSocket.getLocalPort();
        }
        else
        {
            return NetworkPrefs.ListeningPort.get().intValue();
        }
    }
    
    public void resetFirewallCheck()
    {
        lastFirewallCheckTime = 0;
    }

    private class FirewallCheckTimer extends TimerTask
    {
        // once per 5 minutes
        public static final long TIMER_PERIOD = 1000 * 60 * 5;

        // but only when at least 30 minutes have passed since last good known
        // status.
        private static final long CHECK_TIME = 1000 * 60 * 30;

        @Override
        public void run()
        {
            try
            {
                long now = System.currentTimeMillis();

                if ((hasConnectedIncomming && now - lastInConnectionTime > CHECK_TIME)
                    || (!hasConnectedIncomming && now - lastFirewallCheckTime > CHECK_TIME))
                {
                    NetworkHostsContainer netHostsContainer = HostManager
                        .getInstance().getNetworkHostsContainer();
                    if (netHostsContainer.getUltrapeerConnectionCount() <= 2)
                    {
                        return;
                    }
                    
                    boolean isRequestSent = MsgManager.getInstance().requestTCPConnectBack();
                    // in case no request was sent we just assume the last good
                    // known status.
                    if ( isRequestSent )
                    {
                        lastFirewallCheckTime = now;
                        Environment.getInstance().scheduleTimerTask(
                            new IncommingCheckRunner(),
                            IncommingCheckRunner.TIMER_PERIOD );
                    }
                }
            }
            catch (Throwable th)
            {
                NLogger.error( Server.class, th, th );
            }
        }
    }

    private class IncommingCheckRunner extends TimerTask
    {
        // after 60 sec.
        public static final long TIMER_PERIOD = 1000 * 60;

        @Override
        public void run()
        {
            try
            {
                long now = System.currentTimeMillis();
                if (now - lastInConnectionTime > TIMER_PERIOD)
                {
                    hasConnectedIncomming = false;
                }
            }
            catch ( Throwable th )
            {
                NLogger.error( Server.class, th, th );
            }
        }
    }
}
