/***********************************************************************

GECCO, a platform for command and control gaming

Copyright (C) 2001 Henrik Bäärnhielm, Andreas Enblom, Niklas
Hallenfur, Karl Hasselström, Henrik Hägerström, Oskar Linde, Jing Fu
Zi and Jon Åslund

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.

***********************************************************************/

package gecco.communication.server;
import gecco.communication.messages.*;
import gecco.server.core.ActionDefinition;
import gecco.server.core.Client;
import gecco.server.core.Server;
import java.awt.*;
import java.io.*;

import java.util.*;

/**
 *  The class that implements gecco.server.core.Client, and the only part of the
 *  communication package that the server needs to know about
 *
 * @author     Niklas Hallenfur, Henrik Hägerström, Jon Åslund
 * @created    June 27, 2001
 */

public class ClientAmbassador implements gecco.server.core.Client {
    /*
     * a hashtable containing elements of class RoleConnection
     */
    /**
     *  Description of the Field
     */
    private Hashtable roles;
    /*
     * a Vector containing elements of class RoleDefinition
     */
    /**
     *  Description of the Field
     */
    private Vector roledefs;

    /**
     *  Description of the Field
     */
    private gecco.server.core.Server server;

    /**
     *  Description of the Field
     */
    private byte[] initialMapRGB;
    /**
     *  Description of the Field
     */
    private Dimension mapSize;


    /**
     *  Initiates the ServerAmbassador, and also starts a Thread that listens
     *  for incoming clients
     *
     * @param  rd      A vector containing RoleDefinition objects, used to
     *      create the appropriate number of RoleConnectoions, and to keep track
     *      of what roles are still available to join as
     * @param  server  The server from the communication parts point of view
     * @param  port    The port at which to listen for incoming connections from
     *      clients.
     */

    public ClientAmbassador(Vector rd, gecco.server.core.Server server, int port) {
        this.server = server;
        roledefs = new Vector(rd);
        roles = new Hashtable(roledefs.size());
        RoleDefinition c;

        for (int i = 0; i < roledefs.size(); i++) {
            c = (RoleDefinition) roledefs.get(i);
            roles.put(c.role(), new RoleConnection(c.role(), server));
        }

        // Fetch original map from server!
        initialMapRGB = server.getMap();
        mapSize = server.getMapSize();

        try {
            NewClientListener ncl = new NewClientListener(this, port);
            Thread t = new Thread(ncl);
            t.start();
        } catch (IOException ioe) {
            System.out.println("ClientAmbassador(): Could not create NewClientListener");
        }
    }


    /**
     *  returns the number of clients that are connected as 'role' at the
     *  moment.
     *
     * @param  role  Description of Parameter
     * @return       The NumberOfClients value
     */

    public int getNumberOfClients(String role) {
        if (roles.containsKey(role)) {
            return ((RoleConnection) roles.get(role)).getNumberOfClients();
        }
        return -1;
    }


    /**
     *  returns the number of clients that are connected as any role
     *
     * @return    The NumberOfClients value
     */

    public int getNumberOfClients() {
        int r = 0;
        Enumeration e = roles.elements();
        while (e.hasMoreElements()) {
            r = r + ((RoleConnection) e.nextElement()).getNumberOfClients();
        }
        return r;
    }


    /**
     *  Called by NewClientListener when it is about to add a client, and wants
     *  to know what id the client should have. returns a id that is unused by
     *  clients of role 'role'
     *
     * @param  role  Description of Parameter
     * @return       The AvailableId value
     */

    public int getAvailableId(String role) {
        return ((RoleConnection) roles.get(role)).getAvailableId();
    }


    /**
     *  Gets the MapSize attribute of the ClientAmbassador object
     *
     * @return    The MapSize value
     */
    public Dimension getMapSize() {
        return mapSize;
    }


    /**
     *  Gets the Map attribute of the ClientAmbassador object
     *
     * @return    The Map value
     */
    public byte[] getMap() {
        return initialMapRGB;
    }


    /**
     *  Gets the UnitTypeIcons attribute of the ClientAmbassador object
     *
     * @return    The UnitTypeIcons value
     */
    public Map getUnitTypeIcons() {
        return server.getUnitTypeIcons();
    }


    /**
     *  Called by NewClientListener when it has received a legal join request
     *  from a client
     *
     * @param  c  The object that represents the connection to the client to
     *      join.
     */

