/*
 *  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.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.sdp.SessionDescription;
import javax.sip.SipException;
import javax.sip.address.SipURI;
import javax.sip.message.Request;

import jp.ac.naka.ec.EventDispatcher;
import jp.ac.naka.ec.entity.EntityEvent.EventType;
import jp.ac.naka.ec.sip.InviteCallback;
import jp.ac.naka.ec.sip.SipCore;

/**
 * Entity実装のためのヘルパークラス。一番最初にinitが呼ばれることに注意。
 * 抽象メソッドであるreceiveMessageも実装する必要あり。ここは後に変える予定。
 * 
 * @author Takashi Kasuya
 */
public abstract class AbstractEntity extends EntityImpl {

	private static EventDispatcher dispatcher = EntityEventDispatcher
			.getInstance();

	private static SipCore core;

	private static int poolSize = 3;
	// Notify頻度 sec
	private long TTL = 60;
	private ArrayList<Entity> subscribers = new ArrayList<Entity>();
	private Map<String, Notifier> notifiers = new HashMap<String, Notifier>();
	private ArrayList<EntityListener> listeners = new ArrayList<EntityListener>();
	private ExecutorService ex = Executors.newFixedThreadPool(poolSize);
	private InviteCallback callback;

	/**
	 * 
	 * 
	 */
	public AbstractEntity() {
		super();
		core = SipCore.getInstance();
		addEntityListener(this);
		// 必ず最初に呼ばれる
		init();
	}

	public AbstractEntity(SipURI uri) {
		super(uri);
		core = SipCore.getInstance();
		addEntityListener(this);
		init();
	}

	/**
	 * ユーザ実装Entityがインスタンス化された後に呼ばれるメソッド。これを使うとバグが出るポイ。
	 * 
	 */
	public void init()
	{}

	final public void handleEvent(EntityEvent e) {
		for (EntityListener listener : listeners) {
			switch (e.getEventType()) {
			case MESSAGE:
				listener.receiveMessage(e);
				break;
			case NOTIFY:
				// Notifierの情報の更新
				listener.receiveNotify(e);
				break;
			case SUBSCRIBE:
				listener.receiveSubscribe(e);
				break;
			case OFFER:
				SessionDescription sdp = listener.receiveOffer(e);
				if (callback != null) {
					callback.receiveAnswer(sdp, e);
				}
				break;
			case ANSWER:
				listener.receiveAnswer(e);
				break;
			case BYE:
				listener.receiveBye(e);
				break;
			case REMOVE:
				
				listener.expired(e);
				break;
			case ERROR:
				listener.receiveErrorResponse(e);
			}
		}
	}

	final public void addEntityListener(EntityListener listener) {
		listeners.add(listener);
	}

