/*
 netjscene.java - scene 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 scenes received
 */
public class Netjscene
{
    /**
     * All scenes are kept in two static maps
     */
    private static Map table;
    private static Map idTable;
    private static int nscenes;

    /**
     * All connections of the scene are kept in a map
     */
    private Map connections;

    /**
     * The unique scene ID
     */
    public String SCID;

    /**
     * The scene url, must be unique
     */
    public String SCU;

    /**
     * The change ID of the scene, each attribute update increases this
     */
    public int    CHID;

    /**
     * The scene name, display name of the scene
     */
    public String SCN;

    /**
     * All other attributes of the scene are kept in a map
     */
    private Map attributeMap;

    /**
     * Time when the scene was created
     */
    private long creationTime;
    private long lastEnter;
    private long lastLeft;

    /**
     * Count the connections for statistics
     */
    public int  nConnections;
    public long tConnections;

    /**
     * Create a new scene with a specific url
     */
    public Netjscene( String sceneurl, String scenename )
    {
        if( table == null )
        {
            newTable();
        }
        if( idTable == null )
        {   
            newTable();
        }

        // set the creation time of the scene
        //
        creationTime = System.currentTimeMillis();
        lastEnter = 0;
        lastLeft = 0;
        nConnections = 0;
        tConnections = 0;

        // initialize the change count
        //
        CHID         = 0;

        // synchronize the creation access to the table
        //
        synchronized( table )
        {
            // make sure the scene url is unique, add the scene to the table
            //
            SCU = new String( sceneurl );
            if( table.containsKey( SCU ))
            {
                return;
            }
            table.put( SCU, this );

            // count the scenes knowns
            //
            nscenes = table.size();

            // create a unique scene id and add to id table
            //
            for(;;)
            {
                SCID = vrmlnetj.randstr();

                if( idTable.containsKey( SCID ))
                {
                    continue;
                }

                idTable.put( SCID, this );
                break;
            }

            // remember the scene name
            //
            if( scenename != null )
            {
                SCN = new String( scenename );
            } 
            else
            {
                SCN = new String( "" );
            }

            vrmlnetj.log( "LOG", "NEW SCEN " + SCID
                                 + " SCU " + SCU
                                 + " SCN " + SCN );

            // create the connection table of the scene
            //
            connections = new HashMap();

            // create the attributes table of the scene
            //
            attributeMap = new HashMap();
        }
    }

    /**
     * Set an attribute for a scene
     */
    public void setAttribute( String k, String v )
    {
        if( table == null )
        {
            return;
        }

        // synchronize the access to the table
        //
        synchronized( table )
        {
            // increase the change counter
            //
            CHID++;

            if( attributeMap == null )
            {
                return;
            }

            if( attributeMap.containsKey( k ))
            {
                attributeMap.remove( k );
            }
            attributeMap.put( k, v );
        }
        //vrmlnetj.log( "TRA ", "SCNSET " + k + " TO " + v + " CHID " + CHID );
    }

    /**
     * Get the change counter
     */
    public String getChid( )
    {
        return( vrmlnetj.hex8str( CHID ));
    }

    /**
     * Get an attribute of a scene
     */
    public String getAttribute( String k )
    {
        if( table == null )
        {
            return( "" );
        }

        // synchronize the access to the table
        //
        synchronized( table )
        {
            if( attributeMap == null )
            {
                return( "" );
            }

            String res = (String)attributeMap.get( k );
            if( res != null )
            {
                res = new String( res );
            }
            else
            {
                res = "";
            }
            return( res );
        }
    }

    /**
     * Find a scene by its scene url
     */
    public static Netjscene find( String sceneurl, String scenename )
    {
        if( table == null )
        {
            return( null );
        }

        // synchronize the access to the table
        //
        synchronized( table )
        {
            Netjscene scene = (Netjscene)table.get( sceneurl );

            // if the scene does not have a name yet we set it here
            //
            if( scene != null && scenename != null && scene.SCN.length() == 0 )
            {
                scene.SCN = new String( scenename );
            }
                
            return( scene );
        }
    }