    public void addClient(ClientConnection c) {
        ((RoleConnection) roles.get(c.getRole())).add(c);
    }


    /**
     *  Called by NewClientListener when it wants to check if the role a client
     *  wants to join as, is a legal role
     *
     * @param  role  Description of Parameter
     * @return       Description of the Returned Value
     */

    public boolean acceptRole(String role) {
        /*
         * if the role doesn't exist, return false
         */
        if (!roles.containsKey(role)) {
            return false;
        }

        /*
         * leta upp rollens RoleDefinition
         */
        for (int i = 0; i < roledefs.size(); i++) {
            if (role.equals(((RoleDefinition) roledefs.get(i)).role())) {
                if (((RoleDefinition) roledefs.get(i)).isUnique()) {
                    /*
                     * if the role is unique, and no client is using it,
                     * return true
                     */
                    return ((RoleConnection) roles.get(role)).getNumberOfClients() == 0;
                } else {
                    /*
                     * if the role is not unique, return true
                     */
                    return true;
                }
            }
        }
        /*
         * the role is unique and already used, return false
         */
        return false;
    }


    /**
     *  returns a String with the names of all connected clients
     *
     * @return    Description of the Returned Value
     */

    public String whoIsConnected() {
        String clients = "";
        Enumeration elmts = roles.elements();
        while (elmts.hasMoreElements()) {
            clients = clients + ((RoleConnection) elmts.nextElement()).whoIsConnected();
        }
        return clients;
    }


    /**
     *  Called by NewClientListener when a client has asked for the available
     *  roles. returns the available roles as a String, with the roles separated
     *  by spaces
     *
     * @return    Description of the Returned Value
     */

    public String rolesLeft() {
        String possible = "";
        for (int i = 0; i < roledefs.size(); i++) {
            if (acceptRole(((RoleDefinition) roledefs.get(i)).role())) {
                possible = possible + "\n" + ((RoleDefinition) roledefs.get(i)).role();
            }
        }
        return possible;
    }



    /**
     *  Description of the Method
     *
     * @param  role  Description of Parameter
     * @param  id    Description of Parameter
     */
    public void connectionLost(String role, int id) {

    }


