/**
 *  Copyright (C) 2002-2007  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.client.gui.i18n;

import java.io.File;
import java.io.FileInputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Logger;

import net.sf.freecol.FreeCol;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.Feature;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.FreeColGameObjectType;
import net.sf.freecol.common.model.FreeColObject;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Resource;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileItem;
import net.sf.freecol.common.model.TileItemContainer;
import net.sf.freecol.common.model.Typed;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.Map.CircleIterator;
import net.sf.freecol.common.model.Unit.Role;

/**
 * Represents a collection of messages in a particular locale. <p/>
 *
 * This class is NOT thread-safe. (CO: I cannot find any place that really has a
 * problem) <p/>
 *
 * Messages are put in the file "FreeColMessages.properties". This file is
 * presently located in the same directory as the source file of this class.
 */
public class Messages {

    private static final Logger logger = Logger.getLogger(Messages.class.getName());

    public static final String STRINGS_DIRECTORY = "strings";

    public static final String FILE_PREFIX = "FreeColMessages";

    public static final String FILE_SUFFIX = ".properties";

    private static Properties messageBundle = null;

    /**
     * Set the resource bundle for the given locale
     *
     * @param locale
     */
    public static void setMessageBundle(Locale locale) {
        if (locale == null) {
            throw new NullPointerException("Parameter locale must not be null");
        } else {
            if (!Locale.getDefault().equals(locale)) {
                Locale.setDefault(locale);
            }
            setMessageBundle(locale.getLanguage(), locale.getCountry(), locale.getVariant());
        }
    }

    /**
     * Set the resource bundle to the given locale
     *
     * @param language The language for this locale.
     * @param country The language for this locale.
     * @param variant The variant for this locale.
     */
    private static void setMessageBundle(String language, String country, String variant) {

        messageBundle = new Properties();

        if (!language.equals("")) {
            language = "_" + language;
        }
        if (!country.equals("")) {
            country = "_" + country;
        }
        if (!variant.equals("")) {
            variant = "_" + variant;
        }
        String[] fileNames = { FILE_PREFIX + FILE_SUFFIX, FILE_PREFIX + language + FILE_SUFFIX,
                               FILE_PREFIX + language + country + FILE_SUFFIX,
                               FILE_PREFIX + language + country + variant + FILE_SUFFIX };

        List<File> directories = new LinkedList<File>();
        directories.add(getI18nDirectory());
        for (File dir : FreeCol.getModsDirectory().listFiles()) {
            if (dir.isDirectory()) {
                directories.add(dir);
            }
        }        

        for (File directory : directories) {
            for (String fileName : fileNames) {
                File resourceFile = new File(directory, fileName);
                loadResources(resourceFile);
            }
        }
    }

    /**
     * Returns the directory containing language property files.
     *
     * @return a <code>File</code> value
     */
    public static File getI18nDirectory() {
        return new File(FreeCol.getDataDirectory(), STRINGS_DIRECTORY);
    }


    /**
     * Finds the message with a particular ID in the default locale and performs
     * string replacements.
     *
     * @param messageId The key of the message to find
     * @param data consists of pairs of strings, each time the first of the pair
     *       is replaced by the second in the messages.
     */
    public static String message(String messageId, String... data) {
        // Check that all the values are correct.
        if (messageId == null) {
            throw new NullPointerException();
        }
        if (data!=null && data.length % 2 != 0) {
            throw new IllegalArgumentException("Programming error, the data should consist of only pairs.");
        }
        if (messageBundle == null) {
            setMessageBundle(Locale.getDefault());
        }

        String message = messageBundle.getProperty(messageId);
        if (message == null) {
            return messageId;
        }

        if (data!=null && data.length > 0) {
            for (int i = 0; i < data.length; i += 2) {
                if (data[i] == null || data[i+1] == null) {
                    throw new IllegalArgumentException("Programming error, no data should be <null>.");
                }
                message = message.replace(data[i], data[i+1]);
            }
        }
        return message.trim();
    }

    /**
     * Returns true if the message bundle contains the given key.
     *
     * @param key a <code>String</code> value
     * @return a <code>boolean</code> value
     */
    public static boolean containsKey(String key) {
        if (messageBundle == null) {
            setMessageBundle(Locale.getDefault());
        }
        return (messageBundle.getProperty(key) != null);
    }


    /**
     * Returns the preferred key if it is contained in the message
     * bundle and the default key otherwise. This should be used to
     * select the most specific message key available.
     *
     * @param preferredKey a <code>String</code> value
     * @param defaultKey a <code>String</code> value
     * @return a <code>String</code> value
     */
    public static String getKey(String preferredKey, String defaultKey) {
        if (containsKey(preferredKey)) {
            return preferredKey;
        } else {
            return defaultKey;
        }
    }


