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

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 sends messages to the server
 *
 * @author     Niklas Hallenfur, Henrik Hägerström, Jon Åslund
 * @created    June 27, 2001
 */

public class DataOutputExchangeHandler implements Runnable {

    /**
     *  Description of the Field
     */
    private MessageQueue messages_to_send;
    /**
     *  Description of the Field
     */
    private ServerConnection connection;
    /**
     *  Description of the Field
     */
    private ServerAmbassador i_am_working_for;
    /**
     *  Description of the Field
     */
    private boolean die;


    /**
     * @param  q   the queue that will contain the messages to send
     * @param  sc  the connection to send messages to
     * @param  sa  the serverambassador who created this object
     */
    public DataOutputExchangeHandler(MessageQueue q, ServerConnection sc,
            ServerAmbassador sa) {
        messages_to_send = q;
        connection = sc;
        i_am_working_for = sa;
        die = false;
    }


    /**
     *  Used by the ServerAmbassador to add messages to send
     *
     * @param  message  The feature to be added to the MessageToQueue attribute
     */
    public void addMessageToQueue(Object message) {
        synchronized (messages_to_send) {
            messages_to_send.add(message);
            messages_to_send.notifyAll();
        }
    }


    /**
     *  Main processing method for the DataOutputExchangeHandler object
     */
    public void run() {
        while (!die) {
            try {
                Object o = null;
                // Wait until a message is placed in the queue if it's empty!
                synchronized (messages_to_send) {
                    if (messages_to_send.isEmpty()) {
                        messages_to_send.wait();
                    }
                    o = messages_to_send.get();
                }
                // Release sync on message queue, and send message!
                if (o != null) {
                    sendMessage(o);
                    // If we've done something, yield to other threads before continuing!
                    Thread.yield();
                }
            } catch (Exception e) {
                i_am_working_for.connectionLost();
                break;
            }
        }
    }


    /**
     *  called whenever someone wants this thread to die
     */
    public void kill() {
        die = true;
    }


    /**
     *  Called when there are messages to send in the queue. Checks the class of
     *  the message to send, and calls the appropriate method that sends that
     *  kind of message
     *
     * @param  o              Description of Parameter
     * @exception  Exception  Description of Exception
     */

    private void sendMessage(Object o) throws Exception {
        if (o instanceof gecco.communication.messages.PingReplyMessage) {
            sendPingReplyMessage((PingReplyMessage) o);

        } else if (o instanceof gecco.communication.messages.ActionOnVoidMessage) {
            sendActionOnVoidMessage((ActionOnVoidMessage) o);

        } else if (o instanceof gecco.communication.messages.ActionOnUnitMessage) {
            sendActionOnUnitMessage((ActionOnUnitMessage) o);

        } else if (o instanceof gecco.communication.messages.ActionOnPositionMessage) {
            sendActionOnPositionMessage((ActionOnPositionMessage) o);

        } else if (o instanceof gecco.communication.messages.AbortActionMessage) {
            sendAbortActionMessage((AbortActionMessage) o);

        } else if (o instanceof gecco.communication.messages.AbortGameMessage) {
            sendAbortGameMessage();

        } else if (o instanceof gecco.communication.messages.StartGameMessage) {
            sendStartGameMessage();

        }

        try {
            connection.getDataOutputStream().flush();
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a reply to a received ping message from the server
     *
     * @param  m  Description of Parameter
     */
    private void sendPingReplyMessage(PingReplyMessage m) {
        try {
            connection.getDataOutputStream().writeInt(MessageType.PingReply);
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a message that tells the server that a unit wants to perform an
     *  action with no target
     *
     * @param  m  Description of Parameter
     */
    private void sendActionOnVoidMessage(ActionMessage m) {
        try {
            connection.getDataOutputStream().writeInt(MessageType.ActionOnVoid);
            connection.getDataOutputStream().writeLong(m.getId());
            connection.getDataOutputStream().writeInt(m.getPieceHandle());
            connection.getDataOutputStream().writeUTF(m.getActionHandle());
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a message that tells the server that a unit wants to perform an
     *  action with a unit as target
     *
     * @param  m  Description of Parameter
     */
    private void sendActionOnUnitMessage(ActionOnUnitMessage m) {
        try {
            connection.getDataOutputStream().writeInt(MessageType.ActionOnUnit);
            connection.getDataOutputStream().writeLong(m.getId());
            connection.getDataOutputStream().writeInt(m.getPieceHandle());
            connection.getDataOutputStream().writeUTF(m.getActionHandle());
            connection.getDataOutputStream().writeInt(m.getTargetPieceHandle());
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a message that tells the server that a unit wants to perform an
     *  action with a position as target
     *
     * @param  m  Description of Parameter
     */
    private void sendActionOnPositionMessage(ActionOnPositionMessage m) {
        try {
            connection.getDataOutputStream().writeInt(MessageType.ActionOnPosition);
            connection.getDataOutputStream().writeLong(m.getId());
            connection.getDataOutputStream().writeInt(m.getPieceHandle());
            connection.getDataOutputStream().writeUTF(m.getActionHandle());
            connection.getDataOutputStream().writeDouble(m.getX());
            connection.getDataOutputStream().writeDouble(m.getY());
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a message that tells the server that a unit wants to abort an
     *  action
     *
     * @param  m  Description of Parameter
     */
    private void sendAbortActionMessage(AbortActionMessage m) {
        try {
            connection.getDataOutputStream().writeInt(MessageType.abortAction);
            connection.getDataOutputStream().writeLong(m.getId());
        } catch (Exception e) {
            i_am_working_for.connectionLost();
        }
    }


    /**
     *  Sends a message that tells the server that this client wants to abort
     *  the game (disconnect)
     *
     * @exception  Exception  Description of Exception
     */
    private void sendAbortGameMessage() throws Exception {
        connection.getDataOutputStream().writeInt(MessageType.abortGame);
        kill();
    }


    /**
     *  Sends a message that tells the server that this client (role) is ready
     *  to receive messages, and wants all its visible information
     *
     * @exception  Exception  Description of Exception
     */
    private void sendStartGameMessage() throws Exception {
        connection.getDataOutputStream().writeInt(MessageType.startGame);
    }

}