    /**
     *  Here follows the implementation of the methods in interface
     *  gecco.server.core.Client. For details, see gecco.server.core.Client.
     *
     * @param  role              Description of Parameter
     * @param  handle            Description of Parameter
     * @param  name              Description of Parameter
     * @param  type              Description of Parameter
     * @param  x                 Description of Parameter
     * @param  y                 Description of Parameter
     * @param  unblockedActions  Description of Parameter
     * @param  blockedActions    Description of Parameter
     * @param  properties        Description of Parameter
     */
    public void unitVisible(String role, int handle, String name, String type,
            double x, double y, Set unblockedActions,
            Set blockedActions, Map properties) {

        ServerMessage m = new UnitVisibleMessage(handle, name, type, x, y,
                unblockedActions,
                blockedActions,
                properties);
        RoleConnection roleCon = (RoleConnection) roles.get(role);
        roleCon.sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role    Description of Parameter
     * @param  handle  Description of Parameter
     */
    public void unitInvisible(String role, int handle) {
        ServerMessage m = new UnitDeletedMessage(handle);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role    Description of Parameter
     * @param  handle  Description of Parameter
     */
    public void unitDeleted(String role, int handle) {
        ServerMessage m = new UnitDestroyedMessage(handle);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role    Description of Parameter
     * @param  handle  Description of Parameter
     * @param  newX    Description of Parameter
     * @param  newY    Description of Parameter
     */
    public void unitMoved(String role, int handle, double newX, double newY) {
        ServerMessage m = new UnitPositionUpdateMessage(handle,
                newX, newY);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role      Description of Parameter
     * @param  handle    Description of Parameter
     * @param  key       Description of Parameter
     * @param  newValue  Description of Parameter
     */
    public void unitPropertyChanged(String role, int handle,
            String key, String newValue) {
        ServerMessage m = new UnitPropertyChangedMessage(handle, key,
                newValue);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role       Description of Parameter
     * @param  handle     Description of Parameter
     * @param  action     Description of Parameter
     * @param  isBlocked  Description of Parameter
     */
    public void unitActionBlocked(String role, int handle,
            ActionDefinition action, boolean isBlocked) {
        ServerMessage m = new
                ActionBlockedMessage(handle,
                action.name,
                action.description,
                action.argumentType,
                isBlocked);

        ((RoleConnection) roles.get(role)).sendMessage(m);

    }


    /**
     *  Description of the Method
     *
     * @param  role   Description of Parameter
     * @param  x      Description of Parameter
     * @param  y      Description of Parameter
     * @param  color  Description of Parameter
     */
    public void automatonHasState(String role, int x, int y,
            Color color) {

        ServerMessage m = new MapUpdateMessage(x, y, color);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role  Description of Parameter
     * @param  x     Description of Parameter
     * @param  y     Description of Parameter
     */
    public void automatonInvisible(String role, int x, int y) {
        ServerMessage m = new MapPointInvisibleMessage(x, y);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role          Description of Parameter
     * @param  actionHandle  Description of Parameter
     * @param  unitMessage   Description of Parameter
     */
    public void actionCompleted(String role, int actionHandle, String unitMessage) {
        ServerMessage m = new ActionCompletedMessage((long) actionHandle, unitMessage);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role          Description of Parameter
     * @param  actionHandle  Description of Parameter
     * @param  errorMessage  Description of Parameter
     */
    public void actionFailed(String role, int actionHandle,
            String errorMessage) {
        ServerMessage m = new ActionFailedMessage((long) actionHandle, errorMessage);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role          Description of Parameter
     * @param  pointObjects  Description of Parameter
     * @param  colors        Description of Parameter
     */
    public void automatonsHasState(String role, java.util.List pointObjects, java.util.List colors) {
        byte[] changes = new byte[11 * pointObjects.size()];

        Iterator it = pointObjects.iterator();
        Iterator colIt = colors.iterator();
        int i = 0;
        while (it.hasNext()) {
            gecco.server.visibility.Point p = (gecco.server.visibility.Point) it.next();
            int col = ((Color) colIt.next()).getRGB();

            changes[i * 11 + 0] = (byte) (p.x >> 24);
            changes[i * 11 + 1] = (byte) (p.x >> 16);
            changes[i * 11 + 2] = (byte) (p.x >> 8);
            changes[i * 11 + 3] = (byte) (p.x);

            changes[i * 11 + 4] = (byte) (p.y >> 24);
            changes[i * 11 + 5] = (byte) (p.y >> 16);
            changes[i * 11 + 6] = (byte) (p.y >> 8);
            changes[i * 11 + 7] = (byte) (p.y);

            changes[i * 11 + 8] = (byte) (col >> 16);
            changes[i * 11 + 9] = (byte) (col >> 8);
            changes[i * 11 + 10] = (byte) (col);
            i++;
        }
        MapUpdatesMessage m = new MapUpdatesMessage(changes);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }


    /**
     *  Description of the Method
     *
     * @param  role          Description of Parameter
     * @param  pointObjects  Description of Parameter
     */
    public void automatonsInvisible(String role, java.util.List pointObjects) {
        byte[] changes = new byte[8 * pointObjects.size()];

        Iterator it = pointObjects.iterator();
        int i = 0;
        while (it.hasNext()) {
            gecco.server.visibility.Point p = (gecco.server.visibility.Point) it.next();

            /*
             * maybe & 255
             */

            changes[i * 8 + 0] = (byte) (p.x >> 24);
            changes[i * 8 + 1] = (byte) (p.x >> 16);
            changes[i * 8 + 2] = (byte) (p.x >> 8);
            changes[i * 8 + 3] = (byte) (p.x);

            changes[i * 8 + 4] = (byte) (p.y >> 24);
            changes[i * 8 + 5] = (byte) (p.y >> 16);
            changes[i * 8 + 6] = (byte) (p.y >> 8);
            changes[i * 8 + 7] = (byte) (p.y);
            i++;
        }
        MapPointsInvisibleMessage m = new MapPointsInvisibleMessage(changes);

        ((RoleConnection) roles.get(role)).sendMessage(m);

    }


    /**
     *  Description of the Method
     *
     * @param  role        Description of Parameter
     * @param  unitHandle  Description of Parameter
     * @param  message     Description of Parameter
     */
    public void unitMessage(String role, int unitHandle, String message) {
        // Send unit message to role
        UnitMessageMessage m = new UnitMessageMessage(unitHandle, message);
        ((RoleConnection) roles.get(role)).sendMessage(m);
    }

}
