package jp.ac.naka.ec.dht;

import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
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.routing.RoutingException;

public class Chord {

	private static DHT<String> dht = null;
	public static int PORT = 3997;
	private final static int TTL = 600 * 1000;
	private static String host;
	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();
		try {
			secret = ByteArray.valueOf("ec", ENCODING);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		secret = secret.hashWithSHA1();
	}

	public static Chord getInstance() {
		return instance;
	}


	public boolean init() throws Exception {
		if (getHost() == null) {
			setHost(Inet4Address.getLocalHost().getHostAddress());
		}
		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;
		dht.setTTLForPut(TTL);
		// dht.setHashedSecretForPut(secret);
		dht.joinOverlay(getHost(), PORT);

		// 定期的にTTLの更新
		Thread th = new Thread() {
			public void run() {
				while (true) {
					while (true)
						try {
							sleep(TTL - 100);
							Set<ID> set = entry.keySet();
							for (ID id : set) {
								String value = entry.get(id);
								if (dht != null)
									dht.put(id, value);
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
				}
			}
		};

		th.start();
		return init;
	}

	public boolean init(String host, int port) throws Exception {
		PORT = port;
		setHost(host);
		init();

		return true;
	}

	/**
	 * 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) {
			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);
				dht.put(key, value);
				entry.put(key, value);
			}
		} else {
			return false;
		}
		return true;
	}

	public Entity[] getEntities(String keyword) {
		if (idSize == 0)
			idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		ID key = null;
		Set<ValueInfo<String>> valueSet = null;
		try {
			key = ID.getSHA1BasedID(keyword.getBytes(ENCODING), idSize);
			valueSet = dht.get(key); // get
			if (valueSet == null)
				return new Entity[0];
		} catch (Exception e1) {
			System.err.println(e1.toString());
		}

		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) {
			e1.printStackTrace();
			return false;
		}
		EntityInformation ei = new EntityInformation(entity);
		try {
			String value = ei.toXML();
			dht.put(key, value);
			entry.put(key, value);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public boolean removeEntity(String keyword, Entity entity) {
		idSize = dht.getRoutingAlgorithmConfiguration().getIDSizeInByte();
		// ID key = ID.getHashcodeBasedID(keyword, idSize);
		ID key = null;
		Set<ValueInfo<String>> valueSet;
		try {
			key = ID.getSHA1BasedID(keyword.getBytes(ENCODING), idSize);
			valueSet = dht.get(key);
		} catch (Exception e) {
			System.err.println("Fail to Get Entity!");
			e.printStackTrace();
			return false;
		}

		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())) {
				Set<ValueInfo<String>> ret;
				try {
					ret = dht.remove(key, secret);
				} catch (RoutingException e) {
					System.err.println("Fail to Remove Entity!");
					e.printStackTrace();
					return false;
				}
				entry.remove(key);
				return ret != null ? true : false;
			}
		}
		return false;
	}

	public static void setHost(String host) {
		Chord.host = host;
	}

	public static String getHost() {
		return host;
	}

}