/*
 * Copyright (c) 2009, syuu
 * Licensed under the NicoCache License.
 */

package com.dokukino.genkidama;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.HttpURLConnection;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ow.dht.DHT;
import ow.dht.ValueInfo;
import ow.id.ID;
import ow.routing.RoutingException;

import dareka.common.Config;
import dareka.processor.HttpResponseHeader;
import dareka.processor.URLResource;
import dareka.processor.impl.NicoApiUtil;

public class GenkidamaManager {
	public static Log logger = LogFactory.getLog(GenkidamaManager.class);
	private boolean connected;
	private DHT<String> dht;

	private static GenkidamaManager instance = new GenkidamaManager();
	private String externalAddress;
	private int ringPort = Config.getInteger("dhtRingPort", 9899);
	private int dataPort = Config.getInteger("dhtDataPort", 9900);
	private DHTServer dhtServer = new DHTServer();
	private DataServer dataServer = new DataServer();
	private ExecutorService putExecutor = Executors.newSingleThreadExecutor();

	private GenkidamaManager() {
	}

	public static GenkidamaManager getInstance() {
		return instance;
	}

	public int getRingPort() {
		return ringPort;
	}
	
	public DHT<String> getDht() {
		return dht;
	}

	public void setDht(DHT<String> dht) {
		this.dht = dht;
	}

	public String getExternalAddress() {
		return externalAddress;
	}

	public void setExternalAddress(String externalAddress) {
		this.externalAddress = externalAddress;
	}

	public int getDataPort() {
		return dataPort;
	}

	public ExecutorService getPutExecutor() {
		return putExecutor;
	}

	public void connect() throws Exception {
		assert ringPort != dataPort;
		dataServer.bind(dataPort);
		dhtServer.start();
	}

	public void disconnect() throws RoutingException {
		/*
		 * synchronized(mainThread) { mainThread.notify(); }
		 */
		try {
			if (connected)
				dhtServer.disconnect();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			if (logger.isErrorEnabled())
				logger.error("Error Occurs:", e);
		}
	}

	public boolean isConnected() {
		return connected;
	}

	public void setConnected(boolean connected) {
		this.connected = connected;
	}

	public String getNicoCache(String id) throws RoutingException {
		if (!checkDeleted(id)) {
			ID key = ID.getSHA1BasedID(("nc:" + id).getBytes());
			Set<ValueInfo<String>> uriList = dht.get(key);
			for (ValueInfo<String> v : uriList) {
				String uri = v.getValue();
				try {
					java.net.URL url = new java.net.URL(uri);
					if (url.getHost().equals(externalAddress))
						continue;
					HttpURLConnection conn = (HttpURLConnection) url
							.openConnection();
					conn.setRequestMethod("HEAD");
					conn.setConnectTimeout(Config
							.getInteger("dhtTimeout", 1000));
					conn.connect();
					conn.disconnect();
					if (logger.isInfoEnabled())
						logger.info("DHT get: nc:" + id + " success");
					return uri;
				} catch (Exception e) {
					if (logger.isDebugEnabled())
						logger.debug(e);
				}
			}
		}
		logger.info("DHT get: nc:" + id + " failed");
		return null;
	}

	public void putNicoCache(String id) throws Exception {
		putExecutor.execute(new PutTask(id));
	}

	public static boolean checkDeleted(String id) {
		id = id.replaceAll("low$", "");
		String title = null;
		try {
			String url = NicoApiUtil.getThumbURL(id);
			URLResource r = new URLResource(url);
			// In the general case, proxy must let browser know a redirection,
			// but in this case, behave just as a client.
			r.setFollowRedirects(true);

			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			r.transferTo(null, bout, null, null);

			ByteArrayInputStream bin = new ByteArrayInputStream(bout
					.toByteArray());
			new HttpResponseHeader(bin); // skip header

			title = NicoApiUtil.getThumbTitle(bin);

			bout.close();
			bin.close();
		} catch (Exception e) {
			logger.error(e);
			e.printStackTrace();
		}
		if (title == null) {
			logger.info("DHT cacheDeleted: " + id + " -> true");
			return true;
		} else {
			logger.info("DHT cacheDeleted: " + id + " -> false");
			return false;
		}
	}
}
