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

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.client;
import gecco.client.*;

import gecco.communication.messages.*;
import gecco.communication.messages.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;

import java.util.*;

/**
 *  This is the class that implements gecco.client.Server, and is the only part
 *  of the communication the client knows of.
 *
 * @author     Niklas Hallenfur, Jon Åslund
 * @created    June 27, 2001
 */

public class ServerAmbassador implements gecco.client.Server {

    /**
     *  Description of the Field
     */
    private ServerConnection sc;
    /**
     *  Description of the Field
     */
    private gecco.client.Client cl;
    /**
     *  Description of the Field
     */
    private boolean joined;
    /**
     *  Description of the Field
     */
    private DataInputExchangeHandler DEHInput;
    /**
     *  Description of the Field
     */
    private DataOutputExchangeHandler DEHOutput;

    /*
     * contains the next unique available id for an action
     */
    /**
     *  Description of the Field
     */
    private int actionId;
    /**
     *  Description of the Field
     */
    private int connectionstatus;

    /**
     *  Description of the Field
     */
    private final static int DISCONNECTED = 0;
    /**
     *  Description of the Field
     */
    private final static int HANDSHAKING = 1;
    /**
     *  Description of the Field
     */
    private final static int JOINED = 2;


    /**
     * @param  cl  The object that implements gecco.client.Client, and the only
     *      part of the client that the communication part is aware of
     */
    public ServerAmbassador(gecco.client.Client cl) {
        sc = new ServerConnection();
        actionId = 0;
        this.cl = cl;
        connectionstatus = DISCONNECTED;

    }


    /**
     *  asks the server which roles are available for this client to join as
     *
     * @return    The AvailableRoles value
     */
    public StringTokenizer getAvailableRoles() {
        if (connectionstatus == HANDSHAKING) {
            try {
                sc.getDataOutputStream().writeInt(MessageType.getAvailableRoles);
                sc.getDataOutputStream().flush();
                String readrole = sc.getDataInputStream().readUTF();
                return new StringTokenizer(readrole, "\n");
            } catch (IOException ioe) {
                connectionLost();
            }
        }
        return null;
    }


    /**
     *  Called by the client to get the coordinates of the upper left corner of
     *  the map
     *
     * @return    The MapUpperLeft value
     */
    public Point getMapUpperLeft() {
        return new Point(0, 0);
    }


    /**
     *  Called by the client when it wants to know the size of the map
     *
     * @return    The MapSize value
     */
    public Dimension getMapSize() {
        if (connectionstatus != HANDSHAKING) {
            return null;
        }

        try {
            sc.getDataOutputStream().writeInt(MessageType.getMapSize);
            sc.getDataOutputStream().flush();

            int width = sc.getDataInputStream().readInt();
            int height = sc.getDataInputStream().readInt();
            return new Dimension(width, height);
        } catch (Exception e) {
            connectionLost();
            return null;
        }
    }


    /**
     *  Called by the client when it wants to get the whole map
     *
     * @return    The Map value
     */
    public Image getMap() {
        if (connectionstatus != HANDSHAKING) {
            return null;
        }
        try {
            Dimension d = getMapSize();
            int mapArraySize = d.width * d.height * 3;
            sc.getDataOutputStream().writeInt(MessageType.getMap);
            sc.getDataOutputStream().flush();
            byte[] RGBvalues = new byte[mapArraySize];

            for (int i = 0; i < d.height; i++) {

                sc.getDataInputStream().readFully(RGBvalues,
                        d.width * i * 3, 3 * d.width);
            }

            BufferedImage map = new BufferedImage(d.width, d.height,
                    BufferedImage.TYPE_INT_RGB);
            for (int x = 0; x < d.width; x++) {
                for (int y = 0; y < d.height; y++) {
                    int r = (int) RGBvalues[x * 3 + d.width * y * 3];
                    int g = (int) RGBvalues[x * 3 + 1 + d.width * y * 3];
                    int b = (int) RGBvalues[x * 3 + 2 + d.width * y * 3];

                    if (r < 0) {
                        r += 256;
                    }
                    if (g < 0) {
                        g += 256;
                    }
                    if (b < 0) {
                        b += 256;
                    }

                    r = r << 16;
                    g = g << 8;

                    map.setRGB(x, y, r + g + b);
                }
                cl.mapLoadingProgressUpdate((double) x / (double) d.width);
            }
            cl.mapLoadingProgressUpdate(1.0);

            // Read the .gif/.jpg-image icons for the unit types!

            // Get number of types
            int numUnitTypes = sc.getDataInputStream().readInt();

            for (int i = 0; i < numUnitTypes; i++) {
                // Read which unit type the next icon represents!
                String type = sc.getDataInputStream().readUTF();
                // Read the size of the image bytearray!
                int numBytes = sc.getDataInputStream().readInt();
                byte[] icon = new byte[numBytes];
                // Read the image bytearray!
                sc.getDataInputStream().readFully(icon);
                Image ic = Toolkit.getDefaultToolkit().
                        createImage(icon, 0, numBytes);
                Symbols.addPieceSymbol(type, ic);
            }
            return map;
        } catch (Exception e) {
            connectionLost();
            return null;
        }
    }


