package serverP2P;
import tools.Logger;
import tools.LogLevel;
import tools.HostItem;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import exception.RemoteException;
import exception.LocalException;
import protocolP2P.RatioRequest;
import protocolP2P.RatioResponse;
import protocolP2P.Payload;
import protocolP2P.ProtocolP2PPacket;
import remoteException.UnknownHost;
import remoteException.NotATracker;
import localException.InternalError;

/** Class allowing to keep the tracker informed about ratios
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public abstract class RatioWatcher implements Runnable {
	final static double punishmentFactor = 1.2;
	protected Logger logger;
	protected volatile boolean stop;
	protected long time;
	protected boolean force;
	protected HostItem tracker;
	protected Thread thread;
	protected Map<HostItem, Double> cachePunishmentProbability = new HashMap<>();
	protected boolean lock;


	/** Constructor
	 * @param logger	Logger
	 * @param millis	Time interval before recheck
	 * @param tracker	HostItem for the tracker
	 */
	public RatioWatcher(Logger logger, long millis, HostItem tracker) {
		assert logger != null : "Logger is null";
		assert tracker != null : "Tracker is null";
		this.logger = logger;
		time = millis;
		this.tracker = tracker;
		lock = false;
	}


	/** Runnable implementation */
	public void run() {
		writeLog("Ratio watcher started : delay " + time + " milliseconds.", LogLevel.Info);
		while(!stop) {
			try {
				clean();
				Thread.sleep(time);
			} catch(InterruptedException e) {
				writeLog("Ratio watcher interrupted", LogLevel.Info);
				setStop();
			}
		}
		writeLog("Ratio watcher end of loop", LogLevel.Debug);
	}

	/** Invalidate the cache by cleaning all hashmaps
	 * @throws InterruptedException
	 */
	protected synchronized void clean() throws InterruptedException{
		while(lock) {
			this.wait();
		}
		lock = true;
		cachePunishmentProbability.clear();
		lock = false;
		this.notifyAll();
	}
		

	/** Get Up-ratio for an applications
	 * @param application	HostItem of the application
	 * @return	Punishment Probability
	 * @throws	UnknownHost
	 */
	protected synchronized double getPunishmentProbability(HostItem application) throws InternalError, UnknownHost {
		try {
			while(lock) {
				this.wait();
			}
			lock = true;
			if (!cachePunishmentProbability.containsKey(application)) {
				// update if not in cache
				try {
					ProtocolP2PPacket<?> p = createProtocolP2PPacket(new RatioRequest(application));
					p.sendRequest(getTrackerSocket());
					Payload resp = p.receiveResponse().getPayload();
					if (!(resp instanceof RatioResponse)) {
						throw new InternalError();
					}
					RatioResponse rresp = (RatioResponse)resp;
					if (!rresp.getHostItem().equals(application)) {
						writeLog("Ratio response host is not the expected one. Expected : "
							+ application + ". Received : " + rresp.getHostItem(), LogLevel.Debug);
						throw new InternalError();
					}
					long up = rresp.getTotalUp();
					long down = rresp.getTotalDown();
					assert punishmentFactor > 1 : "The punishment factor must be greater than 1";
					if (down == 0 || (punishmentFactor * up) >= down) {
						cachePunishmentProbability.put(application, Double.valueOf(0));
					} else {
						cachePunishmentProbability.put(application, Double.valueOf((down - up)/(down * punishmentFactor)));
					}
				} catch (UnknownHost e) {
					throw e;
				} catch (IOException e) {
					throw new InternalError();
				} catch (LocalException e) {
					writeLog(e, LogLevel.Error);
					throw new InternalError();
				} catch (RemoteException e) {
					writeLog(e, LogLevel.Error);
					throw new InternalError();
				}
			}
			double ret = cachePunishmentProbability.get(application);
			lock = false;
			this.notifyAll();
			return ret;
		} catch (InterruptedException e) {
		throw new InternalError();
		}
	}
	
	/** Ask the thread to stop
	 */
	public void setStop() {
		stop = true;
		if (thread != null) {
			thread.interrupt();
		}
	}

	/** Implementation of writeLog
	 * @param text	Text to log
	 * @param logLevel	level of logging
	 */
	protected abstract void writeLog(String text, LogLevel logLevel);

	/** Implementation of writeLog
	 * @param e	exception to log
	 * @param logLevel	level of logging
	 */
	protected abstract void writeLog(Exception e, LogLevel logLevel);

	/** Set thread
	 * @param thread Thread
	 */
	public void setThread(Thread thread) {
		this.thread = thread;
	}

	/** Create packets
	 * @param payload	Payload
	 */
	protected abstract < T extends Payload > ProtocolP2PPacket<?> createProtocolP2PPacket(T payload);

	/** Tracker socket getter
	 * @return tracker socket
	 */
	protected abstract Object getTrackerSocket();

	/** Closes tracker socket
	 */
	protected abstract void closeTrackerSocket();


}
