package jp.ac.naka.ec.dht;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityImpl;
import jp.ac.naka.ec.entity.EntityInformation;
import jp.ac.naka.ec.entity.EntityInformationParser;
import ow.dht.ByteArray;
import ow.dht.DHT;
import ow.dht.DHTConfiguration;
import ow.dht.DHTFactory;
import ow.dht.ValueInfo;
import ow.id.ID;
import ow.messaging.MessagingAddress;

public class Chord {

	private static DHT<String> dht = null;
	public static int PORT = 3997;
	private final static int TTL = 10 * 1000;

	private static Chord instance = new Chord();
	private EntityInformationParser parser;
	public static String routingStyle = "Iterative";
	public static String routingAlgolithm = "Chord";
	public static final String ENCODING = "UTF-8";
	private ByteArray secret;
	boolean init = false;
	int idSize = 0;
	private Map<ID, String> entry = new HashMap<ID, String>();

	private Chord() {
		parser = EntityInformationParser.getInstance();
		Calendar cal = Calendar.getInstance();
		Date now = cal.getTime();
		try {
			secret = ByteArray.valueOf(now.toString(), ENCODING);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		secret = secret.hashWithSHA1();
	}

	public static Chord getInstance() {
		return instance;
	}

	private Object syncObj = new Object();	

	public boolean init() throws Exception {
		DHTConfiguration config = DHTFactory.getDefaultConfiguration();
		config.setRoutingStyle(routingStyle);
		config.setRoutingAlgorithm(routingAlgolithm);
		config.setSelfPort(PORT);
		try {
			dht = DHTFactory.getDHT(config);
		} catch (Exception e) {
			Random rand = new Random();
			config.setSelfPort(PORT + rand.nextInt(20));
			dht = DHTFactory.getDHT(config);
		}
		System.out.println("A DHT object initialized.");
		init = true;
		// 定期的にTTLの更新
		Thread th = new Thread() {
			public void run() {
				while (true) {
					while (true)
						try {
							sleep(TTL - 100);
							synchronized (syncObj) {
								Set<ID> set = entry.keySet();
								for (ID id : set) {
									String value = entry.get(id);
									if (dht != null)
										dht.put(id, value, TTL, secret);
								}
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
				}
			}
		};
		th.start();
		return init;
	}

	public boolean init(String host, int port) throws Exception {
		boolean ret = init();
		MessagingAddress addr = dht.joinOverlay(host, port);
		init = true;
		return addr != null ? true : false && ret;
	}

	/**
	 * Entityの情報をDHT上に挿入する
	 * 
	 * @param entity
	 * @return
	 * @throws Exception
	 * @throws ServiceException
	 */
	public boolean insertEntity(Entity entity) throws Exception {
		if (idSize == 0)
			idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		String[] keywords = entity.getKeywords();
		if (keywords.length != 0) {
			synchronized (syncObj) {
				for (String keyword : keywords) {
					// ID key = ID.getHashcodeBasedID(keyword, idSize);
					ID key = null;
					try {
						key = ID.getSHA1BasedID(keyword.getBytes(ENCODING),
								idSize);
					} catch (UnsupportedEncodingException e1) {
					}
					EntityInformation ei = new EntityInformation(entity);
					// PIDFData pidf = new PIDFData();
					String value = ei.toXML();
					dht.put(key, value, TTL, secret);
					entry.put(key, value);
				}
			}
		} else {
			return false;
		}
		return true;
	}

	public Entity[] getEntities(String keyword) {
		if (idSize == 0)
			idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		ID key = null;
		try {
			key = ID.getSHA1BasedID(keyword.getBytes(ENCODING), idSize);
		} catch (UnsupportedEncodingException e1) {
		}
		Set<ValueInfo<String>> valueSet = dht.get(key); // get
		if (valueSet == null)
			return new Entity[0];
		Entity[] entities = new Entity[valueSet.size()];
		int num = 0;
		for (ValueInfo<String> s : valueSet) {
			String str = s.getValue();

			try {
				// SipURI uri = EntityImpl.createSipURI(uri_str);
				// Entity entity = new EntityImpl(uri);
				EntityInformation info = parser.parse(str);
				Entity entity = new EntityImpl(info);
				entities[num++] = entity;
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		return entities;
	}

	public boolean isInitiated() {
		return init;
	}

	public boolean insertEntity(String keyword, Entity entity) {
		if (dht == null)
			return false;
		if (idSize == 0)
			idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		// ID key = ID.getHashcodeBasedID(keyword, idSize);
		ID key = null;
		try {
			key = ID.getSHA1BasedID(keyword.getBytes(ENCODING), idSize);
		} catch (UnsupportedEncodingException e1) {
		}
		EntityInformation ei = new EntityInformation(entity);
		try {
			// String value = ei.toString();
			String value = ei.toXML();
			synchronized (syncObj) {
				dht.put(key, value, TTL, secret);
				entry.put(key, value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	public boolean removeEntity(String keyword, Entity entity) {
		idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		// ID key = ID.getHashcodeBasedID(keyword, idSize);
		ID key = null;
		try {
			key = ID.getSHA1BasedID(keyword.getBytes(ENCODING), idSize);
		} catch (UnsupportedEncodingException e1) {
		}
		Set<ValueInfo<String>> valueSet = dht.get(key);
		if (valueSet == null)
			return false;
		for (ValueInfo<String> s : valueSet) {
			String str = s.getValue();
			EntityInformation info;
			try {
				info = parser.parse(str);
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
			if (info.getUri().equals(entity.getURI().toString())) {
				ValueInfo ret = dht.remove(key, str, secret);
				entry.remove(key);
				return ret != null ? true : false;
			}
		}
		return false;
	}

}