/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2004 Gregor Koukkoullis ( phex <at> kouk <dot> de )
 *
 *  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
 */
package phex.upload;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import phex.common.ThreadPool;
import phex.common.bandwidth.BandwidthController;
import phex.common.bandwidth.BandwidthManager;
import phex.connection.Connection;
import phex.connection.SocketProvider;
import phex.http.HTTPMessageException;
import phex.http.HTTPProcessor;
import phex.http.HTTPRequest;
import phex.msg.MsgPushRequest;
import phex.share.ShareFile;
import phex.share.ShareManager;
import phex.statistic.UploadDownloadCountStatistic;
import phex.utils.GnutellaInputStream;
import phex.utils.IOUtil;
import phex.utils.Logger;
import phex.utils.URLCodecUtils;

public class PushWorker implements Runnable
{
    private static final int PUSH_TIMEOUT = 45000;

    private MsgPushRequest pushMsg;

    private Connection connection;

    public PushWorker(MsgPushRequest msg)
    {
        pushMsg = msg;
        //new Thread(this, "PushWorker-" + Integer.toHexString(hashCode())).start();
        ThreadPool.getInstance().addJob( this,
            "PushWorker-" + Integer.toHexString( hashCode() ) );
        UploadDownloadCountStatistic.pushUploadAttempts.increment( 1 );
    }

    public void run()
    {
        HTTPRequest httpRequest;
        try
        {
            httpRequest = connectAndGetRequest();
            if ( httpRequest == null )
            {
                UploadDownloadCountStatistic.pushUploadFailure.increment( 1 );
                return;
            }
            handleRequest( httpRequest );

            Logger.logMessage( Logger.FINER, Logger.UPLOAD,
                "PushWorker finished" );
        }
        catch (Exception exp)
        {
            Logger.logError( exp );
            return;
        }
        finally
        {
            if ( connection != null )
            {
                connection.disconnect();
            }
        }
    }

    /**
     * @param httpRequest
     * @throws IOException
     */
    private void handleRequest(HTTPRequest httpRequest)
    {
        Logger.logMessage( Logger.FINE, Logger.UPLOAD, "Handle PUSH request: "
            + httpRequest.buildHTTPRequestString() );
        try
        {
            UploadDownloadCountStatistic.pushUploadSuccess.increment( 1 );
            if ( httpRequest.isGnutellaRequest() )
            {
                UploadManager.getInstance().handleUploadRequest(
                    connection, httpRequest );
            }
            else
            {
                // Handle the HTTP GET as a normal HTTP GET to upload file.
                // This is most likely a usual browse host request.
                ShareManager.getInstance().httpRequestHandler( connection,
                    httpRequest );
            }
        }
        catch (IOException exp)
        {
            Logger.logMessage( Logger.FINER, Logger.UPLOAD, exp );
        }
    }

    /**
     * @return
     */
    private HTTPRequest connectAndGetRequest()
    {
        try
        {
            HTTPRequest httpRequest;
            Logger.logMessage( Logger.FINE, Logger.UPLOAD,
                "Try PUSH connect to: " + pushMsg.getRequestAddress() );
            Socket sock = SocketProvider.connect( pushMsg.getRequestAddress(),
                PUSH_TIMEOUT );
            BandwidthController bwController = BandwidthManager.getInstance()
                .getUploadBandwidthController();
            connection = new Connection( sock, bwController );
            sendGIV( connection );
            GnutellaInputStream inStream = connection.getInputStream();
            if ( inStream == null )
            {
                // IS from getIs() has been cleared when connection closed.
                throw new IOException(
                    "Disconnected from remote host during initial handshake" );
            }
            httpRequest = HTTPProcessor.parseHTTPRequest( inStream );
            return httpRequest;
        }
        catch (IOException exp)
        {
            Logger.logMessage( Logger.FINER, Logger.UPLOAD, exp );
            return null;
        }
        catch (HTTPMessageException exp)
        {
            Logger.logMessage( Logger.FINER, Logger.UPLOAD, exp );
            return null;
        }
    }

    /**
     * @param connection
     * @param sfile
     * @throws IOException
     */
    private void sendGIV(Connection connection) throws IOException
    {
        ShareManager shareMgr = ShareManager.getInstance();
        // I only give out file indexes in the int range
        ShareFile sfile = shareMgr.getFileAdministration().getFileByIndex(
            (int) pushMsg.getFileIndex() );

        // Send the push greeting.
        // GIV <file-ref-num>:<ClientID GUID in hexdec>/<filename>\n\n
        StringBuffer buffer = new StringBuffer( 100 );
        buffer.append( "GIV " );
        buffer.append( pushMsg.getFileIndex() );
        buffer.append( ':' );
        buffer.append( pushMsg.getClientGUID().toHexString() );
        buffer.append( '/' );
        if ( sfile != null )
        {
            buffer.append( URLCodecUtils.encodeURL( sfile.getFileName() ) );
        }
        Logger.logMessage( Logger.FINE, Logger.UPLOAD, "Send GIV: "
            + buffer.toString() );
        byte[] buf = new byte[buffer.length() + 2];
        int len = IOUtil.serializeString( buffer.toString(), buf, 0 );
        buf[len++] = (byte) '\n';
        buf[len++] = (byte) '\n';
        OutputStream outStream = connection.getOutputStream();
        outStream.write( buf, 0, len );
        outStream.flush();
    }
}