/*
 *  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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.sip.address.SipURI;
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 jp.ac.naka.ec.sip.SipCore;
import net.sourceforge.jsdp.SessionDescription;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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

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

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

	/**
	 * コンテナのSIP URIを指定。
	 * 
	 * @param uri
	 */
	public EntityContainerImpl(SipURI uri) {
		this();
		setURI(uri);
	}

	/**
	 * 名前と親ノードを指定
	 * 
	 * @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 INIT:
			break;
		default:
			throw new UnsupportedOperationException(e.getEventType()
					+ " is not Supported.");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveAck(jp.ac.naka.ec.entity.EntityEvent)
	 */
	public void receiveAck(EntityEvent evt) {
		// TODO NOP
	}

	/**
	 * 
	 * @param e
	 */
	private void procMessage(EntityEvent e) {
		EntityListener target = 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();
			// この時点で重複がある場合は削除
			if (this.include(e)) {
				Set<String> set = table.keySet();
				ArrayList<Entity> removeNodes = new ArrayList<Entity>();
				SipURI parent_uri = (SipURI) e.getURI();
				for (String str : set) {
					Entity node = (Entity) table.get(str);
					SipURI uri = (SipURI) node.getURI();
					// 含まれる子ノードを検索
					if (uri.getHost().equals(parent_uri.getHost())
							&& uri.getPort() == parent_uri.getPort()) {
						removeNodes.add(node);
					}
				}
				
				
				EntityEvent ee = new EntityEvent(e);
				
				ee.setEventType(EventType.REMOVE);
				informToEntities(ee);
				for (Entity node : removeNodes) {
					logger.info("Remove Entity:" + node.getURI());
					table.remove(node.getURI().toString());
					node = null;
				}
			
				logger.info("Remove EntityContainer:" + parent_uri);
				
				table.remove(parent_uri.toString());
			}
			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(evt);
				break;
			}
		case NOTIFY:
		case SUBSCRIBE:
			Entity[] entities = (Entity[]) evt.getSource();
			List<Entity> removed = new ArrayList<Entity>();
			for (Entity entity : entities) {
				if (!table.containsKey(entity.getURI().toString())
						&& !entity.equals(this) && entity.isAlive()) {
					addEntry(entity.getURI(), entity);
					logger.info("Add External Entity :" + entity.getURI());
					informToEntities(evt);
				}
				// 削除対象の場合
				else if (table.containsKey(entity.getURI().toString())
						&& !entity.isAlive()) {
					removed.add(entity);
					logger.info("Remove Entity:" + entity.getURI());
					table.remove(entity.getURI().toString());
				}
			}

			if (removed.size() != 0) {
				Entity[] temp2 = new Entity[removed.size()];
				EntityEvent evt2 = new EntityEvent(removed.toArray(temp2));
				evt.setEventType(EventType.REMOVE);
				informToEntities(evt2);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityListener#removedEntity(jp.ac.naka.ec.entity.EntityEvent)
	 */
	public void removedEntity(EntityEvent evt) {

		if (evt.getSource() instanceof EntityContainer) {
			EntityContainer con = (EntityContainer) evt.getSource();
			Set<String> set = table.keySet();
			ArrayList<Entity> removeNodes = new ArrayList<Entity>();
			SipURI parent_uri = (SipURI) con.getURI();
			for (String str : set) {
				Entity node = (Entity) table.get(str);
				SipURI uri = (SipURI) node.getURI();
				// 含まれる子ノードを検索
				if (uri.getHost().equals(parent_uri.getHost())
						&& uri.getPort() == parent_uri.getPort()) {
					removeNodes.add((Entity) node);
					con.addChild(node);
				}
			}
			EntityEvent ee = new EntityEvent(con);
			ee.setEventType(EventType.REMOVE);
			informToEntities(ee);
			for (Entity node : removeNodes) {
				logger.info("Remove Entity:" + node.getURI());
				table.remove(node.getURI().toString());
			}
			logger.info("Remove EntityContainer:" + parent_uri);
			table.remove(parent_uri.toString());

		} else {
			throw new UnsupportedOperationException("Not implemented");
		}
	}

	/**
	 * 
	 */
	private void informToEntities(EntityEvent evt) {
		for (TreeNode child : getChildNodes()) {
			Entity entity = (Entity) child;
			switch (evt.getEventType()) {
			case NOTIFY:
			case SUBSCRIBE:
			case ADD:
				entity.addedNewEntity(evt);
				break;
			case REMOVE:
				entity.removedEntity(evt);
				break;
			default:
				throw new UnsupportedOperationException("Invalid Operation :"
						+ evt.getEventType());
			}

		}
	}

	/**
	 * 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());
	}

	public int expires = SipCore.DEFAULT_EXPIRES + 10;
	private boolean running = false;

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		alive = true;
		running = true;
		while (alive) {
			alive = false;
			try {
				Thread.sleep(expires * 1000);
			} catch (InterruptedException e) {
				logger.info("Interrupt :" + getURI());
				break;
			}
		}
		running = false;
		logger.info("Expires out :" + getURI());
		// TODO
		EntityEvent evt = new EntityEvent(this);
		evt.setEventType(EventType.REMOVE);
		dispatcher.dispatchEvent(evt);
	}

	public boolean isRunning() {
		return running;
	}

	/**
	 * 
	 * 
	 */
	public void refresh() {
		alive = true;
	}
}
