/*
 * This file is part of the DistSim distributed simulation framework (client)
 * Copyright (C) 2007 Ulf Hermann
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package brn.distsim.client.data;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class represents DbBackedData with "dumb" dependent data. The dependent
 * data are simple entries which can be references and searched by their name.
 *
 * @author alve
 *
 * @param <E>
 */
public abstract class DbBackedEntries<E extends DbBackedEntries.Entry> extends
		DbBackedData<E> {

	protected Map<String, E> entries;

	public abstract class Entry implements Saveable {
		protected String name;

		public Entry(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}
	}

	public DbBackedEntries(Connection definitions, int id) {
		super(definitions);
		this.id = id;
		entries = new HashMap<String, E>();
	}

	public DbBackedEntries(DbBackedEntries<E> other) {
		this(other.definitions, 0);
		for (E e : other.dependentData) {
			E newEntry = getDependent(e);
			dependentData.add(newEntry);
			inserted.add(newEntry);
			entries.put(e.name, newEntry);
		}
	}

	/**
	 * creates this objects, but loads the _dependent data_ from the result set.
	 * This constructor is _different_ from other constructors with connections
	 * and result sets, because Entries are expected to be small and few, so
	 * they can be loaded when creating the Entries object
	 */
	public DbBackedEntries(Connection definitions, int id, ResultSet data)
			throws SQLException {
		this(definitions, id);
		loadFromDb(data);
	}

	protected void loadFromDb(ResultSet data) throws SQLException {
		entries.clear();
		super.loadFromDb(data);
		for (E entry : dependentData) {
			entries.put(entry.getName(), entry);
		}
	}

	/**
	 * remove an entry using its String name. This method is slow if there are
	 * many entries.
	 *
	 * @param name
	 *            name of the entry
	 * @return the removed entry
	 */
	public E remove(String name) {
		E entry = entries.remove(name);
		if (entry != null) {
			dependentData.remove(entry);
			super.removeInChangeTrackers(entry);
		}
		return entry;
	}

	public E remove(int pos) {
		E data = super.remove(pos);
		entries.remove(data.name);
		return data;
	}

	/**
	 * add an entry. If an entry with the same name already exists, the existing
	 * one can't be found with get(String) anymore after this operation
	 *
	 * @param item
	 *            the entry to be added
	 */
	public void add(E item) {
		super.add(item);
		entries.put(item.name, item);
	}

	/**
	 * replaces an entry. If an entry with the same name as the new entry
	 * already exists, the existing one can't be found with get(String) anymore
	 * after this operation
	 *
	 * @param pos
	 * @param item
	 * @return replaces an entry. If an entry with the same name as the new entry
   * already exists, the existing one can't be found with get(String) anymore
   * after this operation
	 */
	public E set(int pos, E item) {
		E old = super.set(pos, item);
		entries.remove(old.name);
		entries.put(item.name, item);
		return old;
	}

	/**
	 * replaces an entry identified by its string name. If an entry with the
	 * same name as item already exists, this one will be removed. item will be
	 * appended to be dependentData list and the entries map, so it will be able
	 * to find all items that could be found before after this operation.
	 *
	 * @param item
	 *            the new item to be inserted
	 */
	public void put(E item) {
		String index = item.name;
		E old = entries.get(index);
		entries.put(index, item);
		if (old != null) {
			dependentData.remove(old);
			if (inserted.contains(old)) {
				inserted.remove(old);
				inserted.add(item);
			} else {
				updated.remove(old);
				updated.add(item);
			}
		} else {
			inserted.add(item);
		}
		dependentData.add(item);
	}

	public void deleteAll() {
		super.deleteAll();
		entries.clear();
	}





	/**
	 * retrieve an entry identified by its string name
	 *
	 * @param index
	 *            name of the entry
	 * @return the entry, or null if not present
	 */
	public E get(String index) {
		return entries.get(index);
	}

	protected static List<String> namesToList(ResultSet names)
			throws SQLException {
		List<String> res = new ArrayList<String>();
		while (names.next()) {
			res.add(names.getString(1));
		}
		return res;
	}

	public Set<String> keySet() {
		return entries.keySet();
	}
}
