/*
 * $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $
 *
 * Copyright (c) 2012, Paul Speed
 * All rights reserved.
 */

package com.jme3.network.message;

import com.jme3.network.AbstractMessage;
import com.jme3.network.serializing.Serializable;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.serializing.SerializerRegistration;
import com.jme3.network.serializing.serializers.FieldSerializer;
import java.util.*;
import java.util.jar.Attributes;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 *  Holds a compiled set of message registration information that
 *  can be sent over the wire.  The received message can then be
 *  used to register all of the classes using the same IDs and 
 *  same ordering, etc..  The intent is that the server compiles
 *  this message once it is sure that all serializable classes have
 *  been registered.  It can then send this to each new client and
 *  they can use it to register all of the classes without requiring
 *  exactly reproducing the same calls that the server did to register
 *  messages.
 *
 *  <p>Normally, JME recommends that apps have a common utility method
 *  that they call on both client and server.  However, this makes 
 *  pluggable services nearly impossible as some central class has to
 *  know about all registered serializers.  This message implementation
 *  gets around by only requiring registration on the server.</p>
 *
 *  @author    Paul Speed
 */
@Serializable
public class SerializerRegistrationsMessage extends AbstractMessage {

    static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName());

    public static final Set<Class> ignore = new HashSet<Class>();
    static {
        // We could build this automatically but then we
        // risk making a client and server out of date simply because
        // their JME versions are out of date.
        ignore.add(Boolean.class);
        ignore.add(Float.class);
        ignore.add(Boolean.class);
        ignore.add(Byte.class);
        ignore.add(Character.class);
        ignore.add(Short.class);
        ignore.add(Integer.class);
        ignore.add(Long.class);
        ignore.add(Float.class);
        ignore.add(Double.class);
        ignore.add(String.class);
 
        ignore.add(DisconnectMessage.class);
        ignore.add(ClientRegistrationMessage.class);
    
        ignore.add(Date.class);
        ignore.add(AbstractCollection.class);
        ignore.add(AbstractList.class);
        ignore.add(AbstractSet.class);
        ignore.add(ArrayList.class);
        ignore.add(HashSet.class);
        ignore.add(LinkedHashSet.class);
        ignore.add(LinkedList.class);
        ignore.add(TreeSet.class);
        ignore.add(Vector.class);
        ignore.add(AbstractMap.class);
        ignore.add(Attributes.class);
        ignore.add(HashMap.class);
        ignore.add(Hashtable.class);
        ignore.add(IdentityHashMap.class);
        ignore.add(TreeMap.class);
        ignore.add(WeakHashMap.class);        
        ignore.add(Enum.class);
        
        ignore.add(GZIPCompressedMessage.class);
        ignore.add(ZIPCompressedMessage.class);

        ignore.add(ChannelInfoMessage.class);
        
        ignore.add(SerializerRegistrationsMessage.class);
        ignore.add(SerializerRegistrationsMessage.Registration.class);        
    }
 
    public static SerializerRegistrationsMessage INSTANCE;   
    public static Registration[] compiled;
    private static final Serializer fieldSerializer = new FieldSerializer();
    
    private Registration[] registrations;    

    public SerializerRegistrationsMessage() {
        setReliable(true);
    }

    public SerializerRegistrationsMessage( Registration... registrations ) {
        setReliable(true);
        this.registrations = registrations;
    }
    
    public static void compile() {
    
        // Let's just see what they are here
        List<Registration> list = new ArrayList<Registration>();
        for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) {
            Class type = reg.getType();
            if( ignore.contains(type) )
                continue;
            if( type.isPrimitive() )
                continue;
 
            list.add(new Registration(reg));
        }
            
        if( log.isLoggable(Level.FINE) ) {
            log.log( Level.FINE, "Number of registered classes:{0}", list.size());
            for( Registration reg : list ) { 
                log.log( Level.FINE, "    {0}", reg);
            }
        }
        compiled = list.toArray(new Registration[list.size()]);
        
        INSTANCE = new SerializerRegistrationsMessage(compiled);  
        
        Serializer.setReadOnly(true);                              
    }
 
    public void registerAll() {

        // See if we will have problems because our registry is locked        
        if( Serializer.isReadOnly() ) {
            // Check to see if maybe we are executing this from the
            // same JVM that sent the message, ie: client and server are running on
            // the same JVM
            // There could be more advanced checks than this but for now we'll
            // assume that if the registry was compiled here then it means
            // we are also the server process.  Note that this wouldn't hold true
            // under complicated examples where there are clients of one server
            // that also run their own servers but realistically they would have
            // to disable the ServerSerializerRegistrationsServer anyway.
            if( compiled != null ) {
                log.log(Level.INFO, "Skipping registration as registry is locked, presumably by a local server process.");
                return;
            }
        }
        
        log.log(Level.FINE, "Registering {0} classes...", registrations.length);
        for( Registration reg : registrations ) {
            log.log(Level.INFO, "Registering:{0}", reg);
            reg.register();
        }
        log.log(Level.FINE, "Done registering serializable classes.");
    }
    
    @Serializable
    public static final class Registration {
    
        private short id;
        private String className;
        private String serializerClassName;
        
        public Registration() {
        }
        
        public Registration( SerializerRegistration reg ) {
        
            this.id = reg.getId();
            this.className = reg.getType().getName();
            if( reg.getSerializer().getClass() != FieldSerializer.class ) {
                this.serializerClassName = reg.getSerializer().getClass().getName();
            } 
        }
 
        public void register() {        
            try {
                Class type = Class.forName(className);
                Serializer serializer;
                if( serializerClassName == null ) {
                    serializer = fieldSerializer;
                } else {
                    Class serializerType = Class.forName(serializerClassName);
                    serializer = (Serializer)serializerType.newInstance();                    
                }
                SerializerRegistration result = Serializer.registerClassForId(id, type, serializer);
                log.log(Level.FINE, "   result:{0}", result);                
            } catch( ClassNotFoundException e ) {
                throw new RuntimeException( "Class not found attempting to register:" + this, e );
            } catch( InstantiationException e ) {
                throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
            } catch( IllegalAccessException e ) {
                throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
            }            
        }
        
        @Override
        public String toString() {
            return "Registration[" + id + " = " + className + ", serializer=" + serializerClassName + "]";
        }
    }
}



