/*
** Luxor - XML User Interface Language (XUL) Toolkit
** Copyright (c) 2001, 2002 by Gerald Bauer
**
** This program is free software.
**
** You may redistribute it and/or modify it under the terms of the GNU
** General Public License as published by the Free Software Foundation.
** Version 2 of the license should be included with this distribution in
** the file LICENSE, as well as License.html. If the license is not
** included with this distribution, you may find a copy at the FSF web
** site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
** Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA.
**
** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
** REDISTRIBUTION OF THIS SOFTWARE.
**
*/

package luxor.http;

import java.io.*;
import java.net.*;
import java.util.*;
import luxor.constant.*;
import luxor.event.*;
import luxor.http.loader.*;
import luxor.http.resource.*;
import luxor.spi.*;
import luxor.status.*;

public class RequestProcessor implements Runnable
{
   static Logger T = Logger.getLogger( RequestProcessor.class );

   private static List _pool = new LinkedList();
   private WebResourceLoader _loader;

   private WebServer _server;

   public RequestProcessor( WebServer server, WebResourceLoader loader )
   {
      // todo: should i use server to access loader?
      //  - only one para required

      _server = server;
      _loader = loader;
   }

   public static void processRequest( Socket request )
   {
      synchronized( _pool )
      {
         _pool.add( _pool.size(), request );
         _pool.notifyAll();
      }
   }

   public void run()
   {
      while( true )
      {
         Socket connection;
         synchronized( _pool )
         {
            while( _pool.isEmpty() )
            {
               try
               {
                  _pool.wait();
               }
               catch( InterruptedException ex )
               {
               }
            }
            connection = ( Socket ) _pool.remove( 0 );
         }

         RequestResponseInfo info = new RequestResponseInfo();

         try
         {
            OutputStream raw = new BufferedOutputStream( connection.getOutputStream() );
            Writer out = new OutputStreamWriter( raw );

            BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream() ) );

            String requestLine = in.readLine();
            if( requestLine == null || requestLine.length() == 0 )
            {
               // socket died; no point nattering: nobody will hear you if you scream in cyperspace
               Status.error( "*** [" + Thread.currentThread().getName() + "] request contains no data" );
               return;
            }

            RequestInfo requestInfo = new RequestInfo( requestLine );

            T.debug( "requestLine=" + requestLine );

            info.setRequestInfo( requestInfo );

            // read headers, up to the null line before the body
            String headerLine;
            while( ( headerLine = in.readLine() ) != null
                   && headerLine.length() != 0 )
            {
               int pos;
               if( ( pos = headerLine.indexOf( ':' ) ) != -1 )
               {
                  String headerName = headerLine.substring( 0, pos );
                  String headerValue = headerLine.substring( pos + 1 ).trim();

                  requestInfo.addHeader( headerName, headerValue );
               }
               else
               {
                  Status.error( "*** [" + Thread.currentThread().getName() + "] invalid header: "
                         + headerLine );
               }
            }

            WebResource resource = null;

            String method = requestInfo.getMethod();

            if( method.equals( Http.Method.GET ) ||
                  method.equals( Http.Method.HEAD ) )
            {
               resource = _loader.getResource( requestInfo.getPath() );
            }
            else
            {
               // method doesn't equal GET or HEAD

               resource = new Error501Resource( method );
            }

            _server.fireRequestAccepted( info );

            try
            {

               info.setResponseContentLength( resource.getContentLength() );

               // fix: rename getHeader to getResponseHeaders()

               // send header
               out.write( resource.getHeader() );
               out.flush();

               if( !method.equals( Http.Method.HEAD ) )
               {
                  // don't send content for HEAD requests

                  info.setResponseStatus( Transfer.Status.UPLOADING );

                  byte data[] = resource.getContent();
                  T.debug( "data.length=" + data.length );

                  // send file in chunks; it may be an image or other binary data
                  //   so use the underlying output stream instead of the writer
                  int total_bytes_written = 0;
                  // aka offset

                  while( total_bytes_written < data.length )
                  {
                     int chunk_size = Math.min( 4096, data.length - total_bytes_written );

                     raw.write( data, total_bytes_written, chunk_size );
                     total_bytes_written += chunk_size;

                     info.setResponseTransferredSize( total_bytes_written );
                  }

                  // raw.write( data );
                  raw.flush();
               }

               info.setResponseCode( resource.getResponseCode() );
               info.setResponseStatus( Transfer.Status.COMPLETED );

               if( resource.getResponseCode() != 200 )
               {
                  info.setResponseError( new Exception(
                        resource.getResponseCode() + " " + resource.getResponseMessage() ) );
               }

               // todo: should i close stream?
            }
            catch( IOException ioex )
            {
               info.addError( ioex );
               info.setResponseError( ioex );
               throw ioex;
               // rethrow exception
            }
         }
         catch( IOException ioex )
         {
            info.addError( ioex );
            Status.error( "*** [" + Thread.currentThread().getName() + "] "
                   + ioex.toString() );
            // T.error( ioex.toString() );
         }
         finally
         {
            _server.fireRequestServiced( info );

            try
            {
               connection.close();
            }
            catch( IOException ioex )
            {
               T.error( ioex.toString() );
            }
         }
      }
      // end while
   }
   // end run()

}