/*
 *  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.sip;

import gov.nist.javax.sip.address.SipUri;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TooManyListenersException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.EventHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;

import jp.ac.naka.ec.EventDispatcher;
import jp.ac.naka.ec.TreeNode;
import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityContainerImpl;
import jp.ac.naka.ec.entity.EntityEvent;
import jp.ac.naka.ec.entity.EntityEventDispatcher;
import jp.ac.naka.ec.entity.EntityListener;
import jp.ac.naka.ec.entity.EntityEvent.EventType;
import jp.ac.naka.ec.sip.pidf.PIDFData;
import jp.ac.naka.ec.sip.pidf.PIDFFactory;
import jp.ac.naka.ec.sip.pidf.PIDFFactoryImpl;
import net.sourceforge.jsdp.SessionDescription;

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

/**
 * SIPリクエスト、レスポンスの送信を行うクラス。NOTIFYリクエストのの自動送信もになう。
 * 
 * @author Takashi Kasuya
 * 
 */
public class SipCore {

	private SipProvider provider;

	private SipListener sip_listener;

	private MessageFactory messageFactory;

	private AddressFactory addressFactory;

	private HeaderFactory headerFactory;

	private SipURI uri;

	private boolean initialized = false;

	private EntityContainer con;

	private static SipCore instance = new SipCore();

	/*
	 * NOTIFYの間隔
	 */
	public static int DEFAULT_EXPIRES = 60;

	public static String event_package = "presence";

	private static Map<String, Dialog> dialogTable = new HashMap<String, Dialog>();

	private static final String pathname = "gov.nist";

	public static String TRANSPORT = "udp";

	private static Log logger = LogFactory.getLog(SipCore.class);

	private PIDFFactory pidfFactory;

	private EntityListener listener = new EntityEventListener();

	/**
	 * 
	 * @param name
	 *            Name of EntityContainer
	 * @param hostname
	 * @param sip_port
	 * @param sip_config
	 * @throws SipException
	 * @throws IOException
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 * @throws TooManyListenersException
	 */
	public void initialize(String name, String hostname, int sip_port,
			String sip_config) throws SipException, IOException,
			ParseException, InvalidArgumentException, TooManyListenersException {
		if (provider != null) {
			return;
		}
		SipFactory sipFactory = SipFactory.getInstance();
		sipFactory.setPathName(pathname);

		Properties prop = new Properties();
		prop.load(new FileInputStream(sip_config));
		// v1.2で非推奨
		// prop.setProperty("javax.sip.IP_ADDRESS", hostname);
		SipStack sipStack = null;
		sipStack = sipFactory.createSipStack(prop);
		addressFactory = sipFactory.createAddressFactory();

		uri = addressFactory.createSipURI(name, hostname + ":" + sip_port);

		logger.info("getHostAddress:\t" + uri.toString());

		ListeningPoint lp = sipStack.createListeningPoint(hostname, sip_port,
				TRANSPORT);
		// ListeningPoint lp1 =
		// sipStack.createListeningPoint(getPort(),"tcp");

		provider = sipStack.createSipProvider(lp);
		// sipProvider.addSipListener();
		// sipProvider = sipStack.createSipProvider(lp1);
		sip_listener = new DefaultListener();
		provider.addSipListener(sip_listener);
		messageFactory = sipFactory.createMessageFactory();
		headerFactory = sipFactory.createHeaderFactory();
		EventDispatcher dispatcher = EntityEventDispatcher.getInstance();
		dispatcher.addEntityListener(listener);
		initialized = true;
	}

	/**
	 * SipCoreの初期化。ホスト名とIPアドレス、ポートを自動設定する。
	 * 
	 * @param sip_config
	 * @throws IOException
	 * @throws TooManyListenersException
	 * @throws InvalidArgumentException
	 * @throws ParseException
	 * @throws SipException
	 */
	public void initialize(String sip_config) throws IOException, SipException,
			ParseException, InvalidArgumentException, TooManyListenersException {
		String hostname = getHostName();
		String ipAddr = getIpAddress(hostname);
		int port = 5060;
		initialize(hostname, ipAddr, port, sip_config);
	}

	/**
	 * SipCoreの初期化。ポートを手動設定する。
	 * 
	 * @param port
	 * @param sip_config
	 * @throws IOException
	 * @throws TooManyListenersException
	 * @throws InvalidArgumentException
	 * @throws ParseException
	 * @throws SipException
	 */
	public void initialize(int port, String sip_config) throws IOException,
			SipException, ParseException, InvalidArgumentException,
			TooManyListenersException {
		String hostname = getHostName();
		String ipAddr = getIpAddress(hostname);
		initialize(hostname, ipAddr, port, sip_config);
	}