    /**
     * Find a scene by its scene id
     */
    public static Netjscene find( String id )
    {
        if( idTable == null || table == null )
        {
            return( null );
        }

        // synchronize the access to the table
        //
        synchronized( table )
        {
            Netjscene scene = (Netjscene)idTable.get( id );
            return( scene );
        }
    }

    /**
     * Add a connection to a scene
     */
    public void addConnection( Netjconn conn )
    {
        if( connections == null )
        {
            connections = new HashMap();
        }

        // synchronize the access to the connections
        //
        synchronized( connections )
        {
            CHID++;

            Netjconn c = (Netjconn)connections.get( conn.CID );
            if( c != null )
            {
                connections.remove( conn.CID );
            }
            connections.put( conn.CID, conn );

            // Count the connections
            //
            nConnections = connections.size();
            tConnections++;
            lastEnter = System.currentTimeMillis();
        }
    }

    /**
     * Delete a connection from a scene
     */
    public int deleteConnection( Netjconn conn )
    {
        if( connections == null )
        {
            return( 0 );
        }

        // synchronize the access to the connections
        //
        synchronized( connections )
        {
            Netjconn c = (Netjconn)connections.get( conn.CID );
            if( c != null )
            {
                CHID++;

                // Count the connections
                //
                lastLeft = System.currentTimeMillis();

                connections.remove( conn.CID );
                nConnections = connections.size();
                if( nConnections < 1 )
                {
                    vrmlnetj.log( "LOG", "DEL SCEN " + SCID
                                         + " SCU " + SCU
                                         + " SCN " + SCN );

                    if( table != null )
                    {
                        // synchronize the access to the scenes
                        //
                        synchronized( table )
                        {
                            table.remove( SCU );
                            idTable.remove( SCID );
                        }
                    }
                }
            }
        }

        return( nConnections );
    }

    /**
     * Send a packet to all connections in a scene
     */
    public void send(
        Netjsendpacket packet, /** packet to send                          */
        String exceptCID       /** do not send to connection with this CID */
    )
    {
        if( connections == null )
        {
            return;
        }

        // synchronize the access to the connections
        //
        synchronized( connections )
        {
            Set s = connections.keySet();

            for( Iterator i = s.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();
                Netjconn conn = (Netjconn)connections.get( currkey );

                if( exceptCID != null && exceptCID.equals( conn.CID ))
                {
                    // do not send to this connection
                    //
                    continue;
                }

                // send the packet to this connection
                //
                packet.send( conn );
            }
        }
    }

    /**
     * Send attribute packets about all connections in a scene
     */
    public void sendConnectionAttributes(
        Netjconn conn          /** connection to send packets to           */
    )
    {
        if( connections == null )
        {
            return;
        }

        // synchronize the access to the connections
        //
        synchronized( connections )
        {
            Set s = connections.keySet();
            for( Iterator i = s.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();
                Netjconn current = (Netjconn)connections.get( currkey );

                // ignore the current connection
                //
                if( conn.CID.equals( current.CID ))
                {
                    continue;
                }

                // create and send a reply
                //
                Netjsendpacket reply = new Netjsendpacket( conn, "RQ" );

                reply.setString( "SET" );
                reply.setString( "CLID", current.CLID );
                reply.setString( "CHID", current.getChid() );

                int k;

                // loop through all attributes requested
                //
                for( k = 4; k < vrmlnetj.nstrargs; k++ )
                {
                    String arg = vrmlnetj.strargs[ k ];

                    if( reply.setString( arg, current.getAttribute( arg )) < 1 )
                    {
                        break;
                    }
                }

                if( k >= vrmlnetj.nstrargs )
                {
                    // send the reply to the client
                    //
                    reply.send();
                }
            }
        }
    }

