/*
 vrmlnetj.java - 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.*;

/**
 * VRMLNETJ - A UDP base multi-user server written in java.
 *
 * @author  Peter Graf
 */
public class vrmlnetj
{
    /**
     * Creates a new instance of vrmlnetj
     */
    public vrmlnetj() {
    }
    
    /**
     * Usage string
     */
    public static final String usage = "Usage: java vrmlnetj -p <port> -l <logfile> -daemon [on|off]";

    /**
     * The datagram socket used to receive request packets
     */
    private static DatagramSocket dsocket;

    /**
     * A random generator for the random 8 byte hex strings used
     */
    private static Random generator;

    /**
     * Return an 8 byte hex string for an int
     */
    public static String hex8str( int i )
    {
        String s = Integer.toHexString( i );

        while( s.length() < 8 )
        {
            s = "0" + s;
        }

        return( new String( s ) );
    }

    /**
     * Return an 8 byte random hex string
     */
    public static String randstr()
    {
        if( generator == null )
        {
            generator = new Random( System.currentTimeMillis());
        }
        else if( generator.nextInt( 128 ) < 2 )
        {
            generator = new Random( generator.nextInt( 0x7fffffff )
                                  ^ System.currentTimeMillis());
        }

        int r = generator.nextInt( 0x7fffffff );

        /*
         * create an 8 byte hex string
         */
        String s = hex8str( r );

        return( s );
    }

    /**
     * The function that receives packets from the net
     */
    public static void udpReceive( DatagramPacket recvPacket )
    {
        // receive the packet from the network
        //
        try
        {
            dsocket.receive( recvPacket );
        }
        catch( Exception e )
        {
        }
    }

    public static int port;
    public static String logfilename;
    public static String daemon;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        port = -1;
        logfilename = "";
        logfile = null;
        out = null;
        daemon = "off";