	/**
	 * It retrieve an Instance of SipCore.
	 * 
	 * @return
	 */
	public static SipCore getInstance() {
		return instance;
	}

	/**
	 * 
	 * @param container
	 */
	public void setEntityContainer(EntityContainer container) {
		con = container;
		uri = (SipURI) con.getURI();
		pidfFactory = new PIDFFactoryImpl(con);
	}

	/**
	 * Send a SIP response by stateless.
	 * 
	 * @param num
	 * @param req
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 * @throws SipException
	 */
	public void sendResponse(int num, Request req) throws ParseException,
			InvalidArgumentException, SipException {
		Response res = messageFactory.createResponse(num, req);
		// ToHeader to = (ToHeader) res.getHeader(ToHeader.NAME);
		FromHeader from = (FromHeader) res.getHeader(FromHeader.NAME);
		logger.info("Sending Response (" + num + ") to " + from.getAddress());
		provider.sendResponse(res);
	}

	/**
	 * Send a SIP response. It holds a Dialog.
	 * 
	 * @param num
	 * @param evt
	 * @param sender_uri
	 * @throws ParseException
	 * @throws SipException
	 * @throws InvalidArgumentException
	 */
	public void sendResponse(int num, RequestEvent evt, String sender_uri)
			throws ParseException, SipException, InvalidArgumentException {
		if (!initialized) {
			return;
		}
		Request req = evt.getRequest();
		Response res = messageFactory.createResponse(num, req);
		// Expires header is mandatory in 2xx response of SUBSCRIBE
		if (req.getMethod().equals(Request.SUBSCRIBE)) {
			prepareResponse(res);
			ExpiresHeader expiresHeader = req.getExpires();
			int expires = expiresHeader.getExpires() - 1;
			res.setHeader(headerFactory.createExpiresHeader(expires));
		}

		ServerTransaction st = evt.getServerTransaction();
		if (st == null) {
			st = provider.getNewServerTransaction(req);
		}
		logger
				.info("Sending " + req.getMethod() + " Response to "
						+ sender_uri);
		st.sendResponse(res);

		// Dialogの保持
		if (req.getMethod().equals(Request.SUBSCRIBE)
				|| req.getMethod().equals(Request.NOTIFY)) {
			Dialog dialog = st.getDialog();
			dialogTable.put(sender_uri, dialog);
		} else if (num == Response.TRYING) {
			Dialog dialog = st.getDialog();
			dialogTable.put(sender_uri, dialog);
			dialog.setApplicationData(st);
		}
	}

	/**
	 * 
	 * @param res
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	private void prepareResponse(Response res) throws ParseException,
			InvalidArgumentException {
		ToHeader to = (ToHeader) res.getHeader(ToHeader.NAME);
		// TODO
		Random rand = new Random();
		to.setTag(rand.nextInt(999999) + "");
		// ContactHeader
		Address addr = addressFactory.createAddress(uri);
		ContactHeader contact = headerFactory.createContactHeader(addr);
		res.setHeader(contact);
	}

	/**
	 * Send a SIP Message by a specified method. It sends by stateless.
	 * 
	 * @param target
	 * @param method
	 * @throws SipException
	 */
	void sendRequest(Entity target, String method) throws SipException {
		Request req = createRequest(target, con, method);
		if (method.equals(Request.REGISTER)) {
			try {
				// REGISTERに必要なヘッダ： Expires, Contact
				ExpiresHeader expires = headerFactory
						.createExpiresHeader(DEFAULT_EXPIRES);
				req.setExpires(expires);
				Address contact_addr = addressFactory.createAddress(con
						.getURI());
				contact_addr.setDisplayName(con.getName());
				ContactHeader contact = headerFactory
						.createContactHeader(contact_addr);
				req.setHeader(contact);
			} catch (Exception e) {
				throw new SipException(e.getMessage());
			}
		} else if (method.equals(Request.SUBSCRIBE)) {
			// PIDF
			PIDFFactory pidf_fac = new PIDFFactoryImpl(con);
			PIDFData data = pidf_fac.createPIDF();
			try {
				req.setContent(data.toString(), headerFactory
						.createContentTypeHeader(PIDFData.CONTENT_TYPE,
								PIDFData.CONTENT_SUBTYPE));
				// Expires
				ExpiresHeader expires = headerFactory
						.createExpiresHeader(DEFAULT_EXPIRES);
				req.setExpires(expires);
				// EventHeader
				EventHeader event = headerFactory
						.createEventHeader(event_package);
				req.setHeader(event);
				Address contact_addr = addressFactory.createAddress(con
						.getURI());
				contact_addr.setDisplayName(con.getName());
				ContactHeader contact = headerFactory
						.createContactHeader(contact_addr);
				req.setHeader(contact);
			} catch (Exception e) {
				throw new SipException(e.getMessage());
			}
		}
		ClientTransaction ct = provider.getNewClientTransaction(req);
		ct.sendRequest();
		logger.info("Sending " + method + " to " + target.getURI());
		Dialog dialog = ct.getDialog();
		if (dialog != null)
			dialogTable.put(target.getURI().toString(), dialog);
	}

