/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mobac.gui;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;

import org.apache.log4j.Logger;

import mobac.program.AtlasThread;
import mobac.program.Logging;
import mobac.program.interfaces.AtlasInterface;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.MapSourceListener;
import mobac.program.model.Atlas;
import mobac.program.model.AtlasOutputFormat;
import mobac.program.model.Settings;
import mobac.utilities.GBC;
import mobac.utilities.GUIExceptionHandler;
import mobac.utilities.I18nUtils;
import mobac.utilities.OSUtilities;
import mobac.utilities.Utilities;

/**
 * A window showing the progress while {@link AtlasThread} downloads and processes the map tiles.
 * 
 */
public class AtlasProgress extends JFrame implements ActionListener, MapSourceListener {

	private static Logger log = Logger.getLogger(AtlasProgress.class);

	private static final long serialVersionUID = -1L;

	private static final Timer TIMER = new Timer(true);

	private JProgressBar atlasProgressBar;
	private JProgressBar mapDownloadProgressBar;
	private JProgressBar mapCreationProgressBar;

	private Container background;

	private long initialTotalTime;
	private long initialMapDownloadTime;

	private static class Data {
		AtlasInterface atlasInterface;
		MapInterface map;
		MapInfo mapInfo;
		long numberOfDownloadedBytes = 0;
		long numberOfBytesLoadedFromCache = 0;
		int totalNumberOfTiles = 0;
		int totalNumberOfMaps = 0;
		int totalProgress = 0;
		int totalProgressTenthPercent = -1;
		int currentMapNumber = 0;
		int mapDownloadProgress = 0;
		int mapDownloadNumberOfTiles = 0;
		int mapCreationProgress = 0;
		int mapCreationMax = 0;
		int mapRetryErrors = 0;
		int mapPermanentErrors = 0;
		int prevMapsRetryErrors = 0;
		int prevMapsPermanentErrors = 0;
		boolean paused = false;
	}

	private final Data data = new Data();

	private boolean aborted = false;
	private boolean finished = false;

	private JLabel windowTitle;

	private JLabel title;
	private JLabel mapInfoLabel;
	private JLabel mapDownloadTitle;
	private JLabel atlasPercent;
	private JLabel mapDownloadPercent;
	private JLabel atlasMapsDone;
	private JLabel mapDownloadElementsDone;
	private JLabel atlasTimeLeft;
	private JLabel mapDownloadTimeLeft;
	private JLabel mapCreation;
	private JLabel nrOfDownloadedBytes;
	private JLabel nrOfDownloadedBytesValue;
	private JLabel nrOfDownloadedBytesPerSecond;
	private JLabel nrOfDownloadedBytesPerSecondValue;
	private JLabel nrOfCacheBytes;
	private JLabel nrOfCacheBytesValue;
	private JLabel activeDownloads;
	private JLabel activeDownloadsValue;
	private JLabel retryableDownloadErrors;
	private JLabel retryableDownloadErrorsValue;
	private JLabel permanentDownloadErrors;
	private JLabel permanentDownloadErrorsValue;
	private JLabel totalDownloadTime;
	private JLabel totalDownloadTimeValue;

	private JCheckBox ignoreDlErrors;
	private JLabel statusLabel;

	private JButton dismissWindowButton;
	private JButton openProgramFolderButton;
	private JButton abortAtlasCreationButton;
	private JButton pauseResumeDownloadButton;

	private AtlasCreationController downloadControlListener = null;

	private UpdateTask updateTask = null;
	private GUIUpdater guiUpdater = null;

	private AtlasThread atlasThread;

	private ArrayList<MapInfo> mapInfos = null;

	private static String TEXT_MAP_DOWNLOAD = I18nUtils.localizedStringForKey("dlg_download_zoom_level_progress");
	private static String TEXT_PERCENT = I18nUtils.localizedStringForKey("dlg_download_done_percent");
	private static String TEXT_TENTHPERCENT = I18nUtils.localizedStringForKey("dlg_download_done_tenthpercent");

	public AtlasProgress(AtlasThread atlasThread) {
		super(I18nUtils.localizedStringForKey("dlg_download_title"));
		this.atlasThread = atlasThread;
		ToolTipManager.sharedInstance().setDismissDelay(12000);
		if (MainGUI.getMainGUI() == null) // Atlas creation started via command-line, no MainGUi available
			setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		else
			setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		setIconImages(MainGUI.MOBAC_ICONS);
		setLayout(new GridBagLayout());
		updateTask = new UpdateTask();
		guiUpdater = new GUIUpdater();

		createComponents();
		// Initialize the layout in respect to the layout (font size ...)
		pack();
		setMinimumSize(new Dimension(getWidth(), getHeight()));

		guiUpdater.run();

		// The layout is now initialized - we disable it because we don't want
		// want to the labels to jump around if the content changes.
		// background.setLayout(null);
		// setResizable(false);

		Dimension dScreen = Toolkit.getDefaultToolkit().getScreenSize();
		Dimension dContent = getSize();
		setLocation((dScreen.width - dContent.width) / 2, (dScreen.height - dContent.height) / 2);

		initialTotalTime = System.currentTimeMillis();
		initialMapDownloadTime = System.currentTimeMillis();

		addWindowListener(new CloseListener());
	}

	private void createComponents() {
		background = new JPanel(new GridBagLayout());

		windowTitle = new JLabel(I18nUtils.localizedStringForKey("dlg_download_window_title", ""));

		title = new JLabel(I18nUtils.localizedStringForKey("dlg_download_map_progress"));

		mapInfoLabel = new JLabel();

		atlasMapsDone = new JLabel(I18nUtils.localizedStringForKey("dlg_download_map_done_count_default"));
		atlasPercent = new JLabel(String.format(TEXT_TENTHPERCENT, 100.0));
		atlasTimeLeft = new JLabel(I18nUtils.localizedStringForKey("dlg_download_remain_time_default"), JLabel.RIGHT);
		atlasProgressBar = new JProgressBar();

		mapDownloadTitle = new JLabel(TEXT_MAP_DOWNLOAD + "000");
		mapDownloadElementsDone = new JLabel(I18nUtils.localizedStringForKey("dlg_download_tile_done_count_default"));
		mapDownloadPercent = new JLabel(String.format(TEXT_PERCENT, 100));
		mapDownloadTimeLeft = new JLabel(I18nUtils.localizedStringForKey("dlg_download_remain_time_default"),
				JLabel.RIGHT);
		mapDownloadProgressBar = new JProgressBar();

		mapCreation = new JLabel(I18nUtils.localizedStringForKey("dlg_download_map_create_title"));
		mapCreationProgressBar = new JProgressBar();

		nrOfDownloadedBytesPerSecond = new JLabel(I18nUtils.localizedStringForKey("dlg_download_avg_speed"));
		nrOfDownloadedBytesPerSecondValue = new JLabel();
		nrOfDownloadedBytes = new JLabel(I18nUtils.localizedStringForKey("dlg_download_total_bytes"));
		nrOfDownloadedBytesValue = new JLabel();
		nrOfCacheBytes = new JLabel(I18nUtils.localizedStringForKey("dlg_download_bytes_from_cache"));
		nrOfCacheBytesValue = new JLabel();

		activeDownloads = new JLabel(I18nUtils.localizedStringForKey("dlg_download_thread_count"));
		activeDownloadsValue = new JLabel();
		retryableDownloadErrors = new JLabel(I18nUtils.localizedStringForKey("dlg_download_retry_count"));
		retryableDownloadErrors.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_error_tips"));
		retryableDownloadErrorsValue = new JLabel();
		retryableDownloadErrorsValue.setToolTipText(retryableDownloadErrors.getToolTipText());
		permanentDownloadErrors = new JLabel(I18nUtils.localizedStringForKey("dlg_download_failed_count"));
		permanentDownloadErrors.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_error_tips"));
		permanentDownloadErrorsValue = new JLabel();
		permanentDownloadErrorsValue.setToolTipText(permanentDownloadErrors.getToolTipText());
		totalDownloadTime = new JLabel(I18nUtils.localizedStringForKey("dlg_download_total_time"));
		totalDownloadTimeValue = new JLabel();

		ignoreDlErrors = new JCheckBox(I18nUtils.localizedStringForKey("dlg_download_checkbox_ignore_error"),
				Settings.getInstance().ignoreDlErrors);
		statusLabel = new JLabel(I18nUtils.localizedStringForKey("dlg_download_status_title"));
		Font f = statusLabel.getFont();
		statusLabel.setFont(f.deriveFont(Font.BOLD));
		abortAtlasCreationButton = new JButton(I18nUtils.localizedStringForKey("dlg_download_btn_abort"));
		abortAtlasCreationButton.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_btn_abort_tips"));
		dismissWindowButton = new JButton(I18nUtils.localizedStringForKey("dlg_download_btn_close_win"));
		dismissWindowButton.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_btn_close_win_tips_disable"));
		dismissWindowButton.setVisible(false);
		openProgramFolderButton = new JButton(I18nUtils.localizedStringForKey("dlg_download_btn_open_folder"));
		openProgramFolderButton
				.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_btn_open_folder_tips_disabled"));
		openProgramFolderButton.setEnabled(false);
		pauseResumeDownloadButton = new JButton(I18nUtils.localizedStringForKey("dlg_download_btn_pause_resume"));

		GBC gbcRIF = GBC.std().insets(0, 0, 20, 0).fill(GBC.HORIZONTAL);
		GBC gbcEol = GBC.eol();
		GBC gbcEolFill = GBC.eol().fill(GBC.HORIZONTAL);
		GBC gbcEolFillI = GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 0);

		background.add(windowTitle, gbcEolFill);
		background.add(Box.createVerticalStrut(10), gbcEol);

		background.add(mapInfoLabel, gbcEolFill);
		background.add(Box.createVerticalStrut(20), gbcEol);

		background.add(title, gbcRIF);
		background.add(atlasMapsDone, gbcRIF);
		background.add(atlasPercent, gbcRIF);
		background.add(atlasTimeLeft, gbcEolFill);
		background.add(atlasProgressBar, gbcEolFillI);
		background.add(Box.createVerticalStrut(20), gbcEol);

		background.add(mapDownloadTitle, gbcRIF);
		background.add(mapDownloadElementsDone, gbcRIF);
		background.add(mapDownloadPercent, gbcRIF);
		background.add(mapDownloadTimeLeft, gbcEolFill);
		background.add(mapDownloadProgressBar, gbcEolFillI);
		background.add(Box.createVerticalStrut(20), gbcEol);

		background.add(mapCreation, gbcEol);
		background.add(mapCreationProgressBar, gbcEolFillI);
		background.add(Box.createVerticalStrut(10), gbcEol);

		JPanel infoPanel = new JPanel(new GridBagLayout());
		GBC gbci = GBC.std().insets(0, 3, 3, 3);
		infoPanel.add(nrOfDownloadedBytes, gbci);
		infoPanel.add(nrOfDownloadedBytesValue, gbci.toggleEol());
		infoPanel.add(nrOfCacheBytes, gbci.toggleEol());
		infoPanel.add(nrOfCacheBytesValue, gbci.toggleEol());
		infoPanel.add(nrOfDownloadedBytesPerSecond, gbci.toggleEol());
		infoPanel.add(nrOfDownloadedBytesPerSecondValue, gbci.toggleEol());
		infoPanel.add(activeDownloads, gbci.toggleEol());
		infoPanel.add(activeDownloadsValue, gbci.toggleEol());
		infoPanel.add(retryableDownloadErrors, gbci.toggleEol());
		infoPanel.add(retryableDownloadErrorsValue, gbci.toggleEol());
		infoPanel.add(permanentDownloadErrors, gbci.toggleEol());
		infoPanel.add(permanentDownloadErrorsValue, gbci.toggleEol());
		infoPanel.add(totalDownloadTime, gbci.toggleEol());
		infoPanel.add(totalDownloadTimeValue, gbci.toggleEol());

