/*
 *  Copyright (C) 2006  Takashi Kasuya <kasuya@sfc.keio.ac.jp>
 *
 * This library is free software; you can redistribute it and/or
 *　modify it under the terms of the GNU Lesser General Public
 *　License as published by the Free Software Foundation; either
 *　version 2.1 of the License, or (at your option) any later version.
 *　This library 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
 *　Lesser General Public License for more details.
 *
 *　You should have received a copy of the GNU Lesser General Public
 *　License along with this library; if not, write to the Free Software
 *　Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jp.ac.naka.ec.entity;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.sip.SipException;
import javax.sip.address.URI;

import jp.ac.naka.ec.EventDispatcher;
import jp.ac.naka.ec.TreeNode;
import jp.ac.naka.ec.db.TupleSpaceAdapter;
import jp.ac.naka.ec.entity.EntityEvent.EventType;
import net.sourceforge.jsdp.SessionDescription;

import org.apache.log4j.Logger;

/**
 * @author Takashi Kasuya
 * 
 */
public class EntityContainerImpl extends EntityImpl implements EntityContainer {

	protected Map<String, EntityListener> table;
	private static Logger logger = Logger.getLogger(EntityContainerImpl.class);
	private TupleSpaceAdapter dispatcher = TupleSpaceAdapter.getInstance();

	/**
	 * 
	 *
	 */
	public EntityContainerImpl() {
		table = new HashMap<String, EntityListener>();
		setEntityType(EntityType.CONTAINER);
	}

	/**
	 * コンテナの名前を指定
	 * @param name
	 */
	public EntityContainerImpl(String name) {
		this();
		setName(name);
	}

