package mde.jini.service;

import mde.lego.*;
import java.io.*;
import java.rmi.*;
import java.rmi.activation.*;
import java.rmi.server.*;
import java.util.*;
import net.jini.core.event.*;
import net.jini.core.lease.*;
import net.jini.discovery.*;
import net.jini.core.lookup.*;

/** 
 * This class handles the low-level communication between the tanks
 * and the host. It comes up in the sign-up state. In that state,
 * the tank daemon sends out a broadcast message, inviting tanks
 * to sign up for a game. Tanks respond by sending back their
 * particular IDs. After the specified number of tanks have signed
 * up, the tank daemon switches to the run state. In that state,
 * it uses a slotted protocol to communicate with the tanks. For each
 * slot, the tank daemon queries all tank proxies for commands to
 * send to the tanks. For each tank, the command is sent (either a
 * real command, or a dummy status command), and the tank hands back
 * status. That status is forwarded back to the tank proxy. If there
 * is no status feedback from the tank in a specified number of
 * slots, that tank is presumed missing, and the run state will be
 * aborted. The tank daemon goes back into sign-up state until the
 * specified number of tanks are registered again.
 */
public class TankDaemonImpl extends Thread implements TankDaemon, 
                                                    TankCommands, 
                                                    DataReceiver, 
                                                    DiscoveryListener {
	
	private ActivationDesc desc;
	private Brick brick;
	private TankProxy[] tankProxy;
	private String[] cmdlist = {"FORWARD", "BACKWARD", "LEFT", "RIGHT", "STOP", "FIRE", "STATUS", "HELLO"};
	private Hashtable statuslist = new Hashtable();
	private transient LookupDiscovery discovery;
	private String[] groups;
	private TankExpiredListener listener;
    private static final int INTER_MESSAGE_DELAY = 200;
    private ByteArrayOutputStream status_buf;
    private final int STATUS_BYTE = 0;
    private final int LEFT_BYTE = 1;
    private final int RIGHT_BYTE = 2;
	
	/** Create a new tank daemon instance */
	public TankDaemonImpl(ActivationID id, MarshalledObject data) throws RemoteException {
		Activatable.exportObject(this, id, 0);
		
		try {
			TankDaemonConfiguration config = (TankDaemonConfiguration) data.get();
            tankProxy = new TankProxy[config.getPlayerCount()];
            System.out.println("TankDaemonImpl(): seting up for " + config.getPlayerCount() + " players");
            groups = config.getGroups();
		} catch(Exception e) {
			System.out.println("TankDaemonImpl(): exception caught retrieving activation data: " + e);
		}
		
		brick = new Brick();
		brick.addDataReceiver(this);
        status_buf = new ByteArrayOutputStream();
		
		try {listener = new TankExpiredListener();}
		catch(Exception e) {e.printStackTrace();}
		
		statuslist.put(new Byte((byte)0), "OK");
		statuslist.put(new Byte((byte)1), "OBSTRUCTED");
		statuslist.put(new Byte((byte)2), "HIT");
		
		setupTankActivation();
		
		// Setup listener for Jini discovery events 
		try {
			discovery = new LookupDiscovery(groups);
			discovery.addDiscoveryListener(this);
		} catch(IOException e) {
			e.printStackTrace();
		}
	}

    private void echoReceived(byte msg) {
        int cmd = (msg & CMD_MASK) >> 3;
        if (cmd < 0 || cmd >= cmdlist.length) {
            // System.out.println("invalid cmd, msg was " + toHex(msg));
            return;
        }

        if (cmd != HELLO && cmd != STATUS) {
            System.out.println("TankDaemonImpl.echoReceived(): " + cmdlist[cmd]);
        }
    }

    private void statusReceived(byte[] buf) {
        byte id = (byte)((buf[STATUS_BYTE] & ID_MASK) - 1);
        byte status;

        if (id < 0 || id >= tankProxy.length) {                                           
            // System.out.println("invalid ID: " + id + ", complete msg was " + toHex(msg));
            return;
        }

        synchronized (this) {
            if (tankProxy[id] == null) {
                tankProxy[id] = startTankProxy(id, groups);
                System.out.println("created new tank proxy with ID " + id);
            }
    
            status = (byte)((buf[STATUS_BYTE] & STATUS_MASK) >> 3);
            TankEvent te = new TankEvent(this, id, status, 
                (buf.length == 3 ) ? buf[LEFT_BYTE] : -1, 
                (buf.length == 3 ) ? buf[RIGHT_BYTE] : -1);

            System.out.println(te);

            try {tankProxy[id].updateStatus(te);}
            catch (Exception e) {e.printStackTrace();}
        }

        if (status != 0) {
            System.out.println("status for tank " + (id + 1)
                + " is " + statuslist.get(new Byte(status)));
        }
    }

	public void dataAvailable(byte msg) {
        // System.out.println("TankDaemonImpl.dataAvailable(): received " + toHex(msg));

        if ((msg & ECHO_BIT) != 0) {
            echoReceived(msg); 
            if (status_buf.size() > 0) statusReceived(status_buf.toByteArray());
            status_buf.reset();
        } else {
            status_buf.write(msg);
        }
    }

	/** Start the tank daemon thread */
    public void run() {
        long[] last_contact = new long[tankProxy.length + 1];
        long last;
        boolean idle;
        byte msg;
        int i, id, lru;

        try {
            for (;;) {
                idle = true;

                for (i=0; i= 0) synchronized (this) {
                tankProxy[id].shutdown();
                tankProxy[id] = null;
            }
		}
	}
}