package jp.ac.naka.ec.entity;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.ParseException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.sdp.SessionDescription;
import javax.sip.address.SipURI;

import jp.ac.naka.ec.EventDispatcher;
import jp.ac.naka.ec.dht.Chord;
import jp.ac.naka.ec.sip.SipCore;

public class EntityContainerImpl extends EntityImpl implements EntityContainer {

	Map<String, Entity> entities = new HashMap<String, Entity>();
	private static EntityContainerImpl instance;

	static {
		try {
			instance = new EntityContainerImpl();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
	}

	private EntityContainerImpl() throws IOException, ParseException {
		String hostname = getHostName();
		String ipAddr = getIpAddress(hostname);
		SipURI uri = createSipURI("sip:" + hostname + "@" + ipAddr + ":"
				+ SipCore.PORT);
		setURI(uri);
		EventDispatcher dispatcher = EntityEventDispatcher.getInstance();
		dispatcher.addEntityListener(this);
	}

	public static EntityContainerImpl getInstance() {
		return instance;
	}
	
	/**
	 * 指定されたネットワークインタフェースのIPアドレスを取得
	 * @param ni
	 * @return
	 * @throws IOException 
	 * @throws ParseException 
	 */
	public static EntityContainerImpl getInstance(String ni) throws IOException, ParseException {
		// 指定されたNIのIPアドレスを取得
		String ipAddr = getIpAddressOf(ni);
		SipURI uri = createSipURI("sip:" + ni + "@" + ipAddr + ":"
				+ SipCore.PORT);
		instance.setURI(uri);
		return instance;
	}

	public void addEntity(Entity e) {
		e.setEntityContainer(this);
		String host = getURI().getHost();
		try {
			String name = e.getName();
			if (name == null) {
				name = e.getClass().getName();
				SipURI uri = createSipURI("sip:" + name + "@" + host + ":"
						+ SipCore.PORT);
				e.setURI(uri);
			} else if (e.getURI() == null) {
				SipURI uri = createSipURI("sip:" + name + "@" + host + ":"
						+ SipCore.PORT);
				e.setURI(uri);
			}
		} catch (ParseException e1) {
			e1.printStackTrace();
		}
		if (getKeywords() != null || getKeywords().length != 0) {
			for (String keyword : getKeywords())
				e.addKeyword(keyword);
		}
		String str = checkURI(e.getURI().toString());
		entities.put(str, e);
	}
	
	

	private String checkURI(String string) {
		int index = string.indexOf(";");
		if (index == -1)
			return string;
		String sub = string.substring(0,index);
		return sub;
	}

	public void removeEntity(Entity entity){
		if (entities.containsValue(entity)) {
			entities.remove(entity.getURI().toString());
		}
	}
	public Entity[] getEntities() {
		Entity[] ee = new Entity[entities.size()];
		int num = 0;
		Set<String> set = entities.keySet();
		for (String key : set) {
			ee[num++] = entities.get(key);
		}
		return ee;
	}

	public Entity getEntity(String uri) {
		Entity entity = entities.get(uri);
		return entity;
	}

	private static Chord chord;

	public Entity[] searchEntities(String keyword)  {
		if (chord == null)
			chord = Chord.getInstance();
		if (!chord.isInitiated()) {
			return new Entity[0];
		}
		Entity[] external = chord.getEntities(keyword);
		for (int i = 0; i < external.length; i++) {
			Entity entity = external[i];
			Entity temp = entities.get(entity.getURI().toString());
			if (temp != null)
				external[i] = temp;
		}
		return external;
	}

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

	/**
	 * 全てのノードに配信
	 * @param e
	 */
	private void procMessage(EntityEvent e) {
		Collection<Entity> list = entities.values();
		Entity source = (Entity)e.getSource();
		for (Entity entity:list) {
			if (source.equals(entity)) {
				continue;
			}
			if (entity instanceof AbstractEntity) {
				((AbstractEntity)entity).handleEvent(e);
			} else {
				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();
		SessionDescription sdp = null;
		// Local node宛ての場合
		Entity entity = entities.get(target.getURI().toString());
		if (entity != null)
			sdp = entity.receiveOffer(e);
		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();
		Entity entity = entities.get(target.getURI().toString());
		if (entity != null)
			entity.receiveAnswer(e);
	}

	/*
	 * (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();
		Entity entity = entities.get(target.getURI().toString());
		if (entity != null)
			entity.receiveBye(e);
	}

	/**
	 * 指定されたhostnameのIPアドレスを取得する
	 * @param hostname
	 * @return
	 * @throws IOException
	 */
	public static String getIpAddress(String hostname) throws IOException {
		String address = "";

		address = InetAddress.getByName(hostname).getHostAddress();
		//System.out.println("Use Interface  :" + hostname + ", " + address);
		return address;
	}

	/**
	 * 指定されたネットワークインタフェースのIPアドレスを取得する
	 * @param niName 取得したいネットワークインタフェースの名前
	 * @return
	 * @throws IOException
	 */
	public static String getIpAddressOf(String niName) throws IOException {
		String address = "";

		java.util.Enumeration<NetworkInterface> enuIfs = NetworkInterface.getNetworkInterfaces();
		if (null != enuIfs) {
			while (enuIfs.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface) enuIfs.nextElement();
				
				// マッチング
				if (ni.getName().equals(niName)) {
					java.util.Enumeration<InetAddress> enuAddrs = ni.getInetAddresses();
					
					while(enuAddrs.hasMoreElements()) {
						InetAddress in4 = (InetAddress) enuAddrs.nextElement();
						if(in4 instanceof Inet4Address) {
							address = in4.getHostAddress();
							break;
						}
					}
				}
			}
		}

		String hostname = InetAddress.getLocalHost().getHostName();
		System.out.println("Use Interface :" + hostname + ", " + address);
		return address;
	}
	
	public static String getHostName() throws IOException {
		String name = InetAddress.getLocalHost().getHostName();
		return name;
	}
}