	/**
	 * 
	 * @param message
	 * @param target
	 * @param source
	 * @param method
	 * @throws SipException
	 * @throws ParseException
	 */
	void sendRequest(String message, Entity target, Entity source, String method)
			throws SipException, ParseException {

		String name = target.getURI().toString();

		if (method.equals(Request.INVITE)) {
			Request req = createRequest(target, source, method);

			Address contact_addr = addressFactory
					.createAddress(source.getURI());
			ContactHeader contact = headerFactory
					.createContactHeader(contact_addr);
			req.setHeader(contact);
			// SDP body
			req.setContent(message, headerFactory.createContentTypeHeader(
					"application", "sdp"));
			ClientTransaction ct = provider.getNewClientTransaction(req);
			ct.sendRequest();
			dialogTable.put(name, ct.getDialog());
		} else if (method.equals(Request.MESSAGE)) {
			Request req;

			req = createRequest(target, source, method);
			FromHeader from = (FromHeader) req.getHeader(FromHeader.NAME);
			Address addr = addressFactory.createAddress(source.getURI());
			addr.setDisplayName(source.getFullyQualifiedName());
			from.setAddress(addr);
			req.setContent(message, headerFactory.createContentTypeHeader(
					"text", "plain"));
			provider.sendRequest(req);
		} else {
			// BYE, NOTIFYの場合
			Dialog dialog = dialogTable.get(name);
			Request req = dialog.createRequest(method);
			ClientTransaction ct = provider.getNewClientTransaction(req);
			dialog.sendRequest(ct);
		}

	}

	Random rand = new Random();

	/**
	 * 
	 * @param target
	 * @param source
	 * @param method
	 * @return
	 * @throws SipException
	 */
	private Request createRequest(EntityListener target, Entity source, String method)
			throws SipException {
		Address toAddress = addressFactory.createAddress(target.getURI());

		Request req;
		String branch = null;
		String tag = "" + rand.nextInt(100000000);
		try {
			ToHeader to = headerFactory.createToHeader(toAddress, null);
			Address fromAddress = addressFactory.createAddress(source.getURI());
			fromAddress.setDisplayName(source.getName());
			FromHeader from = headerFactory.createFromHeader(fromAddress, tag);

			// ToHeader to = headerFactory.createToHeader(toAddress, null);
			CSeqHeader cSeq = headerFactory.createCSeqHeader(1L, method);
			CallIdHeader callId = provider.getNewCallId();
			MaxForwardsHeader maxForwards = headerFactory
					.createMaxForwardsHeader(70);
			ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
			String ipAddr = provider.getListeningPoint(TRANSPORT)
					.getIPAddress();

			int port = provider.getListeningPoint(TRANSPORT).getPort();
			ViaHeader viaHeader = headerFactory.createViaHeader(ipAddr, port,
					TRANSPORT, branch);
			viaHeaders.add(viaHeader);

			req = messageFactory.createRequest(to.getAddress().getURI(),
					method, callId, cSeq, from, to, viaHeaders, maxForwards);

		} catch (Exception e) {
			e.printStackTrace();
			throw new SipException(e.getMessage());
		}

		return req;
	}

	NotifyTask task;

	/**
	 * 
	 * @param sender_name
	 * @param dialog
	 * @throws ParseException
	 */
	void startNotify(String sender_name, Dialog dialog) throws ParseException {

		logger.info("Start NotifyTask to:" + sender_name);
		task = new NotifyTask(dialog);
		task.start();

	}

	/**
	 * Expiresの更新
	 * 
	 */
	public void refresh() {
		if (task != null) {
			logger.info("Refresh expires.");
			task.refresh();
		}
	}

