package net.oni2.aeinstaller.backend.packages.download;

import java.util.Date;
import java.util.TreeSet;
import java.util.Vector;

import net.oni2.aeinstaller.backend.packages.Package;
import net.oni2.aeinstaller.backend.packages.download.ModDownload.ModDownloadState;

/**
 * @author Christian Illy
 */
public class ModDownloader implements ModDownloadListener {
	/**
	 * @author Christian Illy
	 */
	public enum State {
		/**
		 * Downloads running
		 */
		RUNNING,
		/**
		 * Aborted because of an error
		 */
		ERROR,
		/**
		 * Downloads interrupted
		 */
		INTERRUPTED,
		/**
		 * When the last file was downloaded and only unpacking is left
		 */
		LAST_FILE_DOWNLOADED,
		/**
		 * Everything completed
		 */
		FINISHED
	};

	private int currentDownload = -1;
	private int unpacked = 0;
	private Vector<ModDownload> downloads = new Vector<ModDownload>();
	private int totalSize = 0;
	private int downloadedComplete = 0;
	private int downloadedCurrent = 0;
	private long startMS;
	private State state = State.RUNNING;
	private ModDownloaderListener listener;

	/**
	 * Create a mods-download-process
	 * 
	 * @param mods
	 *            Mods to download
	 * @param listener
	 *            Listener for status updates
	 */
	public ModDownloader(TreeSet<Package> mods, ModDownloaderListener listener) {
		this.listener = listener;
		for (Package m : mods) {
			downloads.add(new ModDownload(m, this));
			totalSize += m.getZipSize();
		}
		startMS = new Date().getTime();
		startNextDownload();
	}

	private void startNextDownload() {
		if (currentDownload >= 0)
			downloadedComplete += downloads.get(currentDownload).getSize();
		currentDownload++;
		downloadedCurrent = 0;
		if ((state == State.RUNNING) && (currentDownload < downloads.size())) {
			downloads.get(currentDownload).start();
		} else if (state == State.RUNNING) {
			state = State.LAST_FILE_DOWNLOADED;
			notifyListener();
		} else {
			notifyListener();
		}
	}

	private int getTimeElapsed() {
		int total = (int) (new Date().getTime() - startMS);
		return total;
	}

	private int getDownloadSpeed() {
		int elap = getTimeElapsed();
		long down = downloadedComplete + downloadedCurrent;
		if (elap > 0)
			return (int)(down * 1000 / elap);
		else
			return 1;
	}

	private int getTimeRemaining() {
		int remainingSize = totalSize
				- (downloadedComplete + downloadedCurrent);
		return remainingSize / getDownloadSpeed();
	}

	private void notifyListener() {
		if (currentDownload < downloads.size()) {
			listener.updateStatus(this,
					downloads.get(currentDownload).getMod(), state, unpacked,
					downloads.size(), downloadedComplete + downloadedCurrent,
					totalSize, getTimeElapsed() / 1000, getTimeRemaining(),
					getDownloadSpeed());
		} else {
			listener.updateStatus(this, null, state, unpacked,
					downloads.size(), downloadedComplete + downloadedCurrent,
					totalSize, getTimeElapsed() / 1000, getTimeRemaining(),
					getDownloadSpeed());
		}
	}

	/**
	 * @return total download size
	 */
	public int getTotalSize() {
		return totalSize;
	}

	/**
	 * @return Is this process finished
	 */
	public boolean isFinished() {
		return state == State.FINISHED;
	}

	@Override
	public void modDownloadStatusUpdate(ModDownload source,
			ModDownloadState state, int done, int total) {
		switch (state) {
			case RUNNING:
				downloadedCurrent = done;
				notifyListener();
				break;
			case ERROR:
				this.state = State.ERROR;
				break;
			case DOWNLOADED:
				if (source == downloads.get(currentDownload))
					startNextDownload();
				break;
			case UNPACKED:
				source.getMod().updateLocalData();
				unpacked++;
				if (unpacked >= downloads.size())
					this.state = State.FINISHED;
				notifyListener();
				break;
			case INIT:
				break;
			case INTERRUPTED:
				break;
		}
	}

	/**
	 * Abort download process
	 */
	public void abort() {
		if (currentDownload < downloads.size()) {
			state = State.INTERRUPTED;
			ModDownload md = downloads.get(currentDownload);
			md.abort();
		}
	}

}
