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

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.server.qmanager;

import gecco.server.automaton.*;
import gecco.server.core.*;

import java.awt.Color;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ResourceBundle;
import java.util.TreeSet;

/**
 *  This class manages the queue of AutomatonEvents and QueuedAutomatons.
 *
 * @author     Henrik Bäärnielm, Karl Hasselström, Henrik Hägerström
 * @created    April 23, 2001
 */
public class QManager implements Runnable {

    /**
     *  Description of the Field
     */
    AutomatonMap aMap;

    /**
     *  Description of the Field
     */
    private TreeSet automatonQueue;
    /**
     *  Description of the Field
     */
    private HashMap timeToAutomatonMap;
    /**
     *  Description of the Field
     */
    private LinkedList eventQueue;


    /**
     *  Constructor for the QManager object
     *
     * @param  aMap  A reference to the AutomatonMap
     */
    public QManager(AutomatonMap aMap) {
        this.aMap = aMap;
        automatonQueue = new TreeSet();
        eventQueue = new LinkedList();
        timeToAutomatonMap = new HashMap();
    }


    /**
     *  Gets the queue of automatons
     *
     * @return    The queue
     */
    public TreeSet getAutomatonQueue() {
        return automatonQueue;
    }


    /**
     *  Gets the queue of AutomatonEvents
     *
     * @return    The queue
     */
    public LinkedList getAutomatonEventQueue() {
        return eventQueue;
    }


    /**
     *  Gets the current system time.
     *
     * @return    The current system time.
     */
    public long getCurrentTime() {
        return System.currentTimeMillis();
    }


    /**
     *  Adds an event to the event queue
     *
     * @param  event  The event to be added.
     * @param  x      The x coordinate of the position of the event.
     * @param  y      The y coordinate of the position of the event.
     */
    public void addEventToQueue(AutomatonEvent event, int x, int y) {
        log("Lagger till event...");
        if (!aMap.areValidCoordinates(x, y)) {
            throw new IllegalArgumentException("Invalid coordinates...");
        }
        QueuedEvent qE = new QueuedEvent(event, x, y);
        synchronized (eventQueue) {
            eventQueue.addLast(qE);
        }
        synchronized (automatonQueue) {
            automatonQueue.notifyAll();
        }
    }


    /**
     *  Main processing method for the QManager object
     */
    public void run() {

        /*
         * for (int x = 0; x < aMap.getXBound(); x++) {
         * for (int y = 0; y < aMap.getYBound(); y++) {
         * addUpdateToQueue(x, y, 100);
         * }
         * }
         */

        while (true) {
            QueuedEvent qE = null;
            QueuedAutomaton qA = null;
            long nextExecutionTime = 0;
            synchronized (eventQueue) {
                if (eventQueue.size() > 0) {
                    qE = (QueuedEvent) eventQueue.removeFirst();
                }
            }
            log("In QManager:Run");
            if (qE != null) {
                log("QManager:Run, process event");
                // Processa event...
                int x = qE.getX();
                int y = qE.getY();
                Automaton aut = aMap.getAutomaton(x, y);
                int oldState = aut.getState();
                AutomatonReturn autRet = aut.handleEvent(qE.getAutomatonEvent());
                if (aut.hasChanged()) {
                    // Notify
                    Color c = aut.getCurrentColor();
                    ReferenceHolder.getVisibilityManager().updateAutomatonState(x, y, c);
                }
                checkAutomatonReturn(autRet, x, y);
            } else {
                // Fanns inget element i eventkön, kolla om det är dags att uppdatera
                // en automat...
                log("QManager:Run, eventqueue empty, update automaton");
                synchronized (automatonQueue) {
                    long curTime = getCurrentTime();
                    if (automatonQueue.size() > 0) {
                        qA = (QueuedAutomaton) automatonQueue.first();
                        // Remove key in HashMap...
                        synchronized (timeToAutomatonMap) {
                            Integer mapKey = new Integer(qA.getX() * aMap.getYBound() + qA.getY());
                            timeToAutomatonMap.remove(mapKey);
                        }
                        if (qA.getTimeToExecute() <= curTime) {
                            automatonQueue.remove(qA);
                        } else {
                            nextExecutionTime = qA.getTimeToExecute();
                            qA = null;
                        }
                    }
                }
                if (qA != null) {
                    log("QManager:Run, updating automaton");
                    Automaton aut = aMap.getAutomaton(qA.getX(), qA.getY());
                    int oldState = aut.getState();
                    AutomatonReturn autRet = aut.update(aMap.getNeighbourStates(qA.getX(), qA.getY()));
                    if (aut.hasChanged()) {
                        // TODO Notify
                        Color c = aut.getCurrentColor();
                        ReferenceHolder.getVisibilityManager().updateAutomatonState(qA.getX(), qA.getY(), c);
                        // ServerTest.updatePixel(qA.getX(), qA.getY(), c);
                    }
                    checkAutomatonReturn(autRet, qA.getX(), qA.getY());
                }
            }

            // Yield to other threads if we've "done something"...
            if (qE != null || qA != null) {
                Thread.yield();
            } else {
                // Wait for notifications coming from the automaton queue,
                // or 'til it's time to execute next scheduled automaton update...
                long timeToSleep = nextExecutionTime - getCurrentTime();
                if (timeToSleep <= 0) {
                    timeToSleep = 1;
                }
                synchronized (automatonQueue) {
                    try {
                        if (automatonQueue.size() == 0) {
                            timeToSleep = 0;
                        }
                        log("Tråden borjar sova i " + timeToSleep + " ms..., ko-storlek=" + automatonQueue.size());
                        automatonQueue.wait(timeToSleep);
                    } catch (InterruptedException e) {
                        // Att vi får ett InterruptedException spelar ingen större roll...
                    }
                }
            }
        }
    }