	private class NotifyTask extends TimerTask {

		private final static String eventPackage = "presence";

		private PIDFData temp = null;

		private Dialog dialog;

		private boolean alive = false;

		private Timer timer;

		private Address fromAddress;

		public NotifyTask(Dialog dialog) throws ParseException {
			this.dialog = dialog;
			this.alive = true;
			fromAddress = addressFactory.createAddress(con.getURI());
			fromAddress.setDisplayName(con.getName());
		}

		public void start() {
			timer = new Timer(true);
			timer.schedule(this, 1000, DEFAULT_EXPIRES * 1000);
		}

		@Override
		public void run() {
			try {
				sendNotify();
			} catch (Exception e) {
				logger.warn("Error while sending NOTIFY MESSAGE", e);
			}
		}

		public void sendNotify() throws SipException, ParseException {

			Request notify = dialog.createRequest(Request.NOTIFY);

			// expires
			SubscriptionStateHeader state = headerFactory
					.createSubscriptionStateHeader(SubscriptionStateHeader.ACTIVE);
			EventHeader event = headerFactory.createEventHeader(eventPackage);

			// Fromの書き換え
			// FromHeader from = (FromHeader) notify.getHeader(FromHeader.NAME);
			// from.setAddress(fromAddress);
			PIDFData pidf = pidfFactory.createPIDF();
			if (temp == null || !temp.equals(pidf)) {
				ContentTypeHeader contentType = headerFactory
						.createContentTypeHeader(PIDFData.CONTENT_TYPE,
								PIDFData.CONTENT_SUBTYPE);
				// System.out.println(pidf);
				notify.addHeader(state);
				notify.addHeader(event);
				notify.setContent(pidf.toString(), contentType);
			} else {
				// nop
			}

			ClientTransaction ct = provider.getNewClientTransaction(notify);

			if (alive) {
				logger.info("Sending NOTIFY to " + dialog.getRemoteTarget());
				dialog.sendRequest(ct);
				alive = false;
			} else {
				logger.info("Expires out, remove Parent");
				timer.cancel();
				// Parent削除
				EntityEvent evt = new EntityEvent((EntityContainer) con
						.getParent());
				evt.setEventType(EventType.REMOVE);
				EventDispatcher dispatcher = EntityEventDispatcher
						.getInstance();
				dialog.delete();
				dispatcher.dispatchEvent(evt);
			}
		}

		public void refresh() {
			this.alive = true;
		}
	}

	/**
	 * SIP URIの作成
	 * 
	 * @param temp
	 * @return
	 */
	public SipURI createURI(String uri_str) {
		SipURI uri;
		try {
			if (addressFactory != null) {
				URI temp = addressFactory.createURI(uri_str);
				if (temp.isSipURI()) {
					uri = (SipURI) temp;
				} else {
					throw new IllegalArgumentException(temp.toString()
							+ " is not valid SIP URI.");
				}
			} else {
				SipUri s_uri = new SipUri();
				java.util.StringTokenizer stToken = new java.util.StringTokenizer(
						uri_str, "@:");
				String scheme = stToken.nextToken();
				if (!scheme.equals("sip") && !scheme.equals("pres")) {
					throw new IllegalArgumentException("Bad uri scheme :"
							+ scheme);
				}
				String user = stToken.nextToken();
				String host = stToken.nextToken();
				int port = Integer.parseInt(stToken.nextToken());
				s_uri.setUser(user);
				s_uri.setHost(host);
				s_uri.setPort(port);
				uri = s_uri;
			}
		} catch (ParseException e) {
			return null;
		}
		return uri;
	}

	private class EntityEventListener implements EntityListener {
		ExecutorService ex = Executors.newCachedThreadPool();

