package serverP2P;
import java.util.Vector;
import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import protocolP2P.ProtocolP2PPacketUDP;
import protocolP2P.ProtocolP2PPacket;
import protocolP2P.RequestResponseCode;
import protocolP2P.Payload;
import protocolP2P.LoadRequest;
import protocolP2P.FileList;
import protocolP2P.FilePart;
import localException.InternalError;
import localException.ProtocolError;
import localException.SizeError;
import localException.TransmissionError;
import localException.VersionError;
import localException.SocketClosed;
import exception.LocalException;
import java.util.Arrays;
import tools.Logger;
import tools.LogLevel;
import java.util.HashMap;
import java.util.Map;
import protocolP2P.HashAlgorithm;
import protocolP2P.HashRequest;
import protocolP2P.HashResponse;
import tools.HostItem;
import protocolP2P.Register;
import protocolP2P.Unregister;
import java.net.UnknownHostException;
import serverP2P.ServerManagement;

/** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for UDP.
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public class ServerManagementUDP extends ServerManagement {

	private DatagramSocket socket;

	/** Constructor for UDP implementation, with baseDirectory and UDPPort parameters.
	 * @param baseDirectory	the root directory where files are stored
	 * @param hostName	the server will bind on this address
	 * @param port	the server will listen on this port
	 * @param logger	Logger item
	 * @param tracker	Tracker
	 */
	public ServerManagementUDP(String baseDirectory, HostItem server, HostItem tracker, Logger logger) {
		super(baseDirectory, server, tracker, logger);
		assert baseDirectory != null : "baseDirectory is null";
		assert server != null : "server is null";
		assert tracker != null : "tracker is null";
		assert logger != null : "logger is null";
		try {
			socket = new DatagramSocket(server.getPort(), server.getInetAddress());
		} catch (SocketException e) {
			logger.writeUDP("Error: cannot listen on " + server, LogLevel.Error);
			System.exit(-1);
		}
	}


	/** Implementation of runnable. This methods allows to run the server.
	 */
	public void run() {
		logger.writeUDP("Server sucessfully started", LogLevel.Info);
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				sendUnregisterRequest();
			}
		});
		fileListWatcher = (FileWatcher)new FileWatcherUDP(logger, 10000, server, tracker, baseDirectory); // checking every 10 seconds
		Thread flwt = new Thread(fileListWatcher);
		flwt.start();
		fileListWatcher.setThread(flwt);
		ratioWatcher = (RatioWatcher)new RatioWatcherUDP(logger, 10000, tracker);
		Thread rwt = new Thread(ratioWatcher);
		rwt.start();
		ratioWatcher.setThread(rwt);
		while(!stop) {
			try {
				ProtocolP2PPacketUDP<?> pd = new ProtocolP2PPacketUDP<>((Object)socket);
				handleRequest(pd);
			} catch (IOException e) {
			} catch (LocalException e) {
			}
		}
		writeLog("Server end of loop", LogLevel.Debug);
	}


	/** Implementation of writeLog
	 * @param text	Text to log
	 * @param logLevel	level of logging
	 */
	protected void writeLog(String text, LogLevel logLevel) {
		logger.writeUDP(text, logLevel);
	}

	/** Implementation of writeLog
	 * @param e	exception to log
	 * @param logLevel	level of logging
	 */
	protected void writeLog(Exception e, LogLevel logLevel) {
		logger.writeUDP(e, logLevel);
	}

	/** Create packets
	 * @param payload	Payload
	 */
	protected < T extends Payload > ProtocolP2PPacket<T> createProtocolP2PPacket(T payload) {
		return (ProtocolP2PPacket<T>)new ProtocolP2PPacketUDP<T>(payload);
	}

	/** Getter for tracker socket
	 * @return	Tracker's socket
	 */
	protected Object getTrackerSocket() {
		return (Object)tracker.getUDPSocket();
	}

	/** Closes socket */
	protected void closeSocket() {
		socket.close();
	}
}