    /**
     *  Adds an automaton to the automaton queue.
     *
     * @param  x              The x coordinate of the automaton.
     * @param  y              The y coordinate of the automaton.
     * @param  msToExecution  The time until the automaton should be updated.
     */
    private void addUpdateToQueue(int x, int y, long msToExecution) {
        Integer mapKey = new Integer(x * aMap.getYBound() + y);
        long timeToExecute = getCurrentTime() + msToExecution;
        synchronized (automatonQueue) {
            QueuedAutomaton qA = (QueuedAutomaton) timeToAutomatonMap.get(mapKey);
            if (qA != null) {
                if (qA.getTimeToExecute() >= timeToExecute) {
                    automatonQueue.remove(qA);
                    qA.setTimeToExecute(timeToExecute);
                    automatonQueue.add(qA);
                }
            } else {
                qA = new QueuedAutomaton(x, y, timeToExecute);
                automatonQueue.add(qA);
                timeToAutomatonMap.put(mapKey, qA);
            }
            automatonQueue.notifyAll();
        }
    }


    /**
     *  Checks the return from an automaton update method, and possibly inserts
     *  the automaton and its neighbours into the queue.
     *
     * @param  autRet  The return object from the update method.
     * @param  x       The x coordinate of the automaton that was updated.
     * @param  y       The y coordinate of the automaton that was updated.
     */
    private void checkAutomatonReturn(AutomatonReturn autRet, int x, int y) {
        UnitEvent event = autRet.getUnitEvent();
        if (event != null) {
            ReferenceHolder.getActionProcessor().handleAutomatonUnitEvent(x, y, event);
        }
        if (autRet instanceof AutomatonReturnNoAction) {
            // Nothing...
            return;
        }
        if (autRet instanceof AutomatonReturnQueueSelective) {
            long[][] times = ((AutomatonReturnQueueSelective) autRet).getTimeToExecutions();
            for (int ix = 0; ix < 3; ix++) {
                for (int iy = 0; iy < 3; iy++) {
                    if (times[ix][iy] > 0) {
                        // Queue this automaton
                        int xpos = x + ix - 1;
                        int ypos = y + iy - 1;
                        addUpdateToQueue(x, y, times[ix][iy]);
                    }
                }
            }
            return;
        }
        if (autRet instanceof AutomatonReturnQueueSelf ||
                autRet instanceof AutomatonReturnQueueSelfAndNeighbours) {
            addUpdateToQueue(x, y, autRet.getSelfTimeToExecution());
        }

        if (autRet instanceof AutomatonReturnQueueNeighbours ||
                autRet instanceof AutomatonReturnQueueSelfAndNeighbours) {
            int xFrom = (x - 1) >= 0 ? x - 1 : 0;
            int yFrom = (y - 1) >= 0 ? y - 1 : 0;
            int xTo = (x + 1) < aMap.getXBound() ? x + 1 : aMap.getXBound() - 1;
            int yTo = (y + 1) < aMap.getYBound() ? y + 1 : aMap.getYBound() - 1;
            for (int curX = xFrom; curX <= xTo; curX++) {
                for (int curY = yFrom; curY <= yTo; curY++) {
                    if (curX != x || curY != y) {
                        addUpdateToQueue(curX, curY, autRet.getNeighboursTimeToExecution());
                    }
                }
            }
        }
    }


    /**
     *  Prints to the system log.
     *
     * @param  s  The message to print.
     */
    public static void log(String s) {
        //	 System.out.println(s);
    }


    /**
     *  Outputs to stdout.
     *
     * @param  s  The message to output.
     */
    public static void debug(String s) {
        System.out.println(s);
    }

}