    /**
     * Calling this method can be used to replace the messages used currently
     * with a new bundle. This is used only in the debugging of FreeCol.
     *
     * @param resourceFile
     */
    public static void loadResources(File resourceFile) {

        if ((resourceFile != null) && resourceFile.exists() && resourceFile.isFile() && resourceFile.canRead()) {
            try {
                messageBundle.load(new FileInputStream(resourceFile));
            } catch (Exception e) {
                logger.warning("Unable to load resource file " + resourceFile.getPath());
            }
        }
    }


    /**
     * Returns the localized name of a FreeColGameObjectType.
     *
     * @param object a <code>FreeColGameObjectType</code> value
     * @return a <code>String</code> value
     */
    public static final String getName(FreeColGameObjectType object) {
        return message(object.getId() + ".name");
    }

    /**
     * Returns the localized name of a FreeColGameObject implementing
     * the Typed interface.
     *
     * @param object a <code>Typed</code> value
     * @return a <code>String</code> value
     */
    public static final String getName(Typed object) {
        return message(object.getType().getId() + ".name");
    }

    /**
     * Returns the localized name of a Feature.
     *
     * @param object a <code>Feature</code> value
     * @return a <code>String</code> value
     */
    public static final String getName(Feature object) {
        return message(object.getId() + ".name");
    }

    /**
     * Gets the name of a Tile, or shows "unexplored" if not explored
     * by player.
     *
     * @param tile a <code>Tile</code> value
     * @return The name as a <code>String</code>.
     */
    public static String getName(Tile tile) {
        if (tile.isViewShared()) {
            if (tile.isExplored()) {
                return getName(tile.getType());
            } else {
                return message("unexplored");
            }
        } else {
            Player player = tile.getGame().getCurrentPlayer();
            if (player != null) {
                if (tile.getPlayerExploredTile(player) != null
                    && tile.getPlayerExploredTile(player).isExplored()) {
                    return getName(tile.getType());
                }
                return message("unexplored");
            } else {
                logger.warning("player == null");
                return "";
            }
        }
    }


    /**
     * Return the localized type of the given FoundingFather.
     *
     * @param type an <code>int</code> value
     * @return a <code>String</code> value
     */
    public static String getName(FoundingFather.FoundingFatherType type) {
        return message("model.foundingFather." + type.toString().toLowerCase());
    }

    /**
     * Returns the localized description of a FreeColObject.
     *
     * @param object a <code>FreeColObject</code> value
     * @return a <code>String</code> value
     */
    public static final String getDescription(FreeColObject object) {
        return message(object.getId() + ".description");
    }

    /**
     * Returns the name of a unit in a human readable format. The return value
     * can be used when communicating with the user.
     *
     * @param unit an <code>Unit</code> value
     * @return The given unit type as a String
     */
    public static String getLabel(Unit unit) {
        String completeName = "";
        String customName = "";
        String name = unit.getName();
        if (name != null) {
            customName = " " + name;
        }

        //Gets the type of the unit to be displayed with custom name
        if (unit.canCarryTreasure()) {
            completeName = message(unit.getType().getId() + ".gold", "%gold%",
                                   String.valueOf(unit.getTreasureAmount()));
        } else if ((unit.getEquipment() == null || unit.getEquipment().isEmpty()) &&
                   unit.getType().getDefaultEquipmentType() != null) {
            completeName = getLabel(unit.getType(), unit.getRole()) + " (" +
                message(unit.getType().getDefaultEquipmentType().getId() + ".none") + ")";
        } else {
            completeName = Messages.getLabel(unit.getType(), unit.getRole());
        }

        // FIX THIS URGENTLY: it fails for all localizations that do not use ()
        // Adds the custom name to the type of the unit
        int index = completeName.lastIndexOf(" (");
        if (index < 0) {
            completeName = completeName + customName;
        } else {
            completeName = completeName.substring(0, index) + customName +
                completeName.substring(index);
        }

        return completeName;
    }

    /**
     * Returns the name of a unit in a human readable format. The return value
     * can be used when communicating with the user.
     *
     * @param someType an <code>UnitType</code> value
     * @param someRole a <code>Role</code> value
     * @return The given unit type as a String
     */
    public static String getLabel(UnitType someType, Role someRole) {
        String key = someRole.toString().toLowerCase();
        if (someRole == Role.DEFAULT) {
            key = "name";
        }
        String messageID = someType.getId() +  "." + key;
        if (containsKey(messageID)) {
            return message(messageID);
        } else {
            return message("model.unit." + key + ".name", "%unit%",
                           getName(someType));
        }
    }

    /**
     * Returns a description of the <code>Tile</code>, with the name of the tile
     * and any improvements on it (road/plow/etc) from <code>TileItemContainer</code>.
     *
     * @param tile a <code>Tile</code> value
     * @return The description label for this tile
     */
    public static String getLabel(Tile tile) {
        if (tile.getTileItemContainer() == null) {
            return getName(tile);
        } else {
            return getName(tile) + getLabel(tile.getTileItemContainer());
        }
    }

