/*
 *  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: ShareManager.java 3859 2007-07-01 20:15:19Z gregork $
 */
package phex.share;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

import phex.common.AbstractManager;
import phex.common.Environment;
import phex.common.address.DestAddress;
import phex.common.format.NumberFormatUtils;
import phex.connection.NetworkManager;
import phex.http.HTTPHeader;
import phex.http.HTTPHeaderGroup;
import phex.http.HTTPHeaderNames;
import phex.http.HTTPRequest;
import phex.http.HTTPResponse;
import phex.io.buffer.ByteBuffer;
import phex.msg.GUID;
import phex.msg.MsgHeader;
import phex.msg.QueryResponseMsg;
import phex.msg.QueryResponseRecord;
import phex.net.connection.Connection;
import phex.prefs.core.BandwidthPrefs;
import phex.prefs.core.LibraryPrefs;
import phex.share.export.ExportEngine;
import phex.statistic.MessageCountStatistic;
import phex.utils.NLogger;
import phex.utils.StringUtils;

public class ShareManager extends AbstractManager
{   
    private SharedFilesService sharedFilesService;

    private ShareManager()
    {

    }

    /*
     * This idiom provides lazy and thread-safe instantiation of singletons.
     */
    static private class Holder
    {
        static protected final ShareManager manager = new ShareManager();
    }

    /**
     * Returns the singleton instance of this class.
     * @return the only ShareManager instance.
     */
    static public ShareManager getInstance()
    {
      return ShareManager.Holder.manager;
    }