    /**
     * Send attribute packets about all scenes known
     */
    public void sendSceneAttributes(
        Netjconn conn          /** connection to send packets to           */
    )
    {
        if( table == null || conn.scene == null )
        {
            return;
        }

        // synchronize the access to the scenes
        //
        synchronized( table )
        {
            Set s = table.keySet();
            for( Iterator i = s.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();
                Netjscene scene = (Netjscene)table.get( currkey );

                // ignore the current connection
                //
                if( conn.scene.SCID.equals( scene.SCID ))
                {
                    continue;
                }

                // prevent other threads from updating the scene
                //
                synchronized( scene.SCID )
                {
                    // create and send a reply
                    //
                    Netjsendpacket reply = new Netjsendpacket( conn, "RQ" );

                    reply.setString( "SET" );
                    reply.setString( "SCID", scene.SCID );
                    reply.setString( "CHID", scene.getChid() );

                    int k;

                    // loop through all attributes requested
                    //
                    for( k = 4; k < vrmlnetj.nstrargs; k++ )
                    {
                        String arg = vrmlnetj.strargs[ k ];
                        String val = null;

                        if( arg.equals( "NPP" ))
                        {
                            val = "" + scene.nConnections;
                        }
                        else if( arg.equals( "NSN" ))
                        {
                            val = "" + scene.getNScenes();
                        }
                        else if( arg.equals( "SCU" ))
                        {
                            val = scene.SCU;
                        }
                        else if( arg.equals( "SCN" ))
                        {
                            val = scene.SCN;
                        }
                        else
                        {
                            // get the attribute from the scene
                            //
                            val = scene.getAttribute( arg );
                        }

                        if( reply.setString( arg, val ) < 1 )
                        {
                            break;
                        }
                    }

                    if( k >= vrmlnetj.nstrargs )
                    {
                        // send the reply to the client
                        //
                        reply.send();
                    }
                }
            }
        }
    }

    /**
     * Periodic handling of the scenes, delete empty ones
     */
    public static void periodic()
    {
        if( table == null )
        {
            newTable();
        }

        // synchronize the table access
        //
        synchronized( table )
        {
            // run through all scenes that are active
            //
            Set s = table.keySet();
            HashSet expired = new HashSet();

            for( Iterator i = s.iterator(); i.hasNext(); )
            {
                String currkey = (String)i.next();
                Netjscene scene = (Netjscene)table.get( currkey );

                if( scene.connections == null )
                {
                    // remove the attributes
                    //
                    scene.attributeMap = null;

                    vrmlnetj.log( "LOG", "TOT SCEN " + scene.SCID
                                         + " SCU " + scene.SCU
                                         + " SCN " + scene.SCN );

                    expired.add( currkey );
                    idTable.remove( scene.SCID );
                    continue;
                }

                // synchronize on the connections
                //
                synchronized( scene.connections )
                {
                    scene.nConnections = scene.connections.size();
                    if( scene.nConnections < 1 )
                    {
                        // remove the attributes
                        //
                        scene.attributeMap = null;

                        vrmlnetj.log( "LOG", "TOT SCEN " + scene.SCID
                                             + " SCU " + scene.SCU
                                             + " SCN " + scene.SCN );

                        expired.add( currkey );
                        idTable.remove( scene.SCID );
                    }
                }
            }

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

                table.remove( currkey );
            }
            nscenes = table.size();
        }
    }

    /**
     * Return the total number of scenes known on server
     */
    public int getNScenes()
    {
        int res;

        if( table == null )
        {
            return( 0 );
        }

        // synchronize the table access
        //
        synchronized( table )
        {
            res = nscenes;
        }
        return( res );
    }
    
    /**
     * Create the scene tables, synchronized
     */
    private synchronized static void newTable()
    {
        if( table == null )
        {
            table = new HashMap();
            nscenes = 0;
        }
        if( idTable == null )
        {
            idTable = new HashMap();
        }
    }
}