    /**
     * Returns a description of the tile, with the name of the tile
     * and any improvements made to it (road/plow).
     *
     * @param colonyTile a <code>ColonyTile</code> value
     * @return The description label for this tile
     */
    public static String getLabel(ColonyTile colonyTile) {
        return getLabel(colonyTile.getWorkTile());
    }

    /**
     * Returns a description of the goods.
     *
     * @param goods a <code>Goods</code> value
     * @return The description label for this tile
     */
    public static String getLabel(Goods goods) {
        return getLabel(goods.getType(), goods.getAmount());
    }

    /**
     * Returns a description of the goods.
     *
     * @param type a <code>GoodsType</code> value
     * @param amount an <code>int</code> value
     * @return The description label for this tile
     */
    public static String getLabel(GoodsType type, int amount) {
        return message("model.goods.goodsAmount",
                       "%goods%", getName(type),
                       "%amount%", Integer.toString(amount));
    }

    /**
     * Returns a description of the tile, with the name of the tile
     * and any improvements made to it (road/plow)
     *
     * @param tic a <code>TileItemContainer</code> value
     * @return The description label for this tile
     */
    public static String getLabel(TileItemContainer tic) {
        String label = new String();
        for (TileItem item : tic.getTileItems()) {
            if (item instanceof Resource) {
                label += "/" + getName((Resource) item);
            } else if (item instanceof TileImprovement
                       && ((TileImprovement) item).isComplete()) {
                label += "/" + getName((TileImprovement) item);
            }
        }
        return label;
    }

    /**
     * Returns the nation of the given player as a String.
     *
     * @param player a <code>Player</code> value
     * @return The nation of this player as a String.
     */
    public static String getNationAsString(Player player) {
    	if(player.getNationID().equals(Nation.UNKNOWN_NATION_ID)){
    		return message("model.nation.unknownEnemy.name");
    	}

        return (player.getPlayerType() == Player.PlayerType.REBEL
                || player.getPlayerType() == Player.PlayerType.INDEPENDENT)
            ? player.getIndependentNationName()
            : getName(player.getNation());
    }

    /**
     * Location names
     */

    /**
     * Returns the location name of the given Tile.
     *
     * @return The name of this location.
     */
    public static String getLocationName(Tile tile) {
        Settlement settlement = tile.getSettlement();
        if (settlement == null) {
            String name = getName(tile);
            int radius = 8; // more than 8 tiles away is no longer "near"
            CircleIterator mapIterator = tile.getMap().getCircleIterator(tile.getPosition(), true, radius);
            while (mapIterator.hasNext()) {
                settlement = tile.getMap().getTile(mapIterator.nextPosition()).getSettlement();
                if (settlement != null) {
                    return name + " ("
                        + Messages.message("nearLocation", "%location%",
                                           settlement.getName()) + ")";
                }
            }
            if (tile.getRegion() != null && tile.getRegion().getName() != null) {
                return name + " (" + tile.getRegion().getName() + ")";
            } else {
                return name;
            }
        } else {
            return settlement.getName();
        }
    }

    /**
     * Returns the location name of the given Building.
     *
     * @param building a <code>Building</code> value
     * @return The name of this location.
     */
    public static String getLocationName(Building building) {
        return message("inLocation", "%location%", getName(building));
    }

    /**
     * Returns the (non-unique) name of the given <code>ColonyTile</code>.
     *
     * @param colonyTile a <code>ColonyTile</code> value
     * @return The name of this ColonyTile.
     */
    public static String getLocationName(ColonyTile colonyTile) {
        String name = colonyTile.getColony().getName();
        if (colonyTile.isColonyCenterTile()) {
            return name;
        } else {
            return message("nearLocation", "%location%", name);
        }
    }

    /**
     * Returns a name for this unit, as a location.
     *
     * @return A name for this unit, as a location.
     */
    public static String getLocationName(Unit unit) {
        return message("onBoard", "%unit%", getLabel(unit));
    }

    /**
     * Describe <code>getLocationName</code> method here.
     *
     * @param location a <code>Location</code> value
     * @return a <code>String</code> value
     */
    public static String getLocationName(Location location) {
        if (location instanceof Settlement) {
            return ((Settlement) location).getName();
        } else if (location instanceof Europe) {
            return ((Europe) location).getName();
        } else if (location instanceof Tile) {
            return getLocationName((Tile) location);
        } else if (location instanceof Unit) {
            return getLocationName((Unit) location);
        } else if (location instanceof ColonyTile) {
            return getLocationName((ColonyTile) location);
        } else if (location instanceof Building) {
            return getLocationName((Building) location);
        } else {
            return location.toString();
        }
    }

}