		public void receiveMessage(EntityEvent e) {
			EntityListener target = e.getTarget();
			switch (e.getEventType()) {
			case INIT:
				// REGISTER送信
				try {
					instance.sendRequest((Entity) e.getTarget(),
							Request.REGISTER);
				} catch (SipException se) {
					logger.error("Error while sending REGISTER to "
							+ target.toString(), se);
				}
				break;
			case MESSAGE:
				if (e.isBroadcast()) {
					Set<Entity> entities = con.getEntities();
					for (Iterator<Entity> iter = entities.iterator(); iter
							.hasNext();) {
						target = iter.next();
						if (!target.isLocal()
								&& target instanceof EntityContainer) {
							try {
								Request req = instance.createRequest(target,
										con, Request.MESSAGE);
								req.setContent(e.getMessage(), headerFactory
										.createContentTypeHeader("text",
												"plain"));
								provider.sendRequest(req);
							} catch (Exception e1) {
								logger.warn("Error while sending MESSAGE to "
										+ target, e1);
								// TODO エラー時にそのノードを削除
								// con.removeChild(target);
							}
							logger.info("Send MESSAGE to " + target.getURI());
						}
					}
				} else {
					Entity source = (Entity) e.getSource();
					Set<TreeNode> set = con.getChildNodes();

					if (!source.isLocal() || target.isLocal()) {
						return;
					}
					if (!set.contains(target) && con.include((TreeNode)target)
							&& !target.equals(con)) {
						try {
							Request req = instance.createRequest(target, con,
									Request.MESSAGE);
							req.setContent(e.getMessage(), headerFactory
									.createContentTypeHeader("text", "plain"));
							provider.sendRequest(req);
						} catch (Exception e1) {
							logger.warn("Error while sending MESSAGE to "
									+ target, e1);
							// con.removeChild(target);
						}
						logger.info("Send MESSAGE to " + target.getURI());
					} else {
						logger.info("No External Entry :" + target.getURI());
					}
				}
			}
		}

		public SessionDescription receiveOffer(EntityEvent evt) {
			Entity target = (Entity) evt.getTarget();
			if (target.isLocal()) {
				return null;
			}
			Entity source = (Entity) evt.getSource();
			try {
				instance.sendRequest(evt.getSessionDescription().toString(),
						target, source, Request.INVITE);
			} catch (Exception e) {
				logger.warn("Error while sending INVITE to " + target, e);
			}
			return null;
		}

		public void receiveBye(EntityEvent evt) {
			Entity target = (Entity) evt.getTarget();
			if (target.isLocal()) {
				return;
			}
			Entity source = (Entity) evt.getSource();
			try {
				instance.sendRequest("", target, source, Request.BYE);
			} catch (Exception e) {
				logger.warn("Error while sending BYE to " + target, e);
			}

		}

		public void receiveAnswer(EntityEvent e) {
			Entity source = (Entity) e.getSource();
			Request req = e.getRequest();
			Response res = null;
			if (req == null) {
				return;
			}
			try {
				res = messageFactory.createResponse(Response.OK, req);

				if (e.getSessionDescription() != null) {
					ContentTypeHeader type = headerFactory
							.createContentTypeHeader("application", "sdp");
					res.setContent(e.getSessionDescription().toString(), type);
				}

				// ContactHeader
				Address contact_addr = addressFactory.createAddress(source
						.getURI());
				contact_addr.setDisplayName(source.getName());
				ContactHeader contact = headerFactory
						.createContactHeader(contact_addr);
				res.setHeader(contact);

				Entity target = (Entity) e.getTarget();
				String uri = target.getURI().toString();
				Dialog dialog = dialogTable.get(uri);
				ServerTransaction st = (ServerTransaction) dialog
						.getApplicationData();
				st.sendResponse(res);

				logger.info("Sending Response 200(OK) to " + uri);

			} catch (Exception e1) {
				logger.warn("Error while creating Response", e1);
				return;
			}
		}

		public void addedNewEntity(EntityEvent e) {
			// REGISTER
			switch (e.getEventType()) {
			case REGISTER:
				// SUBSCRIBE送信
				Entity target = (Entity) e.getSource();
				try {
					sendRequest(target, Request.SUBSCRIBE);
				} catch (SipException e1) {
					logger.warn("Error while sending SUBSCRIBE to "
							+ target.getURI(), e1);
				}
				break;
			case SUBSCRIBE:
				// NOTIFY送信
				String target_uri = e.getFrom().toString();
				Dialog dialog = dialogTable.get(target_uri);
				try {
					startNotify(target_uri, dialog);
				} catch (ParseException e2) {
					logger.warn("Error while sending NOTIFY to " + target_uri,
							e2);
				}
				break;
			case NOTIFY:
				target_uri = e.getFrom().toString();

				EntityListener temp = con.getEntity(target_uri);
				if (temp == null) {
					logger.warn("Already expires out :" + target_uri);
					return;
				}

				EntityContainerImpl target_container = (EntityContainerImpl) temp;
				if (target_container.isRunning()) {
					target_container.refresh();
					logger.info("Refresh timer :" + target_container);
					break;
				} else {
					
					ex.execute(target_container);
					logger.info("Running timer :" + target_container);
				}
				
			case ADD:
				if (e.getSource() instanceof Entity) {
					return;
				}
				target_uri = e.getFrom().toString();

				// 本当は差分だけが良い
				// String pidf = instance.pidfFactory.createPIDF().toString();
				String pidf = e.getMessage();

				Set<String> set = dialogTable.keySet();
				for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
					String uri = iter.next();
					// ループ防止
					if (uri.equals(target_uri)) {
						continue;
					}
					dialog = dialogTable.get(uri);
					try {
						Request req = dialog.createRequest(Request.MESSAGE);
						ClientTransaction ct = provider
								.getNewClientTransaction(req);
						req.setContent(pidf, headerFactory
								.createContentTypeHeader(PIDFData.CONTENT_TYPE,
										PIDFData.CONTENT_SUBTYPE));
						dialog.sendRequest(ct);
						logger.info("Forwarding PIDF to :" + uri);
					} catch (Exception e1) {
						logger.error("Error while sending MESSAGE message to "
								+ uri, e1);
					}
				}
				break;

			default:
				logger.warn("Invalid Operation");
			}
		}

