package net.oni2.aeinstaller.gui.modtable;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;

import net.oni2.aeinstaller.backend.packages.Package;
import net.oni2.aeinstaller.backend.packages.PackageManager;
import net.oni2.aeinstaller.gui.modtable.ModTable.ETableContentType;
import net.oni2.resourcebundle.UTF8ResourceBundleLoader;

/**
 * @author Christian Illy
 */
public class ModTableModel extends AbstractTableModel {

	private static final long serialVersionUID = -8278155705802697354L;

	private ResourceBundle bundle = UTF8ResourceBundleLoader
			.getBundle("net.oni2.aeinstaller.localization.ModTable");

	private Vector<Package> items = new Vector<Package>();
	private Vector<Boolean> install = new Vector<Boolean>();

	private HashSet<ModInstallSelectionListener> listeners = new HashSet<ModInstallSelectionListener>();

	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	private ETableContentType contentType = ETableContentType.MODS;

	/**
	 * Create a new model
	 * 
	 * @param contentType
	 *            Content type to show
	 */
	public ModTableModel(ETableContentType contentType) {
		this.contentType = contentType;
	}

	@Override
	public Object getValueAt(int row, int col) {
		Package mod = items.get(row);
		switch (col) {
			case -1:
				return mod;
			case 0:
				return install.get(row);
			case 1:
				return mod.getName();
			case 2:
				return mod.getPackageNumberString();
			case 3:
				return mod.getCreator();
			case 4:
				String res = "";
				res += (mod.isInstalled() ? "I" : "_");
				res += (mod.isLocalAvailable() && mod.isNewerAvailable() ? "U"
						: "_");
				res += (mod.isLocalAvailable() ? "D" : "_");
				return res;
			case 5:
				if (mod.getCreatedMillis() > 0)
					return sdf.format(new Date(mod.getCreatedMillis() * 1000));
				else
					return null;
			case 6:
				if (mod.getUpdatedMillis() > 0)
					return sdf.format(new Date(mod.getUpdatedMillis() * 1000));
				else
					return null;
		}
		return null;
	}

	@Override
	public String getColumnName(int col) {
		switch (col) {
			case 0:
				return bundle.getString("mod.install");
			case 1:
				return bundle.getString("mod.name");
			case 2:
				return bundle.getString("mod.package_number");
			case 3:
				return bundle.getString("mod.creator");
			case 4:
				return bundle.getString("mod.state");
			case 5:
				return bundle.getString("mod.added");
			case 6:
				return bundle.getString("mod.date");
		}
		return null;
	}

	@Override
	public int getRowCount() {
		return items.size();
	}

	@Override
	public int getColumnCount() {
		return 7;
	}

	@Override
	public Class<?> getColumnClass(int col) {
		switch (col) {
			case 0:
				return Boolean.class;
			case 1:
				return String.class;
			case 2:
				return String.class;
			case 3:
				return String.class;
			case 4:
				return String.class;
			case 5:
				return String.class;
			case 6:
				return String.class;
		}
		return null;
	}

	/**
	 * Set the constraints on the columns size for the given column
	 * 
	 * @param colNum
	 *            Column number
	 * @param col
	 *            Column object
	 */
	public void setColumnConstraints(int colNum, TableColumn col) {
		int w;
		switch (colNum) {
			case 0:
				w = 70;
				col.setPreferredWidth(w);
				col.setMinWidth(w);
				col.setMaxWidth(w);
				break;
			case 1:
				col.setPreferredWidth(150);
				break;
			case 2:
				w = 60;
				col.setPreferredWidth(w);
				col.setMinWidth(w);
				col.setMaxWidth(w);
				break;
			case 3:
				col.setPreferredWidth(90);
				break;
			case 4:
				w = 60;
				col.setPreferredWidth(w);
				col.setMinWidth(w);
				col.setMaxWidth(w);
				break;
			case 5:
				w = 100;
				col.setPreferredWidth(w);
				col.setMinWidth(w);
				col.setMaxWidth(w);
				break;
			case 6:
				w = 100;
				col.setPreferredWidth(w);
				col.setMinWidth(w);
				col.setMaxWidth(w);
				break;
		}
	}

	/**
	 * Reload the nodes data after an update to the cache
	 */
	public void reloadData() {
		items.clear();
		switch (contentType) {
			case MODS:
				items.addAll(PackageManager.getInstance()
						.getModsValidAndNotCore());
				break;
			case TOOLS:
				items.addAll(PackageManager.getInstance().getTools());
				break;
			case CORE:
				items.addAll(PackageManager.getInstance().getCoreTools());
				items.addAll(PackageManager.getInstance().getCoreMods());
				break;
		}
		revertSelection();
	}

	/**
	 * Revert the selection to the mods that are currently installed
	 */
	public void revertSelection() {
		install.clear();
		for (int i = 0; i < items.size(); i++) {
			boolean installed = items.get(i).isInstalled();
			install.add(i, installed);
		}
		updateDownloadSize();
		fireTableDataChanged();
	}

	/**
	 * Reload the selection after a config was loaded
	 * 
	 * @param config
	 *            Config to load
	 */
	public void reloadSelection(File config) {
		if (contentType == ETableContentType.MODS) {
			Vector<Integer> selected = PackageManager.getInstance()
					.loadModSelection(config);
			install.clear();
			for (int i = 0; i < items.size(); i++) {
				install.add(i,
						selected.contains(items.get(i).getPackageNumber()));
			}
			updateDownloadSize();
			fireTableDataChanged();
		}
	}

	/**
	 * Get the items vector
	 * 
	 * @return Items
	 */
	public Vector<Package> getItems() {
		return items;
	}

	/**
	 * @return Mods selected for installation
	 */
	public TreeSet<Package> getSelectedMods() {
		TreeSet<Package> res = new TreeSet<Package>();
		for (int i = 0; i < items.size(); i++) {
			if (install.get(i))
				res.add(items.get(i));
		}
		return res;
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return columnIndex == 0;
	}

	private void notifyDownloadSize(int size, int count) {
		for (ModInstallSelectionListener dsl : listeners)
			dsl.modInstallSelectionChanged(size, count);
	}

	/**
	 * Check the current download size and notify listeners
	 */
	public void updateDownloadSize() {
		int size = 0;
		int count = 0;
		for (int i = 0; i < items.size(); i++) {
			if (install.get(i)) {
				count++;
				Package m = items.get(i);
				if (!m.isLocalAvailable())
					size += m.getZipSize();
			}
		}
		notifyDownloadSize(size, count);
	}

	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		super.setValueAt(aValue, rowIndex, columnIndex);
		if (columnIndex == 0) {
			selectPackage(rowIndex, (Boolean) aValue);
			updateDownloadSize();
		}
	}

	/**
	 * Select package in given row
	 * 
	 * @param row
	 *            Row of package
	 * @param select
	 *            Select or not
	 */
	public void selectPackage(int row, boolean select) {
		install.set(row, select);
	}

	/**
	 * @param lis
	 *            Listener to receive download size changed events
	 */
	public void addDownloadSizeListener(ModInstallSelectionListener lis) {
		listeners.add(lis);
	}

	/**
	 * @param lis
	 *            Listener to no longer receive download size changed events
	 */
	public void removeDownloadSizeListener(ModInstallSelectionListener lis) {
		listeners.remove(lis);
	}

}
