/*
 *  Copyright (C) 2007  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;

import java.io.IOException;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.sip.InvalidArgumentException;
import javax.sip.SipException;

import jp.ac.naka.ec.dht.Chord;
import jp.ac.naka.ec.dht.FindPeer;
import jp.ac.naka.ec.dht.LookupEvent;
import jp.ac.naka.ec.dht.LookupListener;
import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityConfigParser;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityContainerImpl;
import jp.ac.naka.ec.sip.SipCore;

/**
 * EntityCollaboratorの機能を利用する際のフロントエンド。 Entityの追加、探索などを行う。
 * また、SipCoreおよびChordの初期化を代行する。
 * 
 * @author Takashi Kasuya
 * 
 */
public class EntityCollaborator {

	private EntityContainer container;
	private static Chord chord;
	private static EntityCollaborator instance = new EntityCollaborator();
	public static int PORT = 5060;
	public static String base = ".";
	public static FindPeer fp;
	private List<Entity> tempEntities = new ArrayList<Entity>();

	private EntityCollaborator() {
	}

	/**
	 * 
	 * @return entities 保持しているEntityの数
	 */
	public Entity[] getEntities() {
		return container.getEntities();
	}

	/**
	 * インスタンスを取得
	 * 
	 * @return
	 * @throws Exception
	 */
	public static EntityCollaborator getInstance() {
		// AutoConfig
		instance.container = EntityContainerImpl.getInstance();
		return instance;
	}

	/**
	 * ネットワークインタフェースを指定した場合のインスタンス取得。 eth0, eth1などを引数とする。
	 * 
	 * @param ni(Network
	 *            Interface)
	 * @return
	 * @throws ParseException
	 * @throws IOException
	 */
	public static EntityCollaborator getInstance(String ni) throws IOException,
			ParseException {
		// 指定されたインタフェースのIPアドレスを取得
		instance.container = EntityContainerImpl.getInstance(ni);
		return instance;
	}

	/**
	 * ローカルに保持するEntityの追加
	 * 
	 * @param entity
	 * @return
	 */
	public boolean addEntity(Entity entity) {
		container.addEntity(entity);
		if (chord != null) {
			try {
				boolean ret = chord.insertEntity(entity);
				// boolean ret = false;
				return ret;
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		} else {

			tempEntities.add(entity);
		}
		return true;
	}
	
	public boolean removeEntity(Entity entity) {
		String[] keywords = entity.getKeywords();
		container.removeEntity(entity);
		if (chord == null)
			return true;
		for (String keyword : keywords) {
			chord.removeEntity(keyword, entity);
		}
		return true;
	}

	/**
	 * Chordを使用し、ネットワークからEntityを探索する。 結果は配列として得られる。
	 * 
	 * @param keyword
	 * @return
	 */
	public Entity[] searchEntities(String keyword) {
		if (chord != null)
			return container.searchEntities(keyword);
		else
			throw new NullPointerException("DHT is not initialized");
	}

	/**
	 * デフォルトのポート番号でChordを初期化。 DHTによるP2Pネットワークを形成する。
	 * 
	 * @throws Exception
	 */
	public void initiateDHT() throws Exception {
		chord = Chord.getInstance();
		chord.init();
		for (Entity entity : tempEntities)
			chord.insertEntity(entity);
		tempEntities.clear();
		tempEntities = null;
	}
	
	public void initiateDHT(int port ) throws Exception {
		Chord.PORT = port;
		initiateDHT();
	}

	/**
	 * ブートストラップノードのIPアドレスを指定してChordを初期化する。 ブートストラップノードが形成しているP2Pネットワークに参加さする。
	 * 
	 * @param bootstrapURI
	 * @throws Exception
	 */
	public void initiateDHT(String bootstrapURI) throws Exception {
		chord = Chord.getInstance();
		chord.init(bootstrapURI, Chord.PORT);
		for (Entity entity : tempEntities)
			chord.insertEntity(entity);
		tempEntities.clear();
		tempEntities = null;
	}

	/**
	 * ブートストラップノードのトランスポートアドレスを指定して、P2Pネットワークに参加する。
	 * 
	 * @param bootstrapURI
	 * @param port
	 * @throws Exception
	 */
	public void initiateDHT(String bootstrapURI, int port) throws Exception {
		chord = Chord.getInstance();
		chord.init(bootstrapURI, port);
		for (Entity entity : tempEntities)
			chord.insertEntity(entity);
		tempEntities.clear();
		tempEntities = null;
	}

	/**
	 * URIで示されたEntityを取得。 ただし、ローカルに保持するEntityのみ
	 * 
	 * @param uri
	 * @return
	 */
	public Entity getEntity(String uri) {
		Entity ret = container.getEntity(uri);
		return ret;
	}

	/**
	 * SipCoreを初期化し、SipCore.PORTで指定されたポートでSIPリクエストの受け付けを開始できる。
	 * 
	 * @throws IOException
	 * @throws SipException
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 * @throws TooManyListenersException
	 */
	public void initiateSipCore() throws IOException, SipException,
			ParseException, InvalidArgumentException, TooManyListenersException {
		SipCore core = SipCore.getInstance();
		String hostname = EntityContainerImpl.getHostName();
		String address = InetAddress.getByName(hostname).getHostAddress();
		core.initialize(address, SipCore.PORT);
	}

	public void initiateSipCore(int port) throws IOException, SipException,
			ParseException, InvalidArgumentException, TooManyListenersException {
		SipCore core = SipCore.getInstance();
		String hostname = EntityContainerImpl.getHostName();
		String address = InetAddress.getByName(hostname).getHostAddress();
		core.initialize(address, port);
	}

	/**
	 * ローカルエリア内のピアを自動発見し、接続する。
	 * 
	 * @throws IOException
	 */
	public void findPeer() throws IOException {
		fp = FindPeer.getInstance();
		fp.addLookupListener(new LookupListener() {
			public void findPeer(LookupEvent evt) {
				System.out.println("Peer Found :"
						+ evt.getPeerAddress().getHostAddress());
				InetAddress addr = evt.getPeerAddress();
				try {
					initiateDHT(addr.getHostAddress());
					for (Entity entity : tempEntities)
						chord.insertEntity(entity);
					tempEntities.clear();
					tempEntities = null;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			public void peerNotFound(LookupEvent evt) {
				try {
					System.out.println("Peer Not Found.");
					initiateDHT();
					for (Entity entity : tempEntities)
						chord.insertEntity(entity);
					tempEntities.clear();
					tempEntities = null;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		fp.findOtherPeer();
		ExecutorService service = Executors.newSingleThreadExecutor();
		service.execute(fp);
	}

	public static String config = "./config/entities.xml";

	public void initEntities() {
		EntityConfigParser parser = new EntityConfigParser();
		Entity[] entities = parser.parse(config);
		for (Entity entity : entities) {
			System.out.println("add " + entity.getName());
			addEntity(entity);
		}
	}

}
