/**
 *  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.client.gui;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;

import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.Canvas.BoycottAction;
import net.sf.freecol.client.gui.Canvas.BuyAction;
import net.sf.freecol.client.gui.Canvas.ClaimAction;
import net.sf.freecol.client.gui.Canvas.EventType;
import net.sf.freecol.client.gui.Canvas.MissionaryAction;
import net.sf.freecol.client.gui.Canvas.ScoutColonyAction;
import net.sf.freecol.client.gui.Canvas.ScoutIndianSettlementAction;
import net.sf.freecol.client.gui.Canvas.SellAction;
import net.sf.freecol.client.gui.Canvas.TradeAction;
import net.sf.freecol.client.gui.animation.Animations;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.client.gui.menu.FreeColMenuBar;
import net.sf.freecol.client.gui.menu.InGameMenuBar;
import net.sf.freecol.client.gui.menu.MapEditorMenuBar;
import net.sf.freecol.client.gui.panel.ChoiceItem;
import net.sf.freecol.client.gui.panel.ColonyPanel;
import net.sf.freecol.client.gui.panel.CornerMapControls;
import net.sf.freecol.client.gui.panel.EuropePanel;
import net.sf.freecol.client.gui.panel.MiniMap;
import net.sf.freecol.client.gui.panel.LabourData.UnitData;
import net.sf.freecol.client.gui.panel.LoadingSavegameDialog;
import net.sf.freecol.client.gui.panel.MapControls;
import net.sf.freecol.client.gui.panel.Parameters;
import net.sf.freecol.client.gui.sound.SoundPlayer;
import net.sf.freecol.common.ServerInfo;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.FreeColObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.IndianSettlement;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.ModelMessage;
import net.sf.freecol.common.model.Monarch.MonarchAction;
import net.sf.freecol.common.model.PathNode;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TradeRoute;
import net.sf.freecol.common.model.TypeCountMap;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.option.AudioMixerOption;
import net.sf.freecol.common.option.BooleanOption;
import net.sf.freecol.common.option.LanguageOption;
import net.sf.freecol.common.option.LanguageOption.Language;
import net.sf.freecol.common.option.Option;
import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.option.PercentageOption;
import net.sf.freecol.common.resources.ResourceManager;

public class GUI {


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

    public static final String COPYRIGHT = "Copyright (C) 2002-2012   The FreeCol Team";

    public static final String LICENSE = "http://www.gnu.org/licenses/gpl.html";

    public static final String REVISION = "$Revision: 10427 $";


    /**
     * The space not being used in windowed mode.
     */
    private static final int DEFAULT_WINDOW_SPACE = 50;



    public static final int MOVE_UNITS_MODE = 0;

    public static final int VIEW_TERRAIN_MODE = 1;


    private FreeColClient freeColClient;


    private GraphicsDevice gd;

    private FreeColFrame frame;

    private Canvas canvas;

    private MapViewer mapViewer;

    private MapControls mapControls;

    /**
     * This is the MapViewer instance used to paint the colony tiles in the
     * ColonyPanel and other panels.  It should not be scaled along
     * with the default MapViewer.
     */
    private MapViewer colonyTileGUI;
 
    private ImageLibrary imageLibrary;

    private SoundPlayer soundPlayer;

    private boolean windowed;

    private Rectangle windowBounds;

    private JWindow splash;

    public GUI(FreeColClient freeColClient) {
        this.freeColClient = freeColClient;
        this.imageLibrary = new ImageLibrary();
    }

    public void activateGotoPath() {
        Unit unit = getActiveUnit();

        // Action should be disabled if there is no active unit, but make sure
        if (unit == null)
            return;

        // Enter "goto mode" if not already activated; otherwise cancel it
        if (mapViewer.isGotoStarted()) {
            mapViewer.stopGoto();
        } else {
            mapViewer.startGoto();

            // Draw the path to the current mouse position, if the
            // mouse is over the screen; see also
            // CanvaseMouseMotionListener
            Point pt = canvas.getMousePosition();
            if (pt != null) {
                Tile tile = mapViewer.convertToMapTile(pt.x, pt.y);
                if (tile != null && unit.getTile() != tile) {
                    mapViewer.setGotoPath(unit.findPath(tile));
                }
            }
        }

    }

    /**
     * Verifies if the client can play sounds.
     * @return boolean <b>true</b> if and only if client sound player has an instance
     */
    public boolean canPlaySound() {
        return soundPlayer != null;
    }

    public void centerActiveUnit() {
        mapViewer.centerActiveUnit();
    }

    /**
     * Change the windowed mode.
     * @param windowed Use <code>true</code> for windowed mode
     *      and <code>false</code> for fullscreen mode.
     */
    public void changeWindowedMode(boolean windowed) {
        JMenuBar menuBar = null;
        if (frame != null) {
            menuBar = frame.getJMenuBar();
            if (frame instanceof WindowedFrame) {
                this.windowBounds = frame.getBounds();
            }
            frame.setVisible(false);
            frame.dispose();
        }
        setWindowed(windowed);

        this.frame = FreeColFrame.createFreeColFrame(freeColClient, canvas, gd, windowed);

        frame.setJMenuBar(menuBar);
        frame.setCanvas(canvas);
        frame.updateBounds(getWindowBounds());

        mapViewer.forceReposition();
        canvas.updateSizes();
        frame.setVisible(true);
    }

    public void closeMainPanel() {
        canvas.closeMainPanel();
    }

    public void closeMenus() {
        canvas.closeMenus();
    }

    public void closeStatusPanel() {
        canvas.closeStatusPanel();
    }

    public boolean containsInGameComponents() {
        return canvas.containsInGameComponents();
    }

    public Dimension determineWindowSize() {
        Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getMaximumWindowBounds();
        Dimension size = new Dimension(bounds.width - DEFAULT_WINDOW_SPACE,
                                       bounds.height - DEFAULT_WINDOW_SPACE);
        return size;
    }

    public void displayChat(String senderNme, String message, boolean privateChat) {
        canvas.displayChat(senderNme, message, privateChat);

    }


    /**
     * Tells the map controls that a chat message was received.
     *
     * @param player The player who sent the chat message.
     * @param message The chat message.
     * @param privateChat 'true' if the message is a private one, 'false'
     *            otherwise.
     * @see GUIMessage
     */
    public void displayChatMessage(Player player, String message,
                                   boolean privateChat) {
        mapViewer.addMessage(new GUIMessage(player.getName() + ": " + message,
                imageLibrary.getColor(player)));

        canvas.repaint(0, 0, canvas.getWidth(), canvas.getHeight());
    }

    public void displayColonyTile(Graphics2D g, Tile tile, Colony colony) {
        colonyTileGUI.displayColonyTile(g, tile, colony);
    }

    public void displaySplashScreen(final String splashFilename) {
        splash = null;
        if (splashFilename != null) {
            try {
                Image im = Toolkit.getDefaultToolkit()
                    .getImage(splashFilename);
                splash = new JWindow();
                splash.getContentPane().add(new JLabel(new ImageIcon(im)));
                splash.pack();
                Point center = GraphicsEnvironment
                    .getLocalGraphicsEnvironment().getCenterPoint();
                splash.setLocation(center.x - splash.getWidth() / 2,
                                   center.y - splash.getHeight() / 2);
                splash.setVisible(true);
            } catch (Exception e) {
                logger.log(Level.WARNING, "Splash fail", e);
                splash = null;
            }
        }
    }

    public void errorMessage(String messageId) {
        canvas.errorMessage(messageId);
    }

    public void errorMessage(String messageID, String message) {
        canvas.errorMessage(messageID, message);

    }

    public void executeWithUnitOutForAnimation(final Unit unit,
            final Tile sourceTile,
            final OutForAnimationCallback r) {
        mapViewer.executeWithUnitOutForAnimation(unit, sourceTile, r);
    }

    public Unit getActiveUnit() {
        if (mapViewer == null)
            return null;
        return mapViewer.getActiveUnit();
    }

    public Canvas getCanvas() {
        return canvas;
    }

    public MapViewer getColonyTileGUI() {
        return colonyTileGUI;
    }

    public int getCurrentViewMode() {
        return mapViewer.getView();
    }

    public Tile getFocus() {
        if (mapViewer == null)
            return null;
        return mapViewer.getFocus();
    }


    public ImageIcon getImageIcon(Object display, boolean small) {
        return imageLibrary.getImageIcon(display, small);
    }

    public ImageLibrary getImageLibrary() {
        return imageLibrary;
    }

    public LoadingSavegameDialog getLoadingSavegameDialog() {
        return canvas.getLoadingSavegameDialog();
    }


    public float getMapScale() {
        return mapViewer.getMapScale();
    }

    public MapViewer getMapViewer() {
        return mapViewer;
    }

    public Tile getSelectedTile() {
        return mapViewer.getSelectedTile();
    }

    public SoundPlayer getSoundPlayer() {
        return soundPlayer;
    }

    public Rectangle getTileBounds(Tile tile) {
        return mapViewer.getTileBounds(tile);
    }

    public Point getTilePosition(Tile tile) {
        return mapViewer.getTilePosition(tile);
    }

    public Rectangle getWindowBounds() {
        return windowBounds;
    }

    public void hideSplashScreen() {
        if (splash != null) {
            splash.setVisible(false);
            splash.dispose();
        }
    }

    public boolean isClientOptionsDialogShowing() {
        return canvas != null && !canvas.isClientOptionsDialogShowing();
    }


    public boolean isMapboardActionsEnabled() {
        return canvas != null && canvas.isMapboardActionsEnabled();
    }

    public boolean isShowingSubPanel() {
        return canvas.isShowingSubPanel();
    }

    public boolean isWindowed() {
        return windowed;
    }


    public boolean onScreen(Tile tileToCheck) {
        return mapViewer.onScreen(tileToCheck);
    }

    public void paintImmediatelyCanvasIn(Rectangle rectangle) {
        canvas.paintImmediately(rectangle);
    }

    public void paintImmediatelyCanvasInItsBounds() {
        canvas.paintImmediately(canvas.getBounds());
    }


    /**
     * Plays some sound. Parameter == null stops playing a sound.
     *
     * @param sound The sound resource to play or <b>null</b>
     */
    public void playSound(String sound) {
        if (canPlaySound()) {
            if (sound == null) {
               soundPlayer.stop();
            } else {
               File file = ResourceManager.getAudio(sound);
               if (file != null) {
                   soundPlayer.playOnce(file);
               }
               logger.finest(((file == null) ? "Could not load" : "Playing")
                             + " sound: " + sound);
            }
        }
    }


    public void quit() throws Exception {
        if (!isWindowed()) {
                gd.setFullScreenWindow(null);
        }
    }

    public void refresh() {
        mapViewer.forceReposition();
        canvas.refresh();
    }

    public void refreshPlayersTable() {
        canvas.refreshPlayersTable();
    }

    /**
     * Refreshes the screen at the specified Tile.
     *
     * @param t The tile to refresh.
     */
    public void refreshTile(Tile t) {
        if (t.getX() >= 0 && t.getY() >= 0) {
            canvas.repaint(mapViewer.getTileBounds(t));
        }
    }

    public void removeFromCanvas(Component component) {
        canvas.remove(component);
    }

    public void removeInGameComponents() {
        canvas.removeInGameComponents();
    }

    public void requestFocusForSubPanel() {
        canvas.getShowingSubPanel().requestFocus();
    }

    public boolean requestFocusInWindow() {
        return canvas.requestFocusInWindow();
    }

    public void resetMenuBar() {
        JMenuBar menuBar = frame.getJMenuBar();
        if (menuBar != null) {
            ((FreeColMenuBar) menuBar).reset();
        }
    }

    public void returnToTitle() {
        canvas.returnToTitle();
    }

    public void scaleMap(float delta) {
        mapViewer.scaleMap(delta);
        refresh();
    }

    public void setActiveUnit(Unit unitToActivate) {
        mapViewer.setActiveUnit(unitToActivate);
        if (unitToActivate != null 
            && !freeColClient.getMyPlayer().owns(unitToActivate)) {
            canvas.repaint(0, 0, canvas.getWidth(), canvas.getHeight());
        }
    }

    public void setFocus(Tile tileToFocus) {
        mapViewer.setFocus(tileToFocus);
    }

    public void setFocusImmediately(Tile tileToFocus) {
        mapViewer.setFocusImmediately(tileToFocus);
    }

    public boolean setSelectedTile(Tile newTileToSelect, boolean clearGoToOrders) {
        return mapViewer.setSelectedTile(newTileToSelect, clearGoToOrders);
    }

    public void setupInGameMenuBar() {
        frame.setJMenuBar(new InGameMenuBar(freeColClient, this));
        frame.paintAll(canvas.getGraphics());
    }

    public void setupMenuBarToNull() {
        frame.setJMenuBar(null);
    }

    public void setUpMouseListenersForCanvas(){
        canvas.addMouseListener(new CanvasMouseListener(freeColClient, canvas, mapViewer));
        canvas.addMouseMotionListener(new CanvasMouseMotionListener(freeColClient, mapViewer));
    }

    public void setWindowed(boolean windowed) {
        this.windowed = windowed;

    }

    public void showAboutPanel() {
        canvas.showAboutPanel();
    }

    public ScoutIndianSettlementAction showArmedUnitIndianSettlementDialog(IndianSettlement settlement) {
        return canvas.showArmedUnitIndianSettlementDialog(settlement);
    }

    public BoycottAction showBoycottedGoodsDialog(Goods goods, Europe europe) {
        return canvas.showBoycottedGoodsDialog(goods, europe);
    }

    public void showBuildQueuePanel(Colony colony) {
        canvas.showBuildQueuePanel(colony);
    }

    public void showBuildQueuePanel(Colony colony, Runnable callBack) {
        canvas.showBuildQueuePanel(colony, callBack);
    }

    public BuyAction showBuyDialog(Unit unit, Settlement settlement,
            Goods goods, int gold, boolean canBuy) {
        return canvas.showBuyDialog(unit, settlement, goods, gold, canBuy);
    }

    public List<Goods> showCaptureGoodsDialog(Unit winner, List<Goods> loot) {
        return canvas.showCaptureGoodsDialog(winner, loot);
    }

    public void showChatPanel() {
        canvas.showChatPanel();
    }

    public <T> T showChoiceDialog(Tile tile, String text, String cancelText,
            List<ChoiceItem<T>> choices) {
        return canvas.showChoiceDialog(tile, text, cancelText, choices);
    }

    public MonarchAction showChoiceMonarchActionDialog(String monarchTitle, List<ChoiceItem<MonarchAction>> actions) {
        return canvas.showChoiceMonarchActionDialog(monarchTitle, actions);
    }

    public FoundingFather showChooseFoundingFatherDialog(List<ChoiceItem<FoundingFather>> fathers, String fatherTitle) {
        return canvas.showChooseFoundingFatherDialog(fathers, fatherTitle);
    }

    public FoundingFather showChooseFoundingFatherDialog(List<FoundingFather> ffs) {
        return canvas.showChooseFoundingFatherDialog(ffs);
    }

    public ClaimAction showClaimDialog(Tile tile, Player player, int price,
            Player owner, boolean canAccept) {
        return canvas.showClaimDialog(tile, player, price, owner, canAccept);
    }

    public OptionGroup showClientOptionsDialog() {
        return canvas.showClientOptionsDialog();
    }

    public ColonyPanel showColonyPanel(Colony colony) {
        return canvas.showColonyPanel(colony);
    }

    public void showColonyPanel(Colony colony, Runnable callback) {
        canvas.showColonyPanel(colony, callback);
    }

    public void showColopediaPanel(String nodeId) {
        canvas.showColopediaPanel(nodeId);
    }

    public void showCompactLabourReport() {
        canvas.showCompactLabourReport();
    }

    public void showCompactLabourReport(UnitData unitData) {
        canvas.showCompactLabourReport(unitData);
    }

    public List<String> showConfirmDeclarationDialog() {
        return canvas.showConfirmDeclarationDialog();
    }

    public boolean showConfirmDialog(String text, String okText, String cancelText) {
        return canvas.showConfirmDialog(text, okText, cancelText);
    }

    public boolean showConfirmDialog(Tile tile, ModelMessage[] messages,
            String okText, String cancelText) {
        return canvas.showConfirmDialog(tile, messages, okText, cancelText);
    }

    public boolean showConfirmDialog(Tile tile, StringTemplate text,
            String okText, String cancelText) {
        return canvas.showConfirmDialog(tile, text, okText, cancelText);
    }

    public void showDeclarationDialog() {
        canvas.showDeclarationDialog();
    }

    public void showDifficultyDialog(boolean editable) {
        canvas.showDifficultyDialog(editable);
    }

    public OptionGroup showDifficultyDialog(Specification specification) {
        return canvas.showDifficultyDialog(specification);
    }

    public List<Goods> showDumpCargoDialog(Unit unit) {
        return canvas.showDumpCargoDialog(unit);
    }


    public boolean showEditOptionDialog(Option option) {
        return canvas.showEditOptionDialog(option);
    }

    public int showEmigrationPanel(boolean fountainOfYouth) {
        return canvas.showEmigrationPanel(fountainOfYouth);
    }

    public boolean showEndTurnDialog(List<Unit> units) {
        return canvas.showEndTurnDialog(units);
    }

    public int showEuropeDialog(EuropePanel.EuropeAction europeAction) {
        return canvas.showEuropeDialog(europeAction);
    }

    public void showEuropePanel() {
        canvas.showEuropePanel();
    }

    public void showEventPanel(EventType type) {
        canvas.showEventPanel(type);
    }

    public void showFindSettlementDialog() {
        canvas.showFindSettlementDialog();
    }

    public void showGameOptionsDialog(boolean editable, boolean loadCustomOptions) {
        canvas.showGameOptionsDialog(editable, loadCustomOptions);
    }

    public void showHighScoresPanel(String messageId) {
        canvas.showHighScoresPanel(messageId);
    }

    public void showIndianSettlementPanel(IndianSettlement indianSettlement) {
        canvas.showIndianSettlementPanel(indianSettlement);
    }


    public TradeAction showIndianSettlementTradeDialog(Settlement settlement,
            boolean canBuy,
            boolean canSell,
            boolean canGift) {
        return canvas.showIndianSettlementTradeDialog(settlement, canBuy, canSell, canGift);
    }

    public void showInformationMessage(FreeColObject displayObject, String messageId) {
        canvas.showInformationMessage(displayObject, messageId);
    }

    public void showInformationMessage(FreeColObject displayObject, StringTemplate template) {
        canvas.showInformationMessage(displayObject, template);
    }

    public void showInformationMessage(ModelMessage message) {
        canvas.showInformationMessage(message);
    }

    public void showInformationMessage(String messageId) {
        canvas.showInformationMessage(messageId);
    }

    public void showInformationMessage(StringTemplate template) {
        canvas.showInformationMessage(template);
    }

    public String showInputDialog(Tile tile, StringTemplate text, String defaultValue,
            String okText, String cancelText,
            boolean rejectEmptyString) {
        return canvas.showInputDialog(tile, text, defaultValue, okText, cancelText, rejectEmptyString);
    }

    public File showLoadDialog(File directory) {
        return canvas.showLoadDialog(directory);
    }

    public File showLoadDialog(File directory, FileFilter[] fileFilters) {
        return canvas.showLoadDialog(directory, fileFilters);
    }

    public boolean showLoadingSavegameDialog(boolean publicServer, boolean singlePlayer) {
        return canvas.showLoadingSavegameDialog(publicServer, singlePlayer);
    }


    public void showLogFilePanel() {
        canvas.showLogFilePanel();
    }

    public void showMainPanel() {
        canvas.showMainPanel();
    }

    public OptionGroup showMapGeneratorOptionsDialog(OptionGroup mgo, boolean editable, boolean loadCustomOptions){
        return canvas.showMapGeneratorOptionsDialog(mgo, editable, loadCustomOptions);
    }

    public Dimension showMapSizeDialog() {
        return canvas.showMapSizeDialog();
    }

    public void showModelMessages(ModelMessage... modelMessages) {
        canvas.showModelMessages(modelMessages);
    }

    public boolean showMonarchPanelDialog(MonarchAction action, StringTemplate replace) {
        return canvas.showMonarchPanelDialog(action, replace);
    }

    public DiplomaticTrade showNegotiationDialog(Unit unit, Settlement settlement, DiplomaticTrade agreement) {
        return canvas.showNegotiationDialog(unit, settlement, agreement);
    }

    public void showNewPanel() {
        canvas.showNewPanel();
    }

    public void showNewPanel(Specification specification) {
        canvas.showNewPanel(specification);
    }

    public boolean showPreCombatDialog(FreeColGameObject attacker,
            FreeColGameObject defender,
            Tile tile) {
        return canvas.showPreCombatDialog(attacker, defender, tile);
    }

    public void showReportCargoPanel() {
        canvas.showReportCargoPanel();
    }

    public void showReportColonyPanel() {
        canvas.showReportColonyPanel();
    }

    public void showReportContinentalCongressPanel() {
        canvas.showReportContinentalCongressPanel();
    }

    public void showReportEducationPanel() {
        canvas.showReportEducationPanel();
    }

    public void showReportExplorationPanel() {
        canvas.showReportExplorationPanel();
    }

    public void showReportForeignAffairPanel() {
        canvas.showReportForeignAffairPanel();
    }

    public void showReportHistoryPanel() {
        canvas.showReportHistoryPanel();
    }

    public void showReportIndianPanel() {
        canvas.showReportIndianPanel();
    }

    public void showReportLabourDetailPanel(UnitType unitType, Map<UnitType, Map<Location, Integer>> data,
            TypeCountMap<UnitType> unitCount, List<Colony> colonies) {
        canvas.showReportLabourDetailPanel(unitType, data, unitCount, colonies);

    }


    public void showReportLabourPanel() {
        canvas.showReportLabourPanel();
    }

    public void showReportMilitaryPanel() {
        canvas.showReportMilitaryPanel();
    }

    public void showReportNavalPanel() {
        canvas.showReportNavalPanel();
    }

    public void showReportProductionPanel() {
        canvas.showReportProductionPanel();
    }

    public void showReportReligiousPanel() {
        canvas.showReportReligiousPanel();
    }

    public void showReportRequirementsPanel() {
        canvas.showReportRequirementsPanel();
    }

    public void showReportTradePanel() {
        canvas.showReportTradePanel();
    }

    public void showReportTurnPanel(ModelMessage... messages) {
        canvas.showReportTurnPanel(messages);
    }

    public File showSaveDialog(File directory, String defaultName) {
        return canvas.showSaveDialog(directory, defaultName);
    }

    public File showSaveDialog(File directory, String standardName, FileFilter[] fileFilters, String defaultName) {
        return canvas.showSaveDialog(directory, standardName, fileFilters, defaultName);
    }

    public ScoutColonyAction showScoutForeignColonyDialog(Colony colony,
            Unit unit,
            boolean canNegotiate) {
        return canvas.showScoutForeignColonyDialog(colony, unit, canNegotiate);
    }




    public ScoutIndianSettlementAction showScoutIndianSettlementDialog(IndianSettlement settlement, String number) {
        return canvas.showScoutIndianSettlementDialog(settlement, number);
    }

    public int showSelectAmountDialog(GoodsType goodsType, int available, int defaultAmount, boolean needToPay) {
        return canvas.showSelectAmountDialog(goodsType, available, defaultAmount, needToPay);
    }

    public Location showSelectDestinationDialog(Unit unit) {
        return canvas.showSelectDestinationDialog(unit);
    }

    public SellAction showSellDialog(Unit unit, Settlement settlement,
            Goods goods, int gold) {
        return canvas.showSellDialog(unit, settlement, goods, gold);
    }

    public void showServerListPanel(String username, List<ServerInfo> serverList) {
        canvas.showServerListPanel(username, serverList);

    }

    public <T> T showSimpleChoiceDialog(Tile tile,
            String text, String cancelText,
            List<T> objects) {
        return canvas.showSimpleChoiceDialog(tile, text, cancelText, objects);
    }

    public void showStartGamePanel(Game game, Player player, boolean singlePlayerMode) {
        canvas.showStartGamePanel(game, player, singlePlayerMode);
    }


    public void showStatisticsPanel() {
        canvas.showStatisticsPanel();
    }

    public void showStatusPanel(String message) {
        canvas.showStatusPanel(message);
    }

    public void showTilePanel(Tile tile) {
        canvas.showTilePanel(tile);
    }

    public void showTilePopUpAtSelectedTile() {
        canvas.showTilePopup(getSelectedTile(),
                mapViewer.getCursor().getCanvasX(),
                mapViewer.getCursor().getCanvasY());
    }

    public boolean showTradeRouteDialog(Unit unit) {
        return canvas.showTradeRouteDialog(unit);
    }

    public boolean showTradeRouteInputDialog(TradeRoute newRoute) {
        return canvas.showTradeRouteInputDialog(newRoute);
    }

    public MissionaryAction showUseMissionaryDialog(Unit unit,
            IndianSettlement settlement,
            boolean canEstablish,
            boolean canDenounce) {
        return canvas.showUseMissionaryDialog(unit, settlement, canEstablish, canDenounce);
    }

    public void showVictoryPanel() {
        canvas.showVictoryPanel();
    }

    public boolean showWarehouseDialog(Colony colony) {
        return canvas.showWarehouseDialog(colony);
    }

    public void showWorkProductionPanel(Unit unit) {
        canvas.showWorkProductionPanel(unit);
    }

    /**
     * Starts the GUI by creating and displaying the GUI-objects.
     */
    public void startGUI(Dimension innerWindowSize,
                         final boolean sound,
                         final boolean showOpeningVideo,
                         final boolean loadGame) {
        final ClientOptions opts = freeColClient.getClientOptions();
        // Prepare the sound system.
        if (sound) {
            final AudioMixerOption amo
                = (AudioMixerOption) opts.getOption(ClientOptions.AUDIO_MIXER);
            final PercentageOption volume
                = (PercentageOption) opts.getOption(ClientOptions.AUDIO_VOLUME);
            try {
                this.soundPlayer = new SoundPlayer(amo, volume);
            } catch (Exception e) {
                // #3168279 reports an undocumented NPE thrown by
                // AudioSystem.getMixer(null).  Workaround this and other
                // such failures by just disabling sound.
                this.soundPlayer = null;
                logger.log(Level.WARNING, "Sound disabled", e);
            }
        } else {
            this.soundPlayer = null;
        }

        if (GraphicsEnvironment.isHeadless()) {
            logger.info("It seems that the GraphicsEnvironment is headless!");
        }
        this.gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        if (!isWindowed()) {
            if (!gd.isFullScreenSupported()) {
                String fullscreenNotSupported =
                   "\nIt seems that full screen mode is not fully supported for this" +
                   "\nGraphicsDevice. Please try the \"--windowed\" option if you\nexperience" +
                   "any graphical problems while running FreeCol.";
                logger.info(fullscreenNotSupported);
                System.out.println(fullscreenNotSupported);
                /*
                 * We might want this behavior later: logger.warning("It seems
                 * that full screen mode is not supported for this
                 * GraphicsDevice! Using windowed mode instead."); windowed =
                 * true; setWindowed(true); frame = new
                 * WindowedFrame(size);
                 */
            }
            Rectangle bounds = gd.getDefaultConfiguration().getBounds();
            innerWindowSize = new Dimension(bounds.width - bounds.x, bounds.height - bounds.y);
        }

        // Work around a Java 2D bug that seems to be X11 specific.
        // According to:
        //   http://www.oracle.com/technetwork/java/javase/index-142560.html
        //
        //   ``The use of pixmaps typically results in better
        //     performance. However, in certain cases, the opposite is true.''
        //
        // The standard workaround is to use -Dsun.java2d.pmoffscreen=false,
        // but this is too hard for some users, so provide an option to
        // do it easily.  However respect the initial value if present.
        //
        // Remove this if Java 2D is ever fixed.  DHYB.
        //
        final String pmoffscreen = "sun.java2d.pmoffscreen";
        BooleanOption usePixmaps
            = (BooleanOption) opts.getOption(ClientOptions.USE_PIXMAPS);
        String pmoffscreenValue = System.getProperty(pmoffscreen);
        if (pmoffscreenValue == null) {
            System.setProperty(pmoffscreen, usePixmaps.getValue().toString());
            logger.info(pmoffscreen + " using client option: "
                + usePixmaps.getValue().toString());
        } else {
            usePixmaps.setValue(new Boolean(pmoffscreenValue));
            logger.info(pmoffscreen + " overrides client option: "
                + pmoffscreenValue);
        }
        usePixmaps.addPropertyChangeListener(new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent e) {
                    String newValue = e.getNewValue().toString();
                    System.setProperty(pmoffscreen, newValue);
                    logger.info("Set " + pmoffscreen + " to: " + newValue);
                }
            });

        this.mapViewer = new MapViewer(freeColClient, this, innerWindowSize, imageLibrary);
        this.canvas = new Canvas(freeColClient, this, innerWindowSize, mapViewer);
        this.colonyTileGUI = new MapViewer(freeColClient, this, innerWindowSize, imageLibrary);

        changeWindowedMode(isWindowed());
        frame.setIconImage(ResourceManager.getImage("FrameIcon.image"));

        // Now that there is a canvas, prepare for language changes.
        LanguageOption o = (LanguageOption) freeColClient.getClientOptions().getOption(ClientOptions.LANGUAGE);
        if (o != null) {
            o.addPropertyChangeListener(new PropertyChangeListener() {
                    public void propertyChange(PropertyChangeEvent e) {
                        if (((Language) e.getNewValue()).getKey().equals(LanguageOption.AUTO)) {
                            showInformationMessage("autodetectLanguageSelected");
                        } else {
                            Locale l = ((Language) e.getNewValue()).getLocale();
                            Messages.setMessageBundle(l);
                            Messages.setModMessageBundle(l);
                            showInformationMessage(StringTemplate.template("newLanguageSelected")
                                .addName("%language%", l.getDisplayName()));
                        }
                    }
                });
        }

        // run opening video or main panel
        if (showOpeningVideo && !loadGame) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    canvas.showOpeningVideoPanel();
                }
            });
        } else {
            if (!loadGame) {
                showMainPanel();
            }
            playSound("sound.intro.general");
        }
        mapViewer.startCursorBlinking();
    }

    public void startMapEditorGUI() {
        // We may need to reset the zoom value to the default value
        scaleMap(2f);

        setupMapEditorMenuBar();
        canvas.showMapEditorTransformPanel();

        setupMouseListenerForMapEditor();
    }

    public void toggleViewMode() {
        mapViewer.toggleViewMode();
    }

    /**
     * Try to remove a unit from a colony.
     * - Check for population limit.
     * - Query if education should be abandoned.
     *
     * @param unit The <code>Unit</code> that is leaving the colony.
     * @return True if the unit is allowed to leave.
     */
    public boolean tryLeaveColony(Unit unit) {
        Colony colony = unit.getColony();
        String message = colony.getReducePopulationMessage();
        if (message != null) {
            showInformationMessage(message);
            return false;
        }
        return confirmAbandonEducation(unit, true);
    }

    /**
     * Confirms that a unit should move somewhere where it would have
     * to abandon its participation in education (if any).
     *
     * @param unit The <code>Unit</code> to check.
     * @param checkStudent Should we check for student movements.
     * @return True if the unit should proceed to move.
     */
    public boolean confirmAbandonEducation(Unit unit, boolean checkStudent) {
        StringTemplate message = unit.getAbandonEducationMessage(checkStudent);
        return message == null
            || showConfirmDialog(unit.getTile(), message,
                "abandonEducation.yes", "abandonEducation.no");
    }

    public void updateGameOptions() {
        canvas.updateGameOptions();
    }

    public void updateMapGeneratorOptions() {
        canvas.updateMapGeneratorOptions();
    }

    public void updateMenuBar() {
        if (frame != null && frame.getJMenuBar() != null) {
            ((FreeColMenuBar) frame.getJMenuBar()).update();
        }
    }


    private void setupMapEditorMenuBar() {
        frame.setJMenuBar(new MapEditorMenuBar(freeColClient, this));
    }

    private void setupMouseListenerForMapEditor() {
        CanvasMapEditorMouseListener listener = new CanvasMapEditorMouseListener(freeColClient, this, canvas);
        canvas.addMouseListener(listener);
        canvas.addMouseMotionListener(listener);
    }

    public Parameters showParametersDialog() {
        return canvas.showParametersDialog();
    }

    public Dimension showScaleMapSizeDialog() {
        return canvas.showScaleMapSizeDialog();
    }




    public void showMapControls(boolean value) {
        if (value && freeColClient.isInGame()) {
            if (mapControls == null) {
                try {
                    String className = freeColClient.getClientOptions()
                        .getString(ClientOptions.MAP_CONTROLS);
                    Class<?> controls = Class.forName("net.sf.freecol.client.gui.panel." + className);
                    mapControls = (MapControls) controls.getConstructor(FreeColClient.class, GUI.class)
                        .newInstance(freeColClient, this);
                } catch(Exception e) {
                    mapControls = new CornerMapControls(freeColClient, this);
                }
            }
            mapControls.update();
        }
        if (mapControls != null) {
            if (value) {
                if (!mapControls.isShowing()) {
                    mapControls.addToComponent(canvas);
                }
                mapControls.update();
            } else {
                if (mapControls.isShowing()) {
                    mapControls.removeFromComponent(canvas);
                }
            }
        }
    }

    public void updateMapControls() {
        if (mapControls != null)
            mapControls.update();
    }

    public void updateMapControlsInCanvas() {
        if (mapControls != null && mapControls.isShowing()) {
            mapControls.removeFromComponent(canvas);
            mapControls.addToComponent(canvas);
        }
    }



    public void zoomInMapControls() {
        mapControls.zoomIn();
    }

    public void zoomOutMapControls() {
        mapControls.zoomOut();
    }

    public boolean canZoomInMapControls() {
        return mapControls != null && mapControls.canZoomInMapControls();
    }

    public boolean canZoomOutMapControls() {
        return mapControls != null && mapControls.canZoomOutMapControls();
    }


    /**
     * Common utility routine to retrieve animation speed.
     *
     * @param unit The <code>Unit</code> to be animated.
     * @return The animation speed.
     */
    public int getAnimationSpeed(Unit unit) {
        String key = (freeColClient.getMyPlayer() == unit.getOwner())
            ? ClientOptions.MOVE_ANIMATION_SPEED
            : ClientOptions.ENEMY_MOVE_ANIMATION_SPEED;
        return freeColClient.getClientOptions().getInteger(key);
    }

    public void animateUnitAttack(Unit attacker, Unit defender,
                                  Tile attackerTile, Tile defenderTile,
                                  boolean success) {
        Animations.unitAttack(this, attacker, defender,
                              attackerTile, defenderTile, success);

    }

    public void animateUnitMove(Unit unit, Tile sourceTile, Tile destinationTile) {
        Animations.unitMove(this, unit, sourceTile, destinationTile);

    }

    public BufferedImage createMiniMapThumbNail() {
        MiniMap miniMap = new MiniMap(freeColClient, this);
        miniMap.setTileSize(MiniMap.MAX_TILE_SIZE);
        int width = freeColClient.getGame().getMap().getWidth()
            * MiniMap.MAX_TILE_SIZE + MiniMap.MAX_TILE_SIZE/2;
        int height = freeColClient.getGame().getMap().getHeight()
            * MiniMap.MAX_TILE_SIZE / 4;
        BufferedImage image = new BufferedImage(width, height,
                                                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = image.createGraphics();
        miniMap.paintMap(g2d);

        // TODO: this can probably done more efficiently
        // by applying a suitable AffineTransform to the
        // Graphics2D
        double scaledWidth = Math.min((64 * width) / height, 128);
        BufferedImage scaledImage = new BufferedImage((int) scaledWidth, 64,
                                                      BufferedImage.TYPE_INT_ARGB);
        scaledImage.createGraphics().drawImage(image, 0, 0, (int) scaledWidth, 64, null);
        return scaledImage;
    }

}
