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

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 java.awt.Color;
import java.awt.Point;
import java.io.*;
import java.util.*;

/**
 *  A class that listens for incoming messages from the server, and tells the
 *  client about them.
 *
 * @author     NiklasHallenfur, Henrik Hägerström, Jon Åslund
 * @created    June 27, 2001
 */

public class DataInputExchangeHandler implements Runnable {

    /**
     *  Description of the Field
     */
    private ServerConnection connection;
    /**
     *  Description of the Field
     */
    private Client client;
    /*
     * the ServerAmbassador who created this object
     */
    /**
     *  Description of the Field
     */
    private ServerAmbassador i_am_working_for;
    /*
     * set to true when the thread should end
     */
    /**
     *  Description of the Field
     */
    private boolean die;
    /*
     * only needed for Ping response
     */
    /**
     *  Description of the Field
     */
    private DataOutputExchangeHandler DEHOutput;


    /**
     * @param  sc         connection to server containing the stream to write to
     * @param  cl         the Client to send incoming messages to
     * @param  sa         the ServerAmbassador this thread is working for
     *      DEHOutput the object to send PingReplyMessage:s to
     * @param  DEHOutput  Description of Parameter
     */
    public DataInputExchangeHandler(ServerConnection sc,
            Client cl, ServerAmbassador sa, DataOutputExchangeHandler DEHOutput) {
        connection = sc;
        client = cl;
        i_am_working_for = sa;
        die = false;
        this.DEHOutput = DEHOutput;
    }


    /**
     *  Main processing method for the DataInputExchangeHandler object
     */
    public void run() {
        while (!die) {
            try {
                /*
                 * Blocking read int
                 */
                int leadingInteger = connection.getDataInputStream().readInt();
                /*
                 * Receive the rest of the message
                 */
                receiveMessage(leadingInteger);
            } catch (Exception e) {
                i_am_working_for.connectionLost();
                break;
            }
            // Yield to other threads!
            Thread.yield();
        }
    }


    /**
     *  called whenever someone wants this thread to die
     */

    public void kill() {
        die = true;
    }


    /**
     *  Get a message from the server that updates the position of a unit
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitPositionUpdate() throws Exception {

        int handle = connection.getDataInputStream().readInt();
        double x = connection.getDataInputStream().readDouble();
        double y = connection.getDataInputStream().readDouble();
        if (!die) {
            client.updatePiece(handle, new Position(x, y));
        }
    }


    /**
     *  Get a message from the server that updates the properties of a unit
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitPropertyUpdate() throws Exception {

        int handle = connection.getDataInputStream().readInt();

        String property;
        String value;

        Property[] props = new Property[1];

        property = connection.getDataInputStream().readUTF();
        value = connection.getDataInputStream().readUTF();
        props[0] = new Property(property, value);
        if (!die) {
            client.updatePiece(handle, props);
        }
    }


    /**
     *  Get a message that a new unit is visible to the client, gets the unit's
     *  properties and action, type, etc.
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitVisible() throws Exception {
        int unit = connection.getDataInputStream().readInt();
        String name = connection.getDataInputStream().readUTF();
        String type = connection.getDataInputStream().readUTF();
        double x = connection.getDataInputStream().readDouble();
        double y = connection.getDataInputStream().readDouble();

        /*
         * get actions
         */
        int actionsSize = connection.getDataInputStream().readInt();
        Vector actions = new Vector(actionsSize);
        for (int i = 0; i < actionsSize; i++) {
            String atype = connection.getDataInputStream().readUTF();
            String adesc = connection.getDataInputStream().readUTF();
            int paramType = connection.getDataInputStream().readInt();
            boolean instant = connection.getDataInputStream().readBoolean();
            actions.add(new Action(atype, adesc, paramType, instant));
        }

        /*
         * get properties
         */
        int propsSize = connection.getDataInputStream().readInt();
        Vector props = new Vector(propsSize);
        for (int i = 0; i < propsSize; i++) {
            String pname = connection.getDataInputStream().readUTF();
            String value = connection.getDataInputStream().readUTF();
            props.add(new Property(pname, value));
        }