	final public boolean removeEntityListener(EntityListener listener) {
		return listeners.remove(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityImpl#receiveMessage(jp.ac.naka.ec.entity.EntityEvent)
	 */
	public abstract void receiveMessage(EntityEvent e);

	@Override
	final public boolean sendOffer(Entity target) {
		return sendOffer(null, target);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityImpl#sendOffer(net.sourceforge.jsdp.SessionDescription,
	 *      jp.ac.naka.ec.entity.Entity)
	 */
	@Override
	final public boolean sendOffer(SessionDescription sdp, final Entity target) {
		if (!this.isLocal()) {
			return false;
		}
		if (target.isLocal()) {
		//	final Entity source = this;
			EntityEvent e = new EntityEvent(sdp, target, this);
			if (target instanceof AbstractEntity) {
				InviteCallback callback = new InviteCallback() {
					public void receiveAnswer(SessionDescription sdp,
							EntityEvent evt) {
						EntityEvent e = null;
						if (sdp != null)
							e = new EntityEvent(sdp, target);
						else  {
							throw new NullPointerException("Response Sdp is null");
							/*
							e = new EntityEvent(target, source, EventType.ERROR);
							e.setStatusCode (500);
							e.setMessage("Sdp is null");
							*/
						}
						handleEvent(e);
					}
				};
				((AbstractEntity) target).setCallback(callback);
				((AbstractEntity) target).handleEvent(e);
			} else {
				SessionDescription ret = target.receiveOffer(e);
				EntityEvent evt = new EntityEvent(ret, target);
				handleEvent(evt);
			}
			return true;
		}
		try {
			core.sendInvite(sdp, target, this);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityImpl#sendMessage(java.lang.String)
	 */
	final public boolean sendMessage(String msg) {
		EntityContainer target = getContainer();
		EntityEvent e = new EntityEvent(msg, target, this);
		// subscribeされているもの、ローカルノードにのみ配信
		for (Entity s : subscribers) {
			try {
				//String pidf = cretePIDF(msg, this);
				core.sendRequest(msg, s, this, Request.MESSAGE);
			} catch (Exception e1) {
				e1.printStackTrace();
				return false;
			}
		}
		dispatcher.dispatchEvent(e);
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.ac.naka.ec.entity.EntityImpl#sendMessage(java.lang.String,
	 *      jp.ac.naka.ec.entity.EntityListener)
	 */
	final public boolean sendMessage(String msg, Entity target) {
		if (target.isLocal()) {
			EntityEvent e = new EntityEvent(msg, target, this);
			target.receiveMessage(e);
		} else {
			try {
				core.sendRequest(msg, target, this, Request.MESSAGE);
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}
		return true;
	}

	final public boolean sendBye(Entity target) {
		if (!this.isLocal()) {
			return false;
		}
		EntityEvent evt = new EntityEvent(target, this, EventType.BYE);
		if (target.isLocal()) {
			if (target instanceof AbstractEntity)
				((AbstractEntity)target).handleEvent(evt);
			else
				target.receiveBye(evt);
		}else {
			try {
				//core.sendRequest(target, this, Request.BYE);
				core.sendBye(target);
			} catch (SipException e) {
				e.printStackTrace();
				return false;
			}
		}
		
		return true;
	}

	final public boolean subscribe(final Entity target) {
		// Defaultは６０秒
		return subscribe(target, 60);
	}
	
	final public boolean subscribe(final Entity target, int expires) {
		if (!this.isLocal() ) {
			return false;
		} else if (target.isLocal()) {
			// TODO
			EntityEvent evt = new EntityEvent(target, this, EventType.SUBSCRIBE);
			target.receiveSubscribe(evt);
		} else {
			try {
				// core.sendRequest(target, this, Request.SUBSCRIBE);
				Notifier notifier = new Notifier(target, this, expires);
				ex.execute(notifier);
				notifiers.put(target.getURI().toString(), notifier);
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}
		return true;
	}

	/**
	 * Subscriptionの解除
	 */
	final public boolean unsubscribe(Entity target) {
		Notifier notifier = notifiers.get(target.getURI().toString());
		if (notifier == null)
			return false;
		try {
			notifier.sendUnsubscribe();
		} catch (SipException e) {
			e.printStackTrace();
			return false;
		}
		notifiers.remove(target.getURI().toString());
		notifier = null;
		return true;
	}

	final public void addSubscriber(Entity entity) {
		subscribers.add(entity);
	}

	final public void removeSubscriber(Entity entity) {
		// System.out.println("expired out " + entity.getName());
		subscribers.remove(entity);
	}

	

	

	final class Notifier extends EntityImpl implements Runnable {

		private Entity target, source;
		private boolean run = true;
		int expires = 60;
		
		public Notifier(Entity target, Entity source) {
			super(target.getURI());
			this.target = target;
			this.source = source;
		}
		
		public Notifier(Entity target, Entity source, int expires) {
			this(target, source);
			this.expires = expires;
		}

		public void run() {
			// 定期的にSUBSCRIBEする
			try {
				while (run) {
					core.sendSubscribe(target, source, expires);
					Thread.sleep(expires * 1000 - 1000);
				}
			} catch (SipException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
			}
		}

		void sendUnsubscribe() throws SipException {
			this.run = false;
			core.sendSubscribe(target, source, 0);
		}
	}

	public void setCallback(InviteCallback callback) {
		this.callback = callback;
	}

	public InviteCallback getCallback() {
		return callback;
	}

	
	public void startNotify(final Entity target) {
		final Entity source = this;
		Runnable th = new Runnable() {
			
			public void run() {
				
				do  {
					System.out.println("notify to " + target);
					//String pidf =createPIDF();
					EntityInformation info = new EntityInformation(source);
					try {
						core.sendRequest(info.toXML(), target, source, Request.NOTIFY);
						Thread.sleep(TTL * 1000);
						
					} catch (Exception e) {
						e.printStackTrace();
						break;
					}
					
				}while(subscribers.contains(target));
			//	System.out.println("expired");
			}
		};
		ex.execute(th);
	}

	final public long getTTL() {
		return TTL;
	}

	final public void setTTL(long ttl) {
		TTL = ttl;
	}
	
	
	
}
