package tracker;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.net.InetAddress;
import protocolP2P.ProtocolP2PPacketTCP;
import protocolP2P.ProtocolP2PPacket;
import protocolP2P.RequestResponseCode;
import protocolP2P.Payload;
import protocolP2P.Register;
import protocolP2P.Unregister;
import protocolP2P.DiscoverRequest;
import protocolP2P.DiscoverResponse;
import protocolP2P.FileList;
import exception.LocalException;
import remoteException.EmptyDirectory;
import localException.InternalError;
import localException.SocketClosed;
import tracker.TrackerManagement;
import tools.HostItem;
import tools.Logger;
import tools.LogLevel;

/** Tracker management implementation with tcp
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public class TrackerManagementTCP extends TrackerManagement {
	private ServerSocket socket;

	/** Constructor with port and logger.
	 * @param tracker	hostitem of the tracker.
	 * @param logger	Logger object
	 */
	public TrackerManagementTCP(HostItem tracker, Logger logger) {
		super(tracker, logger);
		try {
			socket = new ServerSocket(tracker.getPort(), 10, tracker.getInetAddress());
		} catch (SocketException e) {
			writeLog("Error: cannot listen on" + tracker, LogLevel.Error);
			System.exit(-1);
		} catch (IOException e) {
			writeLog("Error: cannot open socket", LogLevel.Error);
			System.exit(-2);
		}
	}

	/** Implementation of runnable. This methods allows to run the server.
	 */
	public void run() {
		writeLog("Tracker sucessfully started", LogLevel.Info);
		while (!stop) {
			try {
				Socket s = socket.accept();
				ClientHandler c = new ClientHandler(s);
				(new Thread(c)).start();
			} catch (IOException e) {
				writeLog("Error while accepting new connection", LogLevel.Warning);
			}
		}
	}


	/** Private runnable class allowing to serve one client.
	 */
	private class ClientHandler implements Runnable {
		private HostItem addr;
		/** Constructor with a socket.
		 * @param	s	Socket of this client
		 */
		public ClientHandler(Socket s) {
			this.addr = new HostItem(s);
		}

		/** Implementation of runnable. This method allow to serve one client.
		 */
		public void run() {

			boolean end = false;
			writeLog("[" + addr + "] New connection", LogLevel.Action);
			do {
				end = handleClientRequest();
			} while(!end);
			writeLog("[" + addr + "] End of connection", LogLevel.Action);
		}

		/** Respond to next request incomming on socket s.
		 * @param s	Socket used to read request and send response
		 * @return	true if cannot expect another request (ie, socket is closed)
		 */
		private boolean handleClientRequest() {
			try {
				ProtocolP2PPacketTCP<?> pd = new ProtocolP2PPacketTCP<>((Object)addr.getTCPSocket());
				handleRequest(pd);
			} catch (IOException e) {
				writeLog(e, LogLevel.Warning);
				return true;
			} catch (SocketClosed e) {
				return true;
			} catch (LocalException e) {
				writeLog(e, LogLevel.Warning);
				return true;
			}
			return false;
		}
	}


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

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

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

	
	/** Getter for HostItem socket
	 * @param hostItem	HostItem
	 */
	protected Object getHostItemSocket(HostItem hostItem) {
		return (Object)hostItem.getTCPSocket();
	}
	
	/** Close HostItem socket
	 * @param hostItem	HostItem
	 */
	protected void closeHostItemSocket(HostItem hostItem) {
		hostItem.closeTCPSocket();
	}
}