        try
        {
            if( args.length < 2 )
            {
                throw new IllegalArgumentException( "Wrong number of args" );
            }

            for( int i = 0; i < args.length - 1; i += 2 )
            {
                if( args[ i ].equals( "-p" ))
                {
                    // Get the port from the command line
                    //
                    port = Integer.parseInt( args[ i + 1 ] );
                }
                else if( args[ i ].equals( "-l" ))
                {
                    // Get the log file name from the command line
                    //
                    logfilename = args[ i + 1 ];
                }
                else if( args[ i ].equals( "-daemon" ))
                {
                    // Get the log file name from the command line
                    //
                    daemon = args[ i + 1 ];
                }
            }

            // Create a socket to listen on the port.
            //
            dsocket = new DatagramSocket( port );

            // Create a buffer to read datagrams into.
            //
            byte[] receivebuffer = new byte[ 32 * 1024 ];

            // Create a packet to receive data into the buffer
            //
            DatagramPacket packet = new DatagramPacket( receivebuffer,
                                                        receivebuffer.length );
            // Create the argument and result string arrays
            //
            strargs = new String[ 32 * 1024 ];
            nstrargs = 0;

            // create the watcher thread
            //
            Thread watcher = new Watcher( 0, "watcher" );
            watcher.start();

            // Now loop forever, waiting to receive packets and printing them.
            //
            for(;;)
            {
                // wait to receive a datagram
                //
                udpReceive( packet );

                // look at the data received
                //
                byte[] data = packet.getData();
                int    datalen = packet.getLength();

                if( datalen < 3 )
                {
                    continue;
                }

                // look at the packet header
                //
                if( data[ 0 ] == 'R' )
                {
                    if( data[ 1 ] != 'Q' )
                    {
                        continue;
                    }

                    // handle the request received
                    //
                    Netjrequest request = new Netjrequest( packet );
                }
                else if( data[ 0 ] == 'A' )
                {
                    if( data[ 1 ] != 'N' )
                    {
                        continue;
                    }

                    // handle the answer received
                    //
                    Netjanswer answer = new Netjanswer( packet );
                }

                // Reset the length of the packet before reusing it.
                // Prior to Java 1.1, we'd just create a new packet each time.
                packet.setLength( receivebuffer.length );
            }
        }
        catch( Exception e )
        {
            System.err.println( e );
            System.err.println( usage );
        }
    }

    /**
     * The global string array packets are parsed to
     */
    public static String[ ] strargs;
    public static int       nstrargs;

    public static File logfile;
    private static PrintWriter out;

    /**
     * Log an event, synchronized
     */
    static public synchronized void log( String t, String s )
    {
        java.text.DateFormat f = java.text.DateFormat.getTimeInstance(
                                         java.text.DateFormat.MEDIUM );
        String time = f.format(new java.util.Date());

        if( !logfilename.equals( "" ) && logfile == null )
        {
            try {
                logfile = new File( logfilename );
                out = new PrintWriter( new FileWriter( logfile ));
            }
            catch (Exception e) {}
        }
        if( t.equals( "LOG" ))
        {
            if( out != null )
            {
                out.println( "L" + time + " " + s );
            }
            else
            {
                System.out.println( "L" + time + " " + s );
            }
        }
        else if( t.equals( "< " ))
        {
            if( out != null )
            {
                out.println( "<" + time + " " + s );
            }
            else
            {
                System.out.println( "<" + time + " " + s );
            }
        }
        else if( t.equals( "> " ))
        {
            if( out != null )
            {
                out.println( ">" + time + " " + s );
            }
            else
            {
                System.out.println( ">" + time + " " + s );
            }
        }
        else
        {
            if( out != null )
            {
                out.println( t + s );
            }
            else
            {
                System.out.println( t + s );
            }
        }

        if( out != null )
        {
            out.flush();
        }
    }

    /**
     * Parse a packet into an array of String variables
     */
    static public int packetParse( byte buffer[], int buflen )
    {
        int n = 0;
        int start = 0;

        // parse the entire buffer received
        //
        for( int offset = 0; offset < buflen; offset++ )
        {
            // a 0 byte terminates the argument string
            //
            if( buffer[ offset ] == 0 )
            {
                strargs[ n++ ] = new String( buffer, start, offset - start );
                start = offset + 1;
            }
        }

        while( --nstrargs >= n )
        {
            strargs[ nstrargs ] = null;
        }

        // remember the number of arguments we received
        //
        nstrargs = n;

        return( n );
    }

    /**
     * The watcher thread that makes sure that things time out
     */
    static class Watcher extends Thread
    {
        private int    number;
        private String name;

        /**
         * Create a new watcher thread
         */
        public Watcher( int n, String nam )
        {
            number = n;
            name   = new String( nam );
        }

        /**
         * format info for the date
         */
        java.text.DateFormat f = java.text.DateFormat.getTimeInstance(
                                         java.text.DateFormat.MEDIUM );

        /**
         * Flag controlling the run of the thread
         */
        volatile boolean keepRunning = true;

        /**
         * a method to stop the thread
         */
        public void pleaseStop() { keepRunning = false; }

        /**
         * The run function loops until told to stop
         */
        public void run()
        {
            // This thread runs the periodic functions of the classes
            //
            long lastSendpacket = 0;
            long lastScene = 0;
            long lastRequest = 0;
            long lastConn = 0;
            long lastLog = 0;

            while( keepRunning )
            {
                long now = System.currentTimeMillis();
                if( lastLog + 10000 < now )
                {
                    String time = f.format(new java.util.Date());
                    log( "WA ", time );
                    lastLog = now;
                }

                now = System.currentTimeMillis();
                if( lastSendpacket + 300 < now )
                {
                    Netjsendpacket.periodic();
                    lastSendpacket = now;
                }

                now = System.currentTimeMillis();
                if( lastRequest + 5000 < now )
                {
                    Netjrequest.periodic();
                    lastRequest = now; 
                }

                now = System.currentTimeMillis();
                if( lastConn + 5000 < now )
                {
                    Netjconn.periodic();
                    lastConn = now; 
                }

                now = System.currentTimeMillis();
                if( lastScene + 5000 < now )
                {
                    Netjscene.periodic();
                    lastScene = now;
                }

                try { Thread.sleep( 311 ); }
                catch (InterruptedException e) {}
            }
        }
    }
}