		public void receiveAck(EntityEvent e) {
			Entity target = (Entity) e.getTarget();
			Dialog dialog = dialogTable.get(target.getURI().toString());
			Request ack;
			try {
				ack = dialog.createAck(dialog.getLocalSeqNumber());
				logger.info("Send ACK to " + dialog.getRemoteTarget());
				dialog.sendAck(ack);
			} catch (Exception e1) {
				logger.warn("Error while sending ACK", e1);
			}
		}

		public void removedEntity(EntityEvent evt) {
			EntityContainer con = (EntityContainer) evt.getSource();
			PIDFFactory fac = new PIDFFactoryImpl(con);
			PIDFData data = fac.createPIDF();
			String key = con.getURI().toString();
			// 親との通信が切れたとき
			if (con == instance.con.getParent()) {
				instance.task = null;
				dialogTable.remove(key);
			} else {
				// 子ノードとの通信が切れたとき
				Dialog dialog = dialogTable.get(key);
				if (dialog != null)
					dialog.delete();
				dialogTable.remove(key);
			} 
			Set<String> set = dialogTable.keySet();
			for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
				String uri = iter.next();
				Dialog dialog = dialogTable.get(uri);
				try {
					Request req = dialog.createRequest(Request.MESSAGE);
					ClientTransaction ct = provider
							.getNewClientTransaction(req);
					req.setContent(data, headerFactory
							.createContentTypeHeader(PIDFData.CONTENT_TYPE,
									PIDFData.CONTENT_SUBTYPE));
					dialog.sendRequest(ct);
					logger.info("Sending PIDF to :" + uri);
				} catch (Exception e1) {
					logger.error("Error while sending MESSAGE message to "
							+ uri, e1);
				}
			}
		}

		public SipURI getURI() {
			return null;
		}

		public boolean isLocal() {
			return false;
		}

		public void setURI(SipURI uri) {
		}
	}

	private String getHostName() throws IOException {
		String name = "";
		java.util.Enumeration enuIfs = NetworkInterface.getNetworkInterfaces();
		if (null != enuIfs) {
			while (enuIfs.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface) enuIfs.nextElement();
				if (ni.getName().equals("lo")) {
					continue;
				}
				java.util.Enumeration enuAddrs = ni.getInetAddresses();
				while (enuAddrs.hasMoreElements()) {
					InetAddress in4 = (InetAddress) enuAddrs.nextElement();
					if (in4 instanceof Inet4Address) {
						name = in4.getHostName();
						break;
					}
				}
			}
		}
		return name;
	}

	private String getIpAddress(String name) throws IOException {
		String address = "";

		java.util.Enumeration enuIfs = NetworkInterface.getNetworkInterfaces();
		if (null != enuIfs) {
			while (enuIfs.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface) enuIfs.nextElement();
				java.util.Enumeration enuAddrs = ni.getInetAddresses();
				boolean flag = false;
				while (enuAddrs.hasMoreElements()) {
					InetAddress in4 = (InetAddress) enuAddrs.nextElement();
					// localhost
					if (ni.getName().equals("lo")) {
						continue;
					}
					if (in4 instanceof Inet4Address) {
						address = in4.getHostAddress();
						flag = true;

						break;
					}
					if (flag)
						break;
				}
				if (flag)
					break;
			}
		}
		return address;
	}

	/**
	 * @return the uri
	 */
	public SipURI getURI() {
		return uri;
	}

}