        if (!die) {
            client.addPiece(unit, name, new Position(x, y), actions.elements(),
                    props.elements(), type);
        }
    }


    /**
     *  get an update of a map point. (not used, getMapUpdates is used instead
     *
     * @exception  Exception  Description of Exception
     */
    private void getMapUpdate() throws Exception {
        int x = connection.getDataInputStream().readInt();
        int y = connection.getDataInputStream().readInt();
        int coln = connection.getDataInputStream().readInt();

        if (!die) {
            client.mapUpdate(new Point(x, y), coln);
        }
    }


    /**
     *  an action is no longer valid for a certain unit, get which action and
     *  which unit
     *
     * @exception  Exception  Description of Exception
     */
    private void getActionBlocked() throws Exception {
        int handle = connection.getDataInputStream().readInt();
        String atype = connection.getDataInputStream().readUTF();
        String adesc = connection.getDataInputStream().readUTF();
        int paramType = connection.getDataInputStream().readInt();
        boolean instant = connection.getDataInputStream().readBoolean();
        boolean isBlocked = connection.getDataInputStream().readBoolean();
        if (isBlocked) {
            if (!die) {
                client.removePieceAction(handle, new Action(atype, adesc, paramType, instant));
            }
        } else {
            if (!die) {
                client.addPieceAction(handle, new Action(atype, adesc, paramType, instant));
            }
        }
    }


    /**
     *  gets which unit has been deleted (not necessarily destroyed)
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitDeleted() throws Exception {
        int handle = connection.getDataInputStream().readInt();
        if (!die) {
            client.removePiece(handle);
        }
    }


    /**
     *  gets a message about a certain unit
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitMessage() throws Exception {
        int handle = connection.getDataInputStream().readInt();
        String message = connection.getDataInputStream().readUTF();
        if (!die) {
            client.unitMessage(handle, message);
        }
    }


    /**
     *  a unit has been destroyed, get which one
     *
     * @exception  Exception  Description of Exception
     */
    private void getUnitDestroyed() throws Exception {
        int handle = connection.getDataInputStream().readInt();
        if (!die) {
            client.destroyPiece(handle);
        }
    }


    /**
     *  get which map point is no longer visible. getMapPointsInvisible is the
     *  preferred method to use
     *
     * @exception  Exception  Description of Exception
     */
    private void getMapPointInvisible() throws Exception {
        int x = connection.getDataInputStream().readInt();
        int y = connection.getDataInputStream().readInt();
        if (!die) {
            client.invisibilizeMapPoint(new Point(x, y));
        }
    }


    /**
     *  gets an array of Points that are no longer visible
     *
     * @exception  Exception  Description of Exception
     */
    private void getMapPointsInvisible() throws Exception {
        int size = connection.getDataInputStream().readInt();
        byte[] b = new byte[size];
        Point[] points = new Point[size / 8];
        connection.getDataInputStream().readFully(b);

        for (int i = 0; i < size / 8; i++) {
            int x = (getInt(b, i * 8) << 24)
                     + (getInt(b, i * 8 + 1) << 16)
                     + (getInt(b, i * 8 + 2) << 8)
                     + getInt(b, i * 8 + 3);

            int y = (getInt(b, i * 8 + 4) << 24)
                     + (getInt(b, i * 8 + 5) << 16)
                     + (getInt(b, i * 8 + 6) << 8)
                     + getInt(b, i * 8 + 7);

            points[i] = new Point(x, y);
        }
        if (!die) {
            client.invisibilizeMapPoints(points);
        }
    }


    /**
     *  A helper function that makes unsigned casting from byte to int
     *
     * @param  byteArray  Description of Parameter
     * @param  index      Description of Parameter
     * @return            The Int value
     */
    private int getInt(byte[] byteArray, int index) {
        // The given byte is always in the range 0-255, i.e. -128 - 127
        int foo = (int) byteArray[index];
        if (foo < 0) {
            return foo + 256;
        }
        return foo;
    }


    /**
     *  Gets an array of positions and new colors for mappoints.
     *
     * @exception  Exception  Description of Exception
     */

    private void getMapUpdates() throws Exception {
        int size = connection.getDataInputStream().readInt();
        byte[] b = new byte[size];
        connection.getDataInputStream().readFully(b);
        Point[] points = new Point[size / 11];
        int[] rgbs = new int[size / 11];

        for (int i = 0; i < size / 11; i++) {
            int x = (getInt(b, i * 11) << 24)
                     + (getInt(b, i * 11 + 1) << 16)
                     + (getInt(b, i * 11 + 2) << 8)
                     + getInt(b, i * 11 + 3);

            int y = (getInt(b, i * 11 + 4) << 24)
                     + (getInt(b, i * 11 + 5) << 16)
                     + (getInt(b, i * 11 + 6) << 8)
                     + getInt(b, i * 11 + 7);

            int c = (getInt(b, i * 11 + 8) << 16)
                     + (getInt(b, i * 11 + 9) << 8)
                     + getInt(b, i * 11 + 10);
            points[i] = new Point(x, y);
            rgbs[i] = c;
        }
        if (!die) {
            client.mapUpdate(points, rgbs);
        }
    }


    /**
     *  Gets the handle of an action that has been completed
     *
     * @exception  Exception  Description of Exception
     */
    private void getActionCompleted() throws Exception {
        long handle = connection.getDataInputStream().readLong();
        String message = connection.getDataInputStream().readUTF();
        if (!die) {
            client.actionReply(handle, true, message);
        }
    }


    /**
     *  Gets the handle and an error message for an action that failed
     *
     * @exception  Exception  Description of Exception
     */
    private void getActionFailed() throws Exception {
        long handle = connection.getDataInputStream().readLong();
        String err = connection.getDataInputStream().readUTF();
        if (!die) {
            client.actionReply(handle, false, err);
        }
    }


    /**
     *  Called when there are messages to receive from the server. Checks the
     *  type of message to get, and calls the appropriate method that gets that
     *  kind of message
     *
     * @param  leadingInteger  Description of Parameter
     * @exception  Exception   Description of Exception
     */
    private void receiveMessage(int leadingInteger) throws Exception {
        switch (leadingInteger) {

            case MessageType.Ping:
                DEHOutput.addMessageToQueue(new PingReplyMessage());
                break;
            case MessageType.UnitPositionUpdate:
                getUnitPositionUpdate();
                break;
            case MessageType.UnitPropertyUpdate:
                getUnitPropertyUpdate();
                break;
            case MessageType.UnitMessage:
                getUnitMessage();
                break;
            case MessageType.UnitVisible:
                getUnitVisible();
                break;
            case MessageType.ActionBlocked:
                getActionBlocked();
                break;
            case MessageType.MapUpdate:
                getMapUpdate();
                break;
            case MessageType.UnitDeleted:
                getUnitDeleted();
                break;
            case MessageType.UnitDestroyed:
                getUnitDestroyed();
                break;
            case MessageType.MapPointInvisible:
                getMapPointInvisible();
                break;
            case MessageType.ActionCompleted:
                getActionCompleted();
                break;
            case MessageType.ActionFailed:
                getActionFailed();
                break;
            case MessageType.MapPointsInvisible:
                getMapPointsInvisible();
                break;
            case MessageType.MapUpdates:
                getMapUpdates();
                break;
            default:
                System.out.println("Got invalid message");
        }
    }

}
