/*
 netjrequest.java - request handling of the  VRML net java server

 Copyright (C) 2002    Peter Graf

 This file is part of VRMLNETJ - The VRML net java server.
 VRMLNETJ is free software.

    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

   For more information on the VRML net java server or Peter Graf,
   please see: http://www.mission-base.com/.

   $Log: vrmlnetj.java,v $

------------------------------------------------------------------------------
*/
//package vrmlnetj;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * A class containing requests received
 */
public class Netjrequest
{
    /**
     * All requests are kept in a static hash
     */
    public static Map table;

    /**
     * The key used for the hash, the request ID
     */
    public String RID;

    /**
     * Time when the request was created
     */
    long creationTime;

    /**
     * All reply packets are kept for replay
     */
    private Collection replyPackets;

    /**
     * create a new request
     */
    public Netjrequest(
        DatagramPacket packet   /** packet that created the request */
    )
    {
        boolean connectionIsNew = false;

        if( table == null )
        {
            newTable();
        }
        
        // set the creation time of the request
        //
        creationTime = System.currentTimeMillis();

        // create the collection of reply packets
        //
        replyPackets = new LinkedList();
     
        // parse the request received
        //
        vrmlnetj.packetParse( packet.getData(), packet.getLength() );

        // convert the contents to a string, and display them
        //
        String msg = "";
        for( int i = 0; i < vrmlnetj.nstrargs; i++ )
        {
            msg = msg + vrmlnetj.strargs[ i ] + " ";
        }

        vrmlnetj.log( "> ", packet.getAddress().getHostName()
                            + ":" + packet.getPort()
                            + " " + msg
                          );

        // ignore packets that are not REQUESTS
        //
        String header = vrmlnetj.strargs[ 0 ];
        if( !header.equals( "RQ" ))
        {
            return;
        }

        // ignore packets that do not contain a tag
        //
        if( vrmlnetj.nstrargs < 4 )
        {
            return;
        }

        // look up the connection the request is for
        //
        Netjconn conn;

        // synchronize the creation access to the table
        //
        synchronized( table )
        {
            String connectionId = vrmlnetj.strargs[ 2 ];
            if( connectionId.equals( "0" ))
            {
                conn = new Netjconn( );
                connectionId = conn.CID;
                vrmlnetj.strargs[ 2 ] = conn.CID;

                connectionIsNew = true;
            }
            else if(( conn = Netjconn.find( connectionId )) == null )
            {
                // ignore the request by the unknown connection
                //
                return;
            }
            else
            {
                vrmlnetj.log( "LOG", "USE CONN " + conn.CID
                                      + " CLID " + conn.CLID);
            }

            // if we can't create a connection, give up
            //
            if( conn == null )
            {
                return;
            }
        }

        // synchronize on the connection object,
        // handle requests by a single connection sequentially
        //
        synchronized( conn )
        {
            // remember the internetadress and port used for connection
            //
            conn.setAddress( packet.getAddress(), packet.getPort() );

            // remember the last time we received a packet from the connection
            //
            conn.setLastRecvTime();

            // now look at the id of the request
            //
            String packetId = vrmlnetj.strargs[ 1 ];
            String rid = conn.CID + "/" + packetId;

            // synchronize the creation access to the table
            //
            synchronized( table )
            {
                Netjrequest otherRequest = (Netjrequest)table.get( rid );
                if( null != otherRequest )
                {
                    vrmlnetj.log( "LOG", "USE REQU " + rid );

                    if( null == otherRequest.replyPackets )
                    {
                        vrmlnetj.log( "E ",
                                      "NO replyPackets collection for " + rid );
                        return;
                    }

                    int n = 0;
                    Iterator i = otherRequest.replyPackets.iterator();

                    // the request was already handled
                    //
                    while( i.hasNext() )
                    {
                        // resend all packets for the request
                        //
                        Netjsendpacket curpacket = (Netjsendpacket) i.next();
                        curpacket.send( );
                        n++;
                    }

                    if( n == 0 )
                    {
                        vrmlnetj.log( "E ", "NO reply packet for " + rid );
                    }

                    return;
                }

                vrmlnetj.log( "LOG", "NEW REQU " + rid );

                // put the request into the request table
                //
                this.RID = new String( rid );
                table.put( this.RID, this );
            }

            // tell the connection which request it has to handle
            //
            conn.setRequest( this );

            // look at the different types of requests we know
            //
            String tag = vrmlnetj.strargs[ 3 ];
            if( tag.equals( "PING" ))
            {
                conn.ping( );
            }
            else if( tag.equals( "ENTER" ))
            {
                conn.enter( );
            }
            else if( tag.equals( "BYE" ))
            {
                conn.bye( );
            }
            else if( tag.equals( "SET" ))
            {
                conn.set( );
            }
            else if( tag.equals( "GET" ))
            {
                conn.get( );
            }
            else if( tag.equals( "LST" ))
            {
                conn.lst( );
            }
            else if( tag.equals( "SCL" ))
            {
                conn.scl( );
            }

            return;
        }
    }

    /**
     * Store a reply packet for a request
     */
    public void setReplyPacket( Netjsendpacket p )
    {
        if( table == null )
        {
            return;
        }

        // synchronize the creation access to the table
        //
        synchronized( table )
        {
            vrmlnetj.log( "LOG", "NEW ANPA " + p.getCidPid() );

            if( replyPackets == null )
            {
                vrmlnetj.log( "E ", "No replyPackets collection for " + RID );
                return;
            }

            replyPackets.add( p );
        }
    }

    /**
     * Periodic handling of requests handled
     */
    public static int periodic()
    {
        if( table == null )
        {
            newTable();
        }

        // synchronize the table access
        //
        synchronized( table )
        {
            long now = System.currentTimeMillis();

            // run through all requests that are active
            //
            Set s = table.keySet();
            HashSet expired = new HashSet();

            for( Iterator i = s.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();

                Netjrequest current = (Netjrequest)table.get( currkey );

                // handle requests that have timed out
                //
                if( current.creationTime + 30000 < now )
                {
                    vrmlnetj.log( "LOG", "TOT REQU " + current.RID );

                    // remove all reply packets stored for the request
                    // this assumes that the members of a collection are
                    // freed when the collection is freed
                    //
                    current.replyPackets = null;

                    // remove the request from the table
                    //
                    expired.add( currkey );
                    continue;
                }
            }

            // delete expired elements from the table
            //
            for( Iterator i = expired.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();

                table.remove( currkey );
            }
        }

        return( 0 );
    }

    /**
     * Create the request table, synchronized
     */
    private synchronized static void newTable()
    {
        if( table == null )
        {
            table = new HashMap();
        }
    }

}