    /**
     *  tries to connect to the server returns false if failed to connect
     *
     * @param  host  The name of the host where to find the server
     * @param  port  The port number to connect to
     * @return       Description of the Returned Value
     */
    public boolean connect(String host, int port) {
        try {
            sc.connectToServer(host, port);
            connectionstatus = HANDSHAKING;
            return true;
        } catch (Exception e) {
            connectionstatus = DISCONNECTED;
            return false;
        }
    }


    /**
     *  Tells the server that the client wants to join the game.
     *
     * @param  role  The role this client wants to join as
     * @return       Description of the Returned Value
     */
    public boolean joinAsRole(String role) {
        if (connectionstatus == HANDSHAKING) {
            try {
                sc.getDataOutputStream().writeInt(MessageType.joinAsRole);
                sc.getDataOutputStream().writeUTF(role);
                sc.getDataOutputStream().flush();
                boolean answer = sc.getDataInputStream().readBoolean();

                if (answer == true) {
                    return true;
                }
            } catch (IOException ioe) {
                connectionLost();
            }
        }
        return false;
    }


    /**
     *  Called whenever a communication error occurs. Kills the Thread that
     *  checks for incoming and outgoing messages, and tells the client that the
     *  connection i lost
     */
    public void connectionLost() {

        if (connectionstatus == JOINED) {
            DEHInput.kill();
            DEHOutput.kill();
        }
        if (connectionstatus != DISCONNECTED) {
            cl.connectionLost();
        }
        connectionstatus = DISCONNECTED;
    }


    /*
     * called by the client when it wants a unit to
     * perform an action.
     * Checks the target type, and creates an ActionMessage
     * of the appropriate type, and adds it to the send queue
     * returns a unique id for the action
     * returns -1 if the action couldn't be sent
     */
    /**
     *  Description of the Method
     *
     * @param  pieceHandle  Description of Parameter
     * @param  a            Description of Parameter
     * @return              Description of the Returned Value
     */
    public long doAction(int pieceHandle, Action a) {
        if (connectionstatus != JOINED) {
            return -1;
        }

        ActionMessage am;

        switch (a.getParameterType()) {
            case gecco.client.Action.VOID:
                am = new ActionOnVoidMessage(actionId, pieceHandle, a.getType());
                break;
            case gecco.client.Action.PIECE:
                gecco.client.Piece t_piece = (gecco.client.Piece) a.getParameter();
                am = new ActionOnUnitMessage(actionId, pieceHandle, a.getType(), t_piece.getHandle());
                break;
            case gecco.client.Action.POSITION:
                gecco.client.Position t_pos = (gecco.client.Position) a.getParameter();
                am = new ActionOnPositionMessage(actionId, pieceHandle, a.getType(), t_pos.getX(), t_pos.getY());
                break;
            default:
                am = null;
                break;
        }
        if (am != null) {
            DEHOutput.addMessageToQueue(am);
            actionId++;
            return (actionId - 1);
        } else {
            return -1;
        }

    }


    /**
     *  Called by the client when it wants a unit to abort an action
     *
     * @param  actionId  Description of Parameter
     */
    public void abortAction(long actionId) {
        if (connectionstatus != JOINED) {
            return;
        }
        AbortActionMessage am = new AbortActionMessage(actionId);
        DEHOutput.addMessageToQueue(am);
    }


    /**
     *  Called by the client when it wants to disconnect
     */
    public void disconnect() {
        if (connectionstatus == DISCONNECTED) {
            return;
        }

        if (connectionstatus == HANDSHAKING) {
            try {
                sc.getDataOutputStream().writeInt(MessageType.disconnect);
                sc.getDataOutputStream().flush();
                return;
            } catch (Exception e) {
                return;
            }
        }

        try {
            DEHOutput.addMessageToQueue(new AbortGameMessage());
        } catch (Exception e) {
            connectionLost();
        }
        connectionstatus = DISCONNECTED;
    }


    /**
     *  Called by the client when it is ready to receive messages from the
     *  server
     */
    public void startGame() {
        try {
            if (connectionstatus == HANDSHAKING) {

                sc.getDataOutputStream().writeInt(MessageType.startGame);

                DEHOutput = new DataOutputExchangeHandler(new MessageQueue(), sc, this);
                Thread t = new Thread(DEHOutput);
                t.start();
                DEHInput = new DataInputExchangeHandler(sc, cl, this, DEHOutput);
                t = new Thread(DEHInput);
                t.start();

                DEHOutput.addMessageToQueue(new StartGameMessage());
                connectionstatus = JOINED;
            }
        } catch (Exception e) {
            connectionLost();
        }
    }

}