	/**
	 * 名前と親ノードを指定
	 * @param name
	 * @param con
	 * @throws EntityException
	 */
	public EntityContainerImpl(String name, EntityContainer con)
			 {
		super(con);
		setName(name);

		table = new HashMap<String, EntityListener>();
		Map temp = con.getEntryTable();
		for (Object key : temp.keySet()) {
			table.put((String) key, (EntityListener) temp.get(key));
		}

		addEntry(con.getURI(), con);
		EntityEvent evt = new EntityEvent(con, this, EventType.ADD);
		dispatcher.dispatchEvent(evt);
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#addEntity(jp.ac.naka.ec.entity.Entity)
	 */
	public void addEntity(Entity e) {
		super.addChild(e);
		addEntry(e.getURI(), e);
		EntityContainer target = (getParent() != null) ? (EntityContainer) getParent()
				: this;
		if (target == null) {
			return;
		}
		EntityEvent evt = new EntityEvent(target, e, EventType.ADD);
		evt.setBroadcast(true);
		dispatcher.dispatchEvent(evt);

	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#getContainer(java.lang.String)
	 */
	public EntityContainer getContainer(String uri) {
		EntityContainer con;
		
		if (uri.equals(getURI().toString())) {
			return this;
		} else if (hasParent()) {
			con = getContainer();
			if (con.getURI().equals(uri)) {
				return con;
			}
		} else {
			//TODO 多分出来ていない
			Entity e = getEntity(uri);
			if (e instanceof EntityContainer) {
				con = (EntityContainer) e;
				return con;
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#getEntities()
	 */
	public Set<Entity> getEntities() {

		if (isLocal()) {
			Set<String> keys = table.keySet();
			Set<Entity> entities = new HashSet<Entity>();
			for (String key : keys) {
				entities.add((Entity) table.get(key));
			}
			return entities;
		} else {
			Set<TreeNode> nodes = super.getChildNodes();
			Set<Entity> entities = new HashSet<Entity>();
			for (TreeNode node : nodes) {
				entities.add((Entity) node);
			}
			return entities;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#getEntitiesByLocation(java.lang.String)
	 */
	public Set<Entity> getEntitiesByLocation(String location) {
		// TODO Auto-generated method stub
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#getEntity(java.lang.String)
	 */
	public Entity getEntity(String sip_uri) {
		Entity entity = (Entity) table.get(sip_uri);
		return entity;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveMessage(jp.ac.naka.ec.entity.EntityEvent)
	 */
	public void receiveMessage(EntityEvent e) {
		if (!isLocal()) {
			return;
		}
		switch (e.getEventType()) {
		case MESSAGE:
			procMessage(e);
			break;
		case ACK:
			break;
		case INIT:
			break;
		default:
			throw new UnsupportedOperationException(e.getEventType()
					+ " is not Supported.");
		}
	}

	/**
	 * 
	 * @param e
	 */
	private void procMessage(EntityEvent e) {
		Entity target = (Entity) e.getTarget();
		if (e.isBroadcast() || target.equals(this)) {
			// 子ノードすべてに送信
			for (TreeNode node : getChildNodes()) {
				EntityListener listener = (EntityListener) node;
				listener.receiveMessage(e);
			}
			return;
		}
		for (TreeNode node : getChildNodes()) {
			if (node instanceof Entity) {
				Entity entity = (Entity) node;
				if (entity.equals(target)) {
					entity.receiveMessage(e);
				}
			}
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveOffer(jp.ac.naka.ec.entity.EntityEvent)
	 */
	@Override
	public SessionDescription receiveOffer(EntityEvent e) {
		Entity target = (Entity) e.getTarget();
		Set<TreeNode> nodes = getChildNodes();
		SessionDescription sdp = null;
		Entity entity = null;
		// Local node宛ての場合
		for (TreeNode node : nodes) {
			entity = (Entity) node;
			if (entity.equals(target)) {
				sdp = entity.receiveOffer(e);
				break;
			}
		}
		if (sdp == null) {
			// TODO 
			return null;
		}
		Entity source = (Entity) e.getSource();
		EntityEvent evt = new EntityEvent(sdp, source, entity);
		evt.setEventType(EventType.ANSWER);
		evt.setRequest(e.getRequest());
		EventDispatcher dispatcher = TupleSpaceAdapter.getInstance();
		dispatcher.dispatchEvent(evt);
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveAnswer(jp.ac.naka.ec.entity.EntityEvent)
	 */
	@Override
	public void receiveAnswer(EntityEvent e) {
		Entity target = (Entity) e.getTarget();
		Set<TreeNode> nodes = getChildNodes();
		for (TreeNode node : nodes) {
			Entity entity = (Entity) node;
			if (entity.equals(target)) {
				entity.receiveAnswer(e);
				break;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveBye(jp.ac.naka.ec.entity.EntityEvent)
	 */
	@Override
	public void receiveBye(EntityEvent e) {
		Entity target = (Entity) e.getTarget();
		Set<TreeNode> nodes = getChildNodes();
		Entity entity = null;
		for (TreeNode node : nodes) {
			entity = (Entity) node;
			if (entity.equals(target)) {
				entity.receiveBye(e);
				break;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityImpl#addedNewEntity(jp.ac.naka.ec.entity.EntityEvent)
	 */
	public void addedNewEntity(EntityEvent evt) {
		
		// TODO プラグアンドプレイっぽくする
		if (evt.getSource().equals(this)) {
			return;
		}

		switch (evt.getEventType()) {
		case REGISTER:
			EntityContainer e = (EntityContainer) evt.getSource();
			logger.info("Add External EntityContainer :" + e.getURI());
			addEntry(e.getURI(), e);
			break;
		case ADD:
			if (evt.getSource() instanceof Entity) {
				Entity source = (Entity) evt.getSource();
				addEntry(source.getURI(), source);
				logger.info("Add Entity :" + source.getURI());
				// 保持中のEntityに通知
				informToEntities();
			}
			break;
		case NOTIFY:
		case SUBSCRIBE:
			Entity[] entities = (Entity[]) evt.getSource();
			for (Entity entity : entities) {
				if (!table.containsKey(entity.getURI().toString())
						&& !entity.equals(this)) {
					addEntry(entity.getURI(), entity);
					logger.info("Add External Entity :" + entity.getURI());
					informToEntities();
				}

			}
		}
	}

	/**
	 * 
	 */
	private void informToEntities() {
		// TODO Auto-generated method stub
		
	}

	/**
	 * Entryの保持。SIP URIをキーに。
	 * @param uri
	 * @param listener
	 */
	private void addEntry(URI uri, EntityListener listener) {
		table.put(uri.toString(), listener);
	}

	/**
	 * エントリの削除
	 * @param name
	 * @return
	 */
	private boolean removeEntry(String name) {
		return table.remove(name) != null;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.entity.EntityContainer#getEntryTable()
	 */
	public Map getEntryTable() {
		return table;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.TreeNodeImpl#setParent(jp.ac.naka.ec.TreeNode)
	 */
	@Override
	public void setParent(TreeNode node) {
		if (getParent() == null) {
			super.setParent(node);
			if (!isLocal()) {
				return;
			}
			EntityEvent evt = new EntityEvent((EntityListener) node, this,
					EntityEvent.EventType.INIT);
			dispatcher.dispatchEvent(evt);
		}
		else {
			Entity entity = (Entity) node;
			addEntry(entity.getURI(), entity);
			super.setParent(node);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.TreeNodeImpl#removeChild(jp.ac.naka.ec.TreeNode)
	 */
	@Override
	public boolean removeChild(TreeNode node) {
		if (node instanceof EntityContainer) {
			EntityContainer con = (EntityContainer) node;
			Set<Entity> entities = con.getEntities();
			for (Entity entity : entities) {
				removeEntry(entity.getURI().toString());
				con.removeChild(entity);
				entity = null;
			}
			removeEntry(con.getURI().toString());
			con = null;
		} else {
			Entity entity = (Entity) node;
			removeEntry(entity.getURI().toString());
			entity = null;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.TreeNodeImpl#include(jp.ac.naka.ec.TreeNode)
	 */
	@Override
	public boolean include(TreeNode node) {
		Entity e = (Entity) node;
		return table.containsKey(e.getURI().toString());
	}
}
