/**
 *  Copyright (C) 2002-2012   The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol 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.
 *
 *  FreeCol 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 FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */


package net.sf.freecol.metaserver;


import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Logger;

import net.sf.freecol.FreeCol;
import net.sf.freecol.common.networking.Connection;
import net.sf.freecol.common.networking.DOMMessage;

import org.w3c.dom.Element;



/**
* The <code>MetaRegister</code> stores information about running servers.
* Each server has it's own {@link MetaItem} object.
*/
public final class MetaRegister {
    private static Logger logger = Logger.getLogger(MetaRegister.class.getName());

    
    private ArrayList<MetaItem> items = new ArrayList<MetaItem>();
    
    
    /**
    * Gets the server entry with the diven address and port.
    *
    * @param address The IP-address of the server.
    * @param port The port number of the server.
    * @return The server entry or <code>null</code> if the given 
    *         entry could not be found.
    */
    private MetaItem getItem(String address, int port) {
        int index = indexOf(address, port);
        if (index >= 0) {
            return items.get(index);
        } else {
            return null;
        }
    }
    

    /**
    * Gets the index of the server entry with the diven address and port.
    *
    * @param address The IP-address of the server.
    * @param port The port number of the server.
    * @return The index or <code>-1</code> if the given entry could not be found.
    */
    private int indexOf(String address, int port) {
        for (int i=0; i<items.size(); i++) {
            if (address.equals(items.get(i).getAddress()) && port == items.get(i).getPort()) {
                return i;
            }
        }

        return -1;
    }


    /**
    * Removes servers that have not sent an update for some time.
    */
    public synchronized void removeDeadServers() {
        logger.info("Removing dead servers.");

        long time = System.currentTimeMillis() - MetaServer.REMOVE_OLDER_THAN;
        for (int i=0; i<items.size(); i++) {
            if (items.get(i).getLastUpdated() < time) {
                logger.info("Removing: " + items.get(i));
                items.remove(i);
            }
        }
    }


    /**
    * Adds a new server with the given attributes.
    *
    * @param name The name of the server.
    * @param address The IP-address of the server.
    * @param port The port number in which clients may connect.
    * @param slotsAvailable Number of players that may conncet.
    * @param currentlyPlaying Number of players that are currently connected.
    * @param isGameStarted <i>true</i> if the game has started.
    * @param version The version of the server.
    * @param gameState The current state of the game.
    */
    public synchronized void addServer(String name, String address, int port, int slotsAvailable,
                int currentlyPlaying, boolean isGameStarted, String version, int gameState)
                throws IOException {
        MetaItem mi = getItem(address, port);
        if (mi == null) {
            // Check connection before adding the server:
            Connection mc = null;
            try {                
                mc = new Connection(address, port, null, FreeCol.METASERVER_THREAD);
                Element element = DOMMessage.createNewRootElement("disconnect");
                mc.send(element);
            } catch (IOException e) {
                logger.info("Server rejected (no route to destination):" + address + ":" + port);
                throw e;
            } finally {
                try {
                    if (mc != null) {
                        mc.close();
                    }
                } catch (IOException e) {}
            }
            items.add(new MetaItem(name, address, port, slotsAvailable, currentlyPlaying, isGameStarted, version, gameState));
            logger.info("Server added:" + address + ":" + port);
        } else {
            updateServer(mi, name, address, port, slotsAvailable, currentlyPlaying, isGameStarted, version, gameState);
        }
    }


    /**
    * Updates a server with the given attributes.
    *
    * @param name The name of the server.
    * @param address The IP-address of the server.
    * @param port The port number in which clients may connect.
    * @param slotsAvailable Number of players that may conncet.
    * @param currentlyPlaying Number of players that are currently connected.
    * @param isGameStarted <i>true</i> if the game has started.
    * @param version The version of the server.
    * @param gameState The current state of the game.
    */
    public synchronized void updateServer(String name, String address, int port, int slotsAvailable,
            int currentlyPlaying, boolean isGameStarted, String version, int gameState)
            throws IOException {
        MetaItem mi = getItem(address, port);
        if (mi == null) {
            addServer(name, address, port, slotsAvailable, currentlyPlaying, isGameStarted, version, gameState);
        } else {
            updateServer(mi, name, address, port, slotsAvailable, currentlyPlaying, isGameStarted, version, gameState);
        }
    }


    /**
    * Removes a server from the register.
    * @param address The IP-address of the server to remove.
    * @param port The port number of the server to remove.
    */
    public synchronized void removeServer(String address, int port) {
        int index = indexOf(address, port);
        if (index >= 0) {
            items.remove(index);
            logger.info("Removing server:" + address + ":" + port);
        } else {
            logger.info("Trying to remove non-existing server:" + address + ":" + port);
        }
    }

    
    /**
    * Creates a server list.
    * @return The server list as an XML DOM Element.
    */
    public synchronized Element createServerList() {
        Element element = DOMMessage.createNewRootElement("serverList");
        for (int i=0; i<items.size(); i++) {
            element.appendChild(items.get(i).toXMLElement(element.getOwnerDocument()));
        }
        return element;
    }


    /**
    * Updates a given <code>MetaItem</code>.
    *
    * @param mi The <code>MetaItem</code> that should be updated.
    * @param name The name of the server.
    * @param address The IP-address of the server.
    * @param port The port number in which clients may connect.
    * @param slotsAvailable Number of players that may conncet.
    * @param currentlyPlaying Number of players that are currently connected.
    * @param isGameStarted <i>true</i> if the game has started.
    * @param version The version of the server.
    * @param gameState The current state of the game: {@link
    *       net.sf.freecol.server.FreeColServer.GameState#STARTING_GAME},
    *       {@link
    *       net.sf.freecol.server.FreeColServer.GameState#IN_GAME} or
    *       {@link
    *       net.sf.freecol.server.FreeColServer.GameState#ENDING_GAME}.
    */
    private void updateServer(MetaItem mi, String name, String address, int port, int slotsAvailable,
            int currentlyPlaying, boolean isGameStarted, String version, int gameState) {
        mi.update(name, address, port, slotsAvailable, currentlyPlaying, isGameStarted, version, gameState);
        logger.info("Server updated:" + mi.toString());
    }
}

