package clientP2P;

import java.util.Scanner;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
import java.io.File;
import java.net.DatagramSocket;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import localException.InternalError;
import localException.ProtocolError;
import localException.SizeError;
import localException.TransmissionError;
import localException.VersionError;
import remoteException.EmptyFile;
import remoteException.EmptyDirectory;
import remoteException.InternalRemoteError;
import remoteException.NotFound;
import remoteException.ProtocolRemoteError;
import remoteException.VersionRemoteError;
import protocolP2P.ProtocolP2PPacketUDP;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import protocolP2P.FileList;
import protocolP2P.FilePart;
import protocolP2P.LoadRequest;
import protocolP2P.HashAlgorithm;
import protocolP2P.HashRequest;
import protocolP2P.HashResponse;
import tools.HostItem;
import tools.Logger;
import tools.LogLevel;
import clientP2P.ClientDownloadUDP;

/** Implementation of P2P-JAVA-PROJECT CLIENT
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public class ClientManagementUDP implements Runnable {
	private String baseDirectory;
	private String partsSubdir;
	private List<HostItem> hostList;
	private Logger logger;

	/** Constructor for UDP implementation, with baseDirectory and UDPPort parameters.
	 * @param baseDirectory	the root directory where files are stored
	 * @param host	hostname of the server
	 * @param UDPPort	the server will listen on this port
	 */
	public ClientManagementUDP(String baseDirectory, List<HostItem> hostList, String partsSubdir, Logger logger) {
		this.baseDirectory = baseDirectory;
		this.hostList = hostList;
		this.partsSubdir = partsSubdir;
		this.logger = logger;
	}

	/** Implementation of Runnable
	 */
	public void run() {
		try {
			System.out.println("Enter all servers: type \"stop\" when finished");
			Scanner scanner = new Scanner(System.in);
			String[] list = listDirectory();
			System.out.println("Files present on the server:");
			for(String listItem: list) {
				System.out.println(listItem);
			}
			System.out.println("Name of the file to download:");
			String f = scanner.nextLine();
			download(f);
			System.out.println("File " + f + " sucessfully downloaded");
			logger.writeUDP("File " + f + " sucessfully downloaded", LogLevel.Info);
		} catch (EmptyDirectory e) {
			System.err.println("Error: Server has no file in directory");
			logger.writeUDP("Error: Server has no file in directory", LogLevel.Error);
		} catch (InternalError e) {
			System.err.println("Error: Client internal error");
			logger.writeUDP("Error: Client internal error", LogLevel.Error);
		} catch (UnknownHostException e) {
			System.err.println("Error: Server host is unknown");
			logger.writeUDP("Error: Server host is unknown", LogLevel.Error);
		} catch (IOException e) {
			System.err.println("Error: Request cannot be send or response cannot be received");
			logger.writeUDP("Error: Request cannot be send or response cannot be received", LogLevel.Error);
		} catch (TransmissionError e) {
			System.err.println("Error: Message received is too big");
			logger.writeUDP("Error: Message received is too big", LogLevel.Error);
		} catch (ProtocolError e) {
			System.err.println("Error: Cannot decode server’s response");
			logger.writeUDP("Error: Cannot decode server’s response", LogLevel.Error);
		} catch (VersionError e) {
			System.err.println("Error: Server’s response use bad version of the protocol");
			logger.writeUDP("Error: Server’s response use bad version of the protocol", LogLevel.Error);
		} catch (SizeError e) {
			System.err.println("Error: Cannot handle this packets because of internal representation limitations of numbers on the client");
			logger.writeUDP("Error: Cannot handle this packets because of internal representation limitations of numbers on the client", LogLevel.Error);
		} catch (InternalRemoteError e) {
			System.err.println("Error: Server internal error");
			logger.writeUDP("Error: Server internal error", LogLevel.Error);
		} catch (ProtocolRemoteError e) {
			System.err.println("Error: Server cannot decode client’s request");
			logger.writeUDP("Error: Server cannot decode client’s request", LogLevel.Error);
		} catch (VersionRemoteError e) {
			System.err.println("Error: Server cannot decode this version of the protocol");
			logger.writeUDP("Error: Server cannot decode this version of the protocol", LogLevel.Error);
		} catch (NotFound e) {
			System.err.println("Error: Server has not this file in directory");
			logger.writeUDP("Error: Server has not this file in directory", LogLevel.Error);
		} catch (EmptyFile e) {
			System.err.println("Error: File is empty");
			logger.writeUDP("Error: File is empty", LogLevel.Error);
		}
	}

	/** Try to download a file
	 * @param filename	name of the file to download
	 * @throws	NotFound
	 * @throws	InternalError
	 * @throws	UnknownHostException
	 * @throws	IOException
	 * @throws	TransmissionError
	 * @throws	ProtocolError
	 * @throws	VersionError
	 * @throws	SizeError
	 * @throws	InternalRemoteError
	 * @throws	ProtocolRemoteError
	 * @throws	VersionRemoteError
	 * @throws	EmptyFile
	 */
	private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError {
		ClientDownloadUDP downLoader = new ClientDownloadUDP(filename, hostList, partsSubdir, baseDirectory, logger);
		Thread t = new Thread(downLoader);
		t.start();
		try {
			t.join();
			if (downLoader.getSuccess()) {
				byte[] hash512 = downLoader.getHashSum512();
				if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) {
					System.err.println("Error: Hashsum does not match");
					System.err.println("Computed checksum:");
					byte[] c = computeHashsum(filename, HashAlgorithm.SHA512);
					for (byte b: c) {
						System.err.print(String.format("%02X", b));
						logger.writeUDP("Computed checksum:" + String.format("%02X", b), LogLevel.Info);
					}
					System.err.println("");
					System.err.println("Received checksum:");
					for (byte b: hash512) {
						System.err.print(String.format("%02X", b));
						logger.writeUDP("Received checksum:" + String.format("%02X", b), LogLevel.Info);
					}
					System.err.println("");
					throw new InternalError();
				}
			} else {
				throw new InternalError();
			}
		} catch (InterruptedException e) {
			logger.writeUDP(e, LogLevel.Error);
			throw new InternalError();
		}
	}

	/** list server’s directory content
	 * @return	list of files
	 * @throws	InternalError
	 * @throws	UnknowHostException
	 * @throws	IOException
	 * @throws	TransmissionError
	 * @throws	ProtocolError
	 * @throws	VersionError
	 * @throws	SizeError
	 * @throws	EmptyDirectory
	 * @throws	InternalRemoteError
	 * @throws	ProtocolRemoteError
	 * @throws	VersionRemoteError
	 */
	private String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError {
		ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.LIST_REQUEST));
		d.sendRequest((Object)hostList.get(0).getUDPSocket());
		try {
			Payload p = d.receiveResponse().getPayload();
			assert p instanceof FileList : "This payload must be instance of Filelist";
			if (!(p instanceof FileList)) {
				throw new InternalError();
			} else {
				return ((FileList)p).getFileList();
			}
		} catch (NotFound e) {
			logger.writeUDP(e, LogLevel.Error);
			throw new ProtocolError();
		} catch (EmptyFile e) {
			logger.writeUDP(e, LogLevel.Error);
			throw new ProtocolError();
		}
	}

	/** Compute Hashsum of a file.
	 * @param	filename
	 * @return	hashsum
	 */
	private byte[] computeHashsum(String filename, HashAlgorithm h) {
		try {
			MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName());
			return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename)));
		} catch (NoSuchAlgorithmException e) {
			System.out.println("Error: " + h.getName() + " not supported");
			logger.writeUDP("Error: " + h.getName() + " not supported", LogLevel.Error);
		} catch (IOException e) {
			System.out.println("Error: cannot read " + filename);
			logger.writeUDP("Error: cannot read " + filename, LogLevel.Error);
		}
		return new byte[0];
	}
}