    /**
     * This method is called in order to initialize the manager. This method
     * includes all tasks that must be done to intialize all the several manager.
     * Like instantiating the singleton instance of the manager. Inside
     * this method you can't rely on the availability of other managers.
     * @return true is initialization was successful, false otherwise.
     */
    @Override
    public boolean initialize()
    {
        sharedFilesService = new SharedFilesService();
        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 is initialization was successful, false otherwise.
     */
    @Override
    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.
     */
    @Override
    public void startupCompletedNotify()
    {
        FileRescanRunner.rescan( sharedFilesService, true, false );
    }

    /**
     * This method is called in order to cleanly shutdown the manager. It
     * should contain all cleanup operations to ensure a nice shutdown of Phex.
     */
    @Override
    public void shutdown()
    {
        getSharedFilesService().triggerSaveSharedFiles();
    }
    
    public SharedFilesService getSharedFilesService()
    {
        return sharedFilesService;
    }

    // Called by ReadWorker to handle a HTTP GET request from the remote host.
    public void httpRequestHandler(Connection connection,
        HTTPRequest httpRequest)
    {
        try
        {
            // GET / HTTP/1.1 (Browse Host request)
            if ( httpRequest.getRequestMethod().equals( "GET" ) )
            {
                String requestURI = httpRequest.getRequestURI();
                if ( requestURI.equals( "/" ) )
                {
                    // The remote host just want the index.html.
                    // Return a list of shared files.
                    sendFileListing( httpRequest, connection );
                    return;
                }
                else if ( requestURI.equals( "/robots.txt" ) )
                {// this appears to be a lost search engine... reject it...
                    HTTPResponse response = new HTTPResponse( (short)200, "OK", true );
                    response.addHeader( new HTTPHeader(HTTPHeaderNames.CONNECTION, "close") );
                    response.addHeader( new HTTPHeader(HTTPHeaderNames.CONTENT_TYPE, "text/plain") );
                    response.addHeader( new HTTPHeader(HTTPHeaderNames.CONTENT_LENGTH, "") );
                    String httpData = response.buildHTTPResponseString();
                    String robotsText = "User-agent: *\r\nDisallow: /";
                    connection.write( ByteBuffer.wrap( 
                        StringUtils.getBytesInUsAscii( httpData ) ) );
                    connection.write( ByteBuffer.wrap( 
                        StringUtils.getBytesInUsAscii( robotsText ) ) );
                    return;
                }
            }
        
            sendErrorHTTP( connection, "404 Not Found",
                "File not found." );
        }
        catch (IOException exp)
        {
            NLogger.debug(ShareManager.class, exp, exp);
        }
    }

    private void sendErrorHTTP( Connection connection, String statusStr, String errMsg)
        throws IOException
    {
        StringBuffer content = new StringBuffer( 300 );
        content.append( "<html><head><title>PHEX</title></head><body>" );
        content.append( errMsg );
        content.append( "<hr>Visit the Phex website at " );
        content.append( "<a href=\"http://phex.sourceforge.net\">http://phex.sourceforge.net</a>." );
        content.append( "</body>" );
        content.append( "</html>" );

        StringBuffer buf = new StringBuffer( 300 );
        buf.append( "HTTP/1.1 " ).append( statusStr ).append( HTTPRequest.CRLF );
        buf.append( "Server: " ).append( Environment.getPhexVendor() ).append( HTTPRequest.CRLF );
        buf.append( "Connection: close" ).append( HTTPRequest.CRLF );
        buf.append( "Content-Type: text/plain" ).append( HTTPRequest.CRLF );
        buf.append( "Content-Length: " ).append( content.length() ).append( HTTPRequest.CRLF );
        buf.append( "\r\n" );
        
        connection.write( ByteBuffer.wrap( 
            StringUtils.getBytesInUsAscii( buf.toString() ) ) );
        
        connection.write( ByteBuffer.wrap( 
            StringUtils.getBytesInUsAscii( content.toString() ) ) );
    }

    private void sendFileListing(HTTPRequest httpRequest, Connection connection)
        throws IOException
    {        
        if ( !LibraryPrefs.AllowBrowsing.get().booleanValue() )
        {
            HTTPHeaderGroup headers = HTTPHeaderGroup.createDefaultResponseHeaders();
            String response = createHTTPResponse( "403 Browsing disabled", headers );
            connection.write( ByteBuffer.wrap( 
                StringUtils.getBytesInUsAscii( response ) ) );
            connection.flush();
            connection.disconnect();
            return;
        }
        
        HTTPHeader acceptHeader = httpRequest.getHeader( "Accept" );
        if ( acceptHeader == null )
        {
            HTTPHeaderGroup headers = HTTPHeaderGroup.createDefaultResponseHeaders();
            String response = createHTTPResponse( "406 Not Acceptable", headers );
            connection.write( ByteBuffer.wrap( 
                StringUtils.getBytesInUsAscii( response ) ) );
            connection.flush();
            connection.disconnect();
            return;
        }
        String acceptHeaderStr = acceptHeader.getValue();
        if ( acceptHeaderStr.indexOf( "application/x-gnutella-packets" ) != -1 )
        {// return file listing via gnutella packages...
            HTTPHeaderGroup headers = HTTPHeaderGroup.createDefaultResponseHeaders();
            headers.addHeader( new HTTPHeader( HTTPHeaderNames.CONTENT_TYPE,
                "application/x-gnutella-packets" ) );
            headers.addHeader( new HTTPHeader( HTTPHeaderNames.CONNECTION,
                "close" ) );
            String response = createHTTPResponse( "200 OK", headers );
            connection.write( ByteBuffer.wrap( 
                StringUtils.getBytesInUsAscii( response ) ) );
            connection.flush();

            // now send QueryReplys...
            List<ShareFile> shareFiles = sharedFilesService.getSharedFiles();

            MsgHeader header = new MsgHeader( new GUID(),
                MsgHeader.QUERY_HIT_PAYLOAD, (byte) 2, (byte) 0, -1 );

            QueryResponseRecord record;
            ShareFile sfile;
            int sendCount = 0;
            int toSendCount = shareFiles.size();
            while ( sendCount < toSendCount )
            {
                int currentSendCount = Math.min( 255, toSendCount - sendCount );
                QueryResponseRecord[] records = new QueryResponseRecord[currentSendCount];
                for (int i = 0; i < currentSendCount; i++)
                {
                    sfile = shareFiles.get( sendCount + i );
                    record = QueryResponseRecord.createFromShareFile( sfile );
                    records[i] = record;
                }

                NetworkManager networkMgr = NetworkManager.getInstance();
                DestAddress hostAddress = networkMgr.getLocalAddress();
                QueryResponseMsg queryResponse = new QueryResponseMsg( header,
                    networkMgr.getServentGuid(), hostAddress,
                    Math.round(
                        BandwidthPrefs.MaxUploadBandwidth.get().floatValue()
                        / NumberFormatUtils.ONE_KB ), records );

                // send msg over the wire 
                ByteBuffer headerBuf = queryResponse.createHeaderBuffer();
                connection.write( headerBuf );
                ByteBuffer messageBuf = queryResponse.createMessageBuffer();
                connection.write( messageBuf );

                // and count message
                MessageCountStatistic.queryHitMsgOutCounter.increment( 1 );

                sendCount += currentSendCount;
            }
            connection.flush();
        }
        else if ( acceptHeaderStr.indexOf( "text/html" ) != -1
            || acceptHeaderStr.indexOf( "*/*" ) != -1 )
        {// return file listing via html page...
            HTTPHeaderGroup headers = HTTPHeaderGroup.createDefaultResponseHeaders();
            headers.addHeader( new HTTPHeader( HTTPHeaderNames.CONTENT_TYPE,
                "text/html; charset=iso-8859-1" ) );
            headers.addHeader( new HTTPHeader( HTTPHeaderNames.CONNECTION,
                "close" ) );
            String response = createHTTPResponse( "200 OK", headers );
            connection.write( ByteBuffer.wrap( 
                StringUtils.getBytesInUsAscii( response ) ) );
            connection.flush();

            // now send html
            exportSharedFiles( connection.getOutputStream() );
            
            connection.flush();
        }
        // close connection as indicated in the header
        connection.disconnect();
    }

    private String createHTTPResponse(String code, HTTPHeaderGroup header)
    {
        StringBuffer buffer = new StringBuffer( 100 );
        buffer.append( "HTTP/1.1 " );
        buffer.append( code );
        buffer.append( "\r\n" );
        buffer.append( header.buildHTTPHeaderString() );
        buffer.append( "\r\n" );
        return buffer.toString();
    }
    
    /**
     * 
     */
    public void exportSharedFiles( OutputStream outStream )
    {
        InputStream inStream = ClassLoader.getSystemResourceAsStream(
            "phex/resources/defaultSharedFilesHTMLExport.xsl" );
            //"phex/resources/magmaSharedFilesYAMLExport.xsl" );
        exportSharedFiles( inStream, outStream, null );
    }

    /**
     * 
     */
    public void exportSharedFiles( InputStream styleSheetStream, OutputStream outStream,
        Map<String, String> exportOptions )
    {
        ExportEngine engine = new ExportEngine();
        engine.setExportOptions(exportOptions);
        engine.startExport( styleSheetStream, outStream );
    }
}