		JPanel bottomPanel = new JPanel(new GridBagLayout());
		bottomPanel.add(infoPanel, GBC.std().gridheight(2).fill());
		bottomPanel.add(ignoreDlErrors, GBC.eol().anchor(GBC.EAST));

		bottomPanel.add(statusLabel, GBC.eol().anchor(GBC.CENTER));

		GBC gbcRight = GBC.std().anchor(GBC.SOUTHEAST).insets(5, 0, 0, 0);
		bottomPanel.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
		bottomPanel.add(abortAtlasCreationButton, gbcRight);
		bottomPanel.add(dismissWindowButton, gbcRight);
		bottomPanel.add(pauseResumeDownloadButton, gbcRight);
		bottomPanel.add(openProgramFolderButton, gbcRight);

		background.add(bottomPanel, gbcEolFillI);

		JPanel borderPanel = new JPanel(new GridBagLayout());
		borderPanel.add(background, GBC.std().insets(10, 10, 10, 10).anchor(GBC.NORTH).fill());

		add(borderPanel, GBC.std().fill().anchor(GBC.NORTH));

		abortAtlasCreationButton.addActionListener(this);
		dismissWindowButton.addActionListener(this);
		openProgramFolderButton.addActionListener(this);
		pauseResumeDownloadButton.addActionListener(this);
	}

	public void initAtlas(AtlasInterface atlasInterface) {
		data.atlasInterface = atlasInterface;
		if (atlasInterface.getOutputFormat().equals(AtlasOutputFormat.TILESTORE)) {
			data.totalNumberOfTiles = (int) atlasInterface.calculateTilesToDownload();
		} else {
			data.totalNumberOfTiles = (int) atlasInterface.calculateTilesToDownload() * 2;
		}
		int mapCount = 0;
		int tileCount = 0;
		mapInfos = new ArrayList<>(100);
		for (LayerInterface layer : atlasInterface) {
			mapCount += layer.getMapCount();
			for (MapInterface map : layer) {
				int before = tileCount;
				int mapTiles = (int) map.calculateTilesToDownload();
				tileCount += mapTiles + mapTiles;
				mapInfos.add(new MapInfo(map, before, tileCount));
			}
		}
		mapInfos.trimToSize();
		data.totalNumberOfMaps = mapCount;

		initialTotalTime = System.currentTimeMillis();
		initialMapDownloadTime = -1;
		updateGUI();
		setVisible(true);
		TIMER.schedule(updateTask, 0, 500);
	}

	public void initMapDownload(MapInterface map) {
		int index = mapInfos.indexOf(new MapInfo(map, 0, 0));
		data.mapInfo = mapInfos.get(index);
		data.totalProgress = data.mapInfo.tileCountOnStart;
		data.map = map;
		data.mapDownloadNumberOfTiles = (int) map.calculateTilesToDownload();
		initialMapDownloadTime = System.currentTimeMillis();
		data.prevMapsPermanentErrors += data.mapPermanentErrors;
		data.prevMapsRetryErrors += data.mapRetryErrors;
		data.mapCreationProgress = 0;
		data.mapDownloadProgress = 0;
		data.currentMapNumber = index + 1;
		updateGUI();
	}

	/**
	 * Initialize the GUI progress bars
	 * 
	 * @param maxTilesToProcess
	 */
	public void initMapCreation(int maxTilesToProcess) {
		data.mapCreationProgress = 0;
		data.mapCreationMax = maxTilesToProcess;
		initialMapDownloadTime = -1;
		updateGUI();
	}

	public void setErrorCounter(int retryErrors, int permanentErrors) {
		data.mapRetryErrors = retryErrors;
		data.mapPermanentErrors = permanentErrors;
		updateGUI();
	}

	public void incMapDownloadProgress() {
		data.mapDownloadProgress++;
		data.totalProgress++;
		updateGUI();
	}

	public void incMapCreationProgress() {
		setMapCreationProgress(data.mapCreationProgress + 1);
	}

	public void incMapCreationProgress(int stepSize) {
		setMapCreationProgress(data.mapCreationProgress + stepSize);
	}

	public void setMapCreationProgress(int progress) {
		data.mapCreationProgress = progress;
		data.totalProgress = data.mapInfo.tileCountOnStart + data.mapInfo.mapTiles
				+ (int) (((long) data.mapInfo.mapTiles) * data.mapCreationProgress / data.mapCreationMax);
		updateGUI();
	}

	public boolean ignoreDownloadErrors() {
		return ignoreDlErrors.isSelected();
	}

	public void tileDownloaded(int size) {
		synchronized (data) {
			data.numberOfDownloadedBytes += size;
		}
		updateGUI();
	}

	public void tileLoadedFromCache(int size) {
		synchronized (data) {
			data.numberOfBytesLoadedFromCache += size;
		}
		updateGUI();
	}

	private String formatTime(long longSeconds) {
		String timeString = "";

		if (longSeconds < 0) {
			timeString = I18nUtils.localizedStringForKey("dlg_download_time_unknown");
		} else {
			int minutes = (int) (longSeconds / 60);
			int seconds = (int) (longSeconds % 60);
			if (minutes > 0)
				timeString += Integer.toString(minutes) + " "
						+ (minutes == 1 ? I18nUtils.localizedStringForKey("minute")
								: I18nUtils.localizedStringForKey("minutes"))
						+ " ";
			timeString += Integer.toString(seconds) + " " + (seconds == 1 ? I18nUtils.localizedStringForKey("second")
					: I18nUtils.localizedStringForKey("seconds"));
		}
		return timeString;
	}

	public void setZoomLevel(int theZoomLevel) {
		mapDownloadTitle.setText(TEXT_MAP_DOWNLOAD + Integer.toString(theZoomLevel));
	}

	public void atlasCreationFinished() {
		finished = true;
		stopUpdateTask();
		forceUpdateGUI();
		downloadControlListener = null;
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				abortAtlasCreationButton.setEnabled(false);

				if (aborted) {
					setTitle(I18nUtils.localizedStringForKey("dlg_download_abort_title"));
				} else {
					setTitle(I18nUtils.localizedStringForKey("dlg_download_succeed_title"));
				}
				// mapInfoLabel.setText("");
				atlasMapsDone.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_map_done_count"),
						data.currentMapNumber, data.totalNumberOfMaps));

				abortAtlasCreationButton.setVisible(false);

				dismissWindowButton
						.setToolTipText(I18nUtils.localizedStringForKey("dlg_download_btn_close_win_tips_enable"));
				dismissWindowButton.setVisible(true);

				if (!aborted) {
					openProgramFolderButton.setToolTipText(
							I18nUtils.localizedStringForKey("dlg_download_btn_open_folder_tips_enabled"));
					openProgramFolderButton.setEnabled(true);
				}
			}
		});
	}

	private synchronized void stopUpdateTask() {
		try {
			updateTask.cancel();
			updateTask = null;
		} catch (Exception e) {
		}
	}

	public void closeWindow() {
		try {
			stopUpdateTask();
			downloadControlListener = null;
			setVisible(false);
		} finally {
			dispose();
		}
	}

	public AtlasCreationController getDownloadControlListener() {
		return downloadControlListener;
	}

	public void setDownloadControlerListener(AtlasCreationController threadControlListener) {
		this.downloadControlListener = threadControlListener;
	}

	public void actionPerformed(ActionEvent event) {
		Object source = event.getSource();
		File atlasFolder = Settings.getInstance().getAtlasOutputDirectory();
		if (openProgramFolderButton.equals(source)) {
			try {
				OSUtilities.openFolderBrowser(atlasFolder);
			} catch (Exception e) {
				log.error("", e);
			}
		} else if (dismissWindowButton.equals(source)) {
			downloadControlListener = null;
			closeWindow();
		} else if (abortAtlasCreationButton.equals(source)) {
			aborted = true;
			stopUpdateTask();
			if (downloadControlListener != null)
				downloadControlListener.abortAtlasCreation();
			else
				closeWindow();
		} else if (pauseResumeDownloadButton.equals(source)) {
			if (downloadControlListener != null)
				downloadControlListener.pauseResumeAtlasCreation();
		}
	}

	public void updateGUI() {
		guiUpdater.updateAsynchronously();
	}

	public void forceUpdateGUI() {
		SwingUtilities.invokeLater(guiUpdater);
	}

	private class GUIUpdater implements Runnable {

		int scheduledCounter = 0;

		public void updateAsynchronously() {
			// If there is still at least one scheduled update request to be
			// executed we don't have add another one as this can result in an
			// to overloaded swing invocation queue.
			synchronized (this) {
				if (scheduledCounter > 0) {
					return;
				}
				scheduledCounter++;
			}
			SwingUtilities.invokeLater(this);
		}

		public void run() {
			synchronized (this) {
				scheduledCounter--;
			}
			if (data.atlasInterface != null) {
				windowTitle.setText(
						I18nUtils.localizedStringForKey("dlg_download_window_title", data.atlasInterface.getName()));
			}

			if (data.map != null) {
				String text = String.format(I18nUtils.localizedStringForKey("dlg_download_map_info_label"),
						data.map.getName(), data.map.getLayer().getName(), data.map.getMapSource().toString());
				mapInfoLabel.setText(text);
			}

			// atlas progress
			atlasProgressBar.setMaximum(data.totalNumberOfTiles);
			atlasProgressBar.setValue(data.totalProgress);

			int newTenthPercent = (int) (data.totalProgress * 1000d / (double) data.totalNumberOfTiles);
			try {
				boolean pauseState = atlasThread.isPaused();
				String statusText = I18nUtils.localizedStringForKey("dlg_download_status_running");
				if (aborted)
					statusText = I18nUtils.localizedStringForKey("dlg_download_status_aborted");
				else if (finished)
					statusText = I18nUtils.localizedStringForKey("dlg_download_status_finished");
				else if (pauseState)
					statusText = I18nUtils.localizedStringForKey("dlg_download_status_paused");
				else
					statusText = I18nUtils.localizedStringForKey("dlg_download_status_running");
				statusLabel.setText(I18nUtils.localizedStringForKey("dlg_download_status_title") + " " + statusText);

				if (data.totalProgressTenthPercent != newTenthPercent || pauseState != data.paused) {
					data.totalProgressTenthPercent = newTenthPercent;
					atlasPercent.setText(String.format(TEXT_TENTHPERCENT, data.totalProgressTenthPercent / 10.0));
					if (data.atlasInterface != null) {
						String text = String.format(I18nUtils.localizedStringForKey("dlg_download_atlas_progress"),
								data.totalProgressTenthPercent / 10, data.atlasInterface.getName(),
								data.atlasInterface.getOutputFormat());
						if (pauseState) {
							text += " [" + I18nUtils.localizedStringForKey("dlg_download_status_paused") + "]";
						}
						AtlasProgress.this.setTitle(text);
					}
				}
				data.paused = pauseState;
			} catch (NullPointerException e) {
			}

			long seconds = -1;
			int totalProgress = data.totalProgress;
			if (totalProgress != 0) {
				// Avoid for a possible division by zero
				int totalTilesRemaining = data.totalNumberOfTiles - totalProgress;
				long totalElapsedTime = System.currentTimeMillis() - initialTotalTime;
				seconds = (totalElapsedTime * totalTilesRemaining / (1000L * totalProgress));
			}
			atlasTimeLeft.setText(I18nUtils.localizedStringForKey("dlg_download_remain_time", formatTime(seconds)));

			// layer progress
			mapDownloadProgressBar.setMaximum(data.mapDownloadNumberOfTiles);
			mapDownloadProgressBar.setValue(data.mapDownloadProgress);

			mapDownloadPercent
					.setText(String.format(TEXT_PERCENT, (int) (mapDownloadProgressBar.getPercentComplete() * 100)));

			mapDownloadElementsDone
					.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_tile_done_count"),
							data.mapDownloadProgress, data.mapDownloadNumberOfTiles));

			seconds = -1;
			int mapDlProgress = data.mapDownloadProgress;
			if (mapDlProgress != 0 && initialMapDownloadTime > 0)
				seconds = ((System.currentTimeMillis() - initialMapDownloadTime)
						* (data.mapDownloadNumberOfTiles - mapDlProgress) / (1000L * mapDlProgress));
			mapDownloadTimeLeft.setText(
					String.format(I18nUtils.localizedStringForKey("dlg_download_remain_time"), formatTime(seconds)));

			// map progress
			mapCreation.setText(I18nUtils.localizedStringForKey("dlg_download_map_create_title"));
			mapCreationProgressBar.setValue(data.mapCreationProgress);
			mapCreationProgressBar.setMaximum(data.mapCreationMax);
			atlasMapsDone.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_map_done_count"),
					(data.currentMapNumber - 1), data.totalNumberOfMaps));

			// bytes per second
			long rate = data.numberOfDownloadedBytes * 1000;
			long time = System.currentTimeMillis() - initialMapDownloadTime;
			if (data.mapCreationProgress == 0 && initialMapDownloadTime > 0) {
				if (time == 0) {
					nrOfDownloadedBytesPerSecondValue.setText("??");
				} else {
					rate = rate / time;
					nrOfDownloadedBytesPerSecondValue
							.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_avg_speed_value"),
									Utilities.formatBytes(rate)));
				}
			}

			// downloaded bytes
			nrOfDownloadedBytesValue.setText(": " + Utilities.formatBytes(data.numberOfDownloadedBytes));
			nrOfCacheBytesValue.setText(": " + Utilities.formatBytes(data.numberOfBytesLoadedFromCache));

			// total creation time
			long totalSeconds = (System.currentTimeMillis() - initialTotalTime) / 1000;
			totalDownloadTimeValue.setText(": " + formatTime(totalSeconds));
			totalDownloadTimeValue.repaint();

			// active downloads
			int activeDownloads = (atlasThread == null) ? 0 : atlasThread.getActiveDownloads();
			activeDownloadsValue.setText(": " + activeDownloads);
			activeDownloadsValue.repaint();

			int totalRetryableErrors = data.prevMapsRetryErrors + data.mapRetryErrors;
			retryableDownloadErrorsValue
					.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_retry_count_value"),
							data.mapRetryErrors, totalRetryableErrors));
			retryableDownloadErrorsValue.repaint();
			int totalPermanentErrors = data.prevMapsPermanentErrors + data.mapPermanentErrors;
			permanentDownloadErrorsValue
					.setText(String.format(I18nUtils.localizedStringForKey("dlg_download_failed_count_value"),
							data.mapPermanentErrors, totalPermanentErrors));
			permanentDownloadErrorsValue.repaint();
		}
	}

	private class UpdateTask extends TimerTask {

		@Override
		public void run() {
			updateGUI();
		}
	}

	private class CloseListener extends WindowAdapter {

		@Override
		public void windowClosing(WindowEvent e) {
			log.debug("Closing event detected for atlas progress window");
			AtlasCreationController listener = AtlasProgress.this.downloadControlListener;
			if (listener != null)
				listener.abortAtlasCreation();
		}

	}

	protected static class MapInfo {

		final MapInterface map;
		final int tileCountOnStart;
		final int tileCountOnEnd;
		final int mapTiles;

		public MapInfo(MapInterface map, int tileCountOnStart, int tileCountOnEnd) {
			super();
			this.map = map;
			this.tileCountOnStart = tileCountOnStart;
			this.tileCountOnEnd = tileCountOnEnd;
			this.mapTiles = (int) map.calculateTilesToDownload();
		}

		@Override
		public int hashCode() {
			return map.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof MapInfo))
				return false;
			return map.equals(((MapInfo) obj).map);
		}
	}

	public static interface AtlasCreationController {

		public void abortAtlasCreation();

		public void pauseResumeAtlasCreation();

		public boolean isPaused();

	}

	public static void main(String[] args) {
		Logging.configureLogging();
		GUIExceptionHandler.installToolkitEventQueueProxy();
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			log.error("The selection of look and feel failed!", e);
		}
		AtlasProgress ap = new AtlasProgress(null);
		Atlas atlas = Atlas.newInstance();
		atlas.setName("Atlas name");
		ap.initAtlas(atlas);
		ap.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		ap.setVisible(true);
	}
}
