/**
 * Copyright (c) 2001-2018 Mathew A. Nelson and Robocode contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://robocode.sourceforge.net/license/epl-v10.html
 */
package net.sf.robocode.roborumble.netengine;


import net.sf.robocode.io.Logger;
import net.sf.robocode.roborumble.battlesengine.CompetitionsSelector;
import static net.sf.robocode.roborumble.netengine.FileTransfer.DownloadStatus;
import static net.sf.robocode.roborumble.util.ExcludesUtil.*;
import static net.sf.robocode.roborumble.util.PropertiesUtil.getProperties;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;


/**
 * Class used for downloading participating robots from the Internet.
 * Manages the download operations (participants and JAR files).
 * Controlled by properties files.
 *
 * @author Albert Pérez (original)
 * @author Flemming N. Larsen (contributor)
 */
public class BotsDownload {

	// private String internetrepository;
	private final String botsrepository;
	private final String participantsfile;
	private final String participantsurl;
	private final String tempdir;
	private final String tag;
	private final String isteams;
	private final String sizesfile;
	private final CompetitionsSelector size;
	private final String ratingsurl;
	private String generalBots;
	private final String miniBots;
	private final String microBots;
	private final String nanoBots;
	private final String generalbotsfile;
	private final String minibotsfile;
	private final String microbotsfile;
	private final String nanobotsfile;
	private final String removeboturl;

	public BotsDownload(String propertiesfile) {
		// Read parameters
		Properties parameters = getProperties(propertiesfile);

		botsrepository = parameters.getProperty("BOTSREP", "");
		isteams = parameters.getProperty("TEAMS", "NOT");
		participantsurl = parameters.getProperty("PARTICIPANTSURL", "");
		participantsfile = parameters.getProperty("PARTICIPANTSFILE", "");
		tag = parameters.getProperty("STARTAG", "pre");
		tempdir = parameters.getProperty("TEMP", "");

		// Code size
		sizesfile = parameters.getProperty("CODESIZEFILE", "");
		size = new CompetitionsSelector(sizesfile, botsrepository);

		// Ratings files
		ratingsurl = parameters.getProperty("RATINGS.URL", "");
		generalBots = propertiesfile;
		while (generalBots.indexOf("/") != -1) {
			generalBots = generalBots.substring(generalBots.indexOf("/") + 1);
		}
		generalBots = generalBots.substring(0, generalBots.indexOf("."));
		miniBots = parameters.getProperty("MINIBOTS", "");
		microBots = parameters.getProperty("MICROBOTS", "");
		nanoBots = parameters.getProperty("NANOBOTS", "");
		generalbotsfile = parameters.getProperty("RATINGS.GENERAL", "");
		minibotsfile = parameters.getProperty("RATINGS.MINIBOTS", "");
		microbotsfile = parameters.getProperty("RATINGS.MICROBOTS", "");
		nanobotsfile = parameters.getProperty("RATINGS.NANOBOTS", "");
		// remove old bots
		removeboturl = parameters.getProperty("UPDATEBOTSURL", "");

		// Read and prepare exclude filters
		setExcludes(parameters);
	}

	public boolean downloadRatings() {
		File file;

		// delete previous files
		if (generalbotsfile.length() != 0) {
			file = new File(generalbotsfile);
			if (file.exists() && !file.delete()) {
				Logger.logError("Can't delete file: " + file);
			}
		}
		if (minibotsfile.length() != 0) {
			file = new File(minibotsfile);
			if (file.exists() && !file.delete()) {
				Logger.logError("Can't delete file: " + file);
			}
		}
		if (microbotsfile.length() != 0) {
			file = new File(microbotsfile);
			if (file.exists() && !file.delete()) {
				Logger.logError("Can't delete file: " + file);
			}
		}
		if (nanobotsfile.length() != 0) {
			file = new File(nanobotsfile);
			if (file.exists() && !file.delete()) {
				Logger.logError("Can't delete file: " + file);
			}
		}
		// download new ones
		if (ratingsurl.length() == 0) {
			return false;
		}
		boolean downloaded = true;

		if (generalBots.length() != 0 && generalbotsfile.length() != 0) {
			downloaded = downloadRatingsFile(generalBots, generalbotsfile) & downloaded;
		}
		if (miniBots.length() != 0 && minibotsfile.length() != 0) {
			downloaded = downloadRatingsFile(miniBots, minibotsfile) & downloaded;
		}
		if (microBots.length() != 0 && microbotsfile.length() != 0) {
			downloaded = downloadRatingsFile(microBots, microbotsfile) & downloaded;
		}
		if (nanoBots.length() != 0 && nanobotsfile.length() != 0) {
			downloaded = downloadRatingsFile(nanoBots, nanobotsfile) & downloaded;
		}
		return downloaded;
	}

	public boolean downloadParticipantsList() {
		String begin = "<" + tag + ">";
		String end = "</" + tag + ">";
		Vector<String> bots = new Vector<String>();

		InputStream in = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;
		HttpURLConnection conn = null;

		try {
			URL url = new URL(participantsurl);

			conn = FileTransfer.connectToHttpInputConnection(url);

			// Check that we received a HTTP_OK response code.
			// Bugfix [2779557] - Client tries to remove all participants.
			if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
				System.out.print("Unable to retrieve participants list. Response is " + conn.getResponseCode());
				if (conn.getResponseMessage() != null) {
					System.out.print(": " + conn.getResponseMessage());
				}
				System.out.println();
				
				return false; // Error
			}

			boolean arebots = false;

			in = FileTransfer.getInputStream(conn);
			inputStreamReader = new InputStreamReader(in); 
			bufferedReader = new BufferedReader(inputStreamReader);

			for (String participant; (participant = bufferedReader.readLine()) != null;) {
				if (participant.indexOf(begin) >= 0) {
					arebots = true;
				} else if (participant.indexOf(end) >= 0) {
					arebots = false;
				} else if (arebots) {
					String bot = participant.split("[,]")[0];

					if (!isExcluded(bot)) {
						if (!verifyParticipantFormat(participant)) {
							if (participant.trim().length() > 0) {
								System.out.println("Participant ignored due to invalid line: " + bot);
							}
						} else {
							bots.add(participant);
						}
					} else {
						System.out.println("Ignored excluded: " + bot);
					}
				}
			}

			// Prevent our local participants file to be overwritten, if the downloaded list is empty.
			// Bugfix [2779557] - Client tries to remove all participants.
			if (bots.size() == 0) {
				System.out.println("The participants list is empty");
				return false; // Error
			}

			final File dir = new File(participantsfile).getParentFile();

			if (!dir.exists() && !dir.mkdirs()) {
				Logger.logError("Can't create directory: " + dir);
			}

			PrintStream outtxt = new PrintStream(new BufferedOutputStream(new FileOutputStream(participantsfile)), false);

			for (String bot : bots) {
				outtxt.println(bot);
			}
			outtxt.close();

		} catch (IOException e) {
			System.out.println("Unable to retrieve participants list:");
			System.out.println(e);
			return false; // Error
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ignored) {}
			}
			if (conn != null) {
				conn.disconnect();
			}
		}
		return true; // Success
	}

	public void downloadMissingBots() {
		Vector<String> jars = new Vector<String>();
		Vector<String> ids = new Vector<String>();
		Vector<String> names = new Vector<String>();

		// Read participants

		BufferedReader br = null;

		try {
			FileReader fr = new FileReader(participantsfile);
			br = new BufferedReader(fr);

			for (String record; (record = br.readLine()) != null;) {
				if (record.indexOf(",") >= 0) {
					String id = record.substring(record.indexOf(",") + 1).trim();
					String name = record.substring(0, record.indexOf(",")).trim();
					String jar = name.replace(' ', '_') + ".jar";

					jars.add(jar);
					ids.add(id);
					names.add(name);
				}
			}
		} catch (IOException e) {
			System.out.println("Participants file not found ... Aborting");
			System.out.println(e);
			return;
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException ignored) {}
			}
		}

		// check if the file exists in the repository and download if not present
		for (int i = 0; i < jars.size(); i++) {
			String botjar = jars.get(i);
			String botid = ids.get(i);
			String botname = names.get(i);
			String botpath = botsrepository + botjar;
			boolean exists = (new File(botpath)).exists();

			if (!exists) {
				boolean downloaded = downloadBot(botname, botjar, botid, botsrepository, tempdir);
				if (!downloaded) {
					System.out.println("Could not download " + botjar);
				}
			}
		}
	}

	public void updateCodeSize() {
		if (sizesfile.length() != 0) {
			BufferedReader br = null;
			try {
				FileReader fr = new FileReader(participantsfile);
				br = new BufferedReader(fr);

				for (String record; (record = br.readLine()) != null;) {
					String name = record.substring(0, record.indexOf(","));
					size.checkCompetitorForSize(name, 1500);
				}
			} catch (IOException e) {
				System.out.println("Battles input file not found ... Aborting");
				System.out.println(e);
			} finally {
				if (br != null) {
					try {
						br.close();
					} catch (IOException ignored) {}
				}
			}
		}
	}

	private boolean downloadBot(String botname, String file, String id, String destination, String tempdir) {
		String tempFileName = tempdir + file;
		String repositoryFileName = destination + file;

		// check if the bot exists in the repository

		boolean exists = (new File(repositoryFileName)).exists();
		if (exists) {
			System.out.println("The bot already exists in the repository.");
			return false;
		}

		// Download the bot

		String url;
		String sessionId = null;

		if (id.indexOf("://") == -1) {
			url = "http://robocoderepository.com/Controller.jsp?submitAction=downloadClass&id=" + id;

			sessionId = FileTransfer.getSessionId(
					"http://robocoderepository.com/BotSearch.jsp?botName=''&authorName=''&uploadDate=");
		} else {
			url = id;
		}

		DownloadStatus downloadStatus = FileTransfer.download(url, tempFileName, sessionId);

		if (downloadStatus == DownloadStatus.FILE_NOT_FOUND) {
			System.out.println("Could not find " + botname + " from " + url);
			return false;
		} else if (downloadStatus == DownloadStatus.COULD_NOT_CONNECT) {
			System.out.println("Could not connect to " + url);
			return false;
		}

		// Check the bot and save it into the repository

		if (checkJarFile(tempFileName, botname)) {
			if (!FileTransfer.copy(tempFileName, repositoryFileName)) {
				System.out.println("Unable to copy " + tempFileName + " into the repository");
				return false;
			}
		} else {
			System.out.println("Downloaded file is wrong or corrupted: " + file);
			return false;
		}

		System.out.println("Downloaded " + botname + " into " + repositoryFileName);
		return true;
	}

	private boolean checkJarFile(String file, String botname) {
		if (botname.indexOf(" ") == -1) {
			System.out.println("Are you sure " + botname + " is a bot/team? Can't download it.");
			return false;
		}

		String bot = botname.substring(0, botname.indexOf(" "));

		bot = bot.replace('.', '/');
		if (isteams.equals("YES")) {
			bot += ".team";
		} else {
			bot += ".properties";
		}

		try {
			JarFile jarf = new JarFile(file);
			ZipEntry zipe = jarf.getJarEntry(bot);

			if (zipe == null) {
				System.out.println("Not able to read properties");
				return false;
			}
			InputStream properties = jarf.getInputStream(zipe);

			Properties parameters = getProperties(properties);

			if (!isteams.equals("YES")) {
				String classname = parameters.getProperty("robot.classname", "");
				String version = parameters.getProperty("robot.version", "");

				return (botname.equals(classname + " " + version));
			}
			String version = parameters.getProperty("team.version", "");

			return (botname.equals(botname.substring(0, botname.indexOf(" ")) + " " + version));
		} catch (Exception e) {
			System.out.println(e);
			return false;
		}
	}

	// ----------------------------------------------------------------------------------
	// download ratings file
	// ----------------------------------------------------------------------------------
	private boolean downloadRatingsFile(String competition, String file) {

		InputStream in = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;
		PrintStream outtxt = null;

		try {
			URL url = new URL(ratingsurl + "?version=1&game=" + competition);

			HttpURLConnection conn = FileTransfer.connectToHttpInputConnection(url);

			final File dir = new File(file).getParentFile();

			if (!dir.exists() && !dir.mkdirs()) {
				Logger.logError("Can't create directory: " + dir);
			}

			outtxt = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)), false);

			in = FileTransfer.getInputStream(conn);
			inputStreamReader = new InputStreamReader(in); 
			bufferedReader = new BufferedReader(inputStreamReader);

			for (String str; (str = bufferedReader.readLine()) != null;) {
				outtxt.println(str);
			}
			conn.disconnect();

		} catch (IOException e) {
			System.out.println("Unable to ratings for " + competition);
			System.out.println(e);
			return false;
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ignored) {}
			}
			if (outtxt != null) {
				outtxt.close();
			}
		}
		return true;
	}

	// ----------------------------------------------------------------------------------
	// download ratings file
	// ----------------------------------------------------------------------------------

	public void notifyServerForOldParticipants() {
		// Load participants names
		Set<String> namesAll = new HashSet<String>();
		Set<String> namesMini = new HashSet<String>();
		Set<String> namesMicro = new HashSet<String>();
		Set<String> namesNano = new HashSet<String>();

		BufferedReader br = null;
		try {
			FileReader fr = new FileReader(participantsfile);
			br = new BufferedReader(fr);

			for (String record; (record = br.readLine()) != null;) {
				if (record.indexOf(",") != -1) {
					String name = record.substring(0, record.indexOf(",")).replace(' ', '_');

					namesAll.add(name);
					if (size.checkCompetitorForSize(name, 1500)) {
						namesMini.add(name);
					}
					if (size.checkCompetitorForSize(name, 750)) {
						namesMicro.add(name);
					}
					if (size.checkCompetitorForSize(name, 250)) {
						namesNano.add(name);
					}
				}
			}
		} catch (IOException e) {
			System.out.println("Participants file not found when removing old participants ... Aborting");
			System.out.println(e);
			return;
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException ignored) {}
			}
		}

		// Load ratings files
		Properties generalRatings = getProperties(generalbotsfile);
		Properties miniRatings = getProperties(minibotsfile);
		Properties microRatings = getProperties(microbotsfile);
		Properties nanoRatings = getProperties(nanobotsfile);

		// Check general ratings
		for (Enumeration<?> e = generalRatings.propertyNames(); e.hasMoreElements();) {
			String bot = (String) e.nextElement();

			if (!(isExcluded(bot) || namesAll.contains(bot))) {
				// Remove the bot from the ratings file
				System.out.println("Removing entry ... " + bot + " from " + generalBots);
				removeBot(generalBots, bot);
			}
		}
		// Check mini ratings
		for (Enumeration<?> e = miniRatings.propertyNames(); e.hasMoreElements();) {
			String bot = (String) e.nextElement();

			if (!(isExcluded(bot) || namesMini.contains(bot))) {
				// Remove the bot from the ratings file
				System.out.println("Removing entry ... " + bot + " from " + miniBots);
				removeBot(miniBots, bot);
			}
		}

		// Check micro ratings
		for (Enumeration<?> e = microRatings.propertyNames(); e.hasMoreElements();) {
			String bot = (String) e.nextElement();

			if (!(isExcluded(bot) || namesMicro.contains(bot))) {
				// Remove the bot from the ratings file
				System.out.println("Removing entry ... " + bot + " from " + microBots);
				removeBot(microBots, bot);
			}
		}

		// Check nano ratings
		for (Enumeration<?> e = nanoRatings.propertyNames(); e.hasMoreElements();) {
			String bot = (String) e.nextElement();

			if (!(isExcluded(bot) || namesNano.contains(bot))) {
				// Remove the bot from the ratings file
				System.out.println("Removing entry ... " + bot + " from " + nanoBots);
				removeBot(nanoBots, bot);
			}
		}
	}

	private void removeBot(String game, String bot) {
		if (removeboturl.length() == 0) {
			System.out.println("UPDATEBOTS URL not defined!");
			return;
		}

		String data = "version=1&game=" + game + "&name=" + bot.trim() + "&dummy=NA";

		OutputStream out = null;
		OutputStreamWriter outputStreamWriter = null;
		PrintWriter wr = null;
		InputStream in = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;

		try {
			// Send data
			URLConnection conn = FileTransfer.openOutputURLConnection(new URL(removeboturl));

			out = FileTransfer.getOutputStream(conn);
			outputStreamWriter = new OutputStreamWriter(out);
			wr = new PrintWriter(outputStreamWriter);

			wr.println(data);
			wr.flush();

			// Get the response
			in = FileTransfer.getInputStream(conn);
			inputStreamReader = new InputStreamReader(in); 
			bufferedReader = new BufferedReader(inputStreamReader);

			for (String line; (line = bufferedReader.readLine()) != null;) {
				System.out.println(line);
			}
		} catch (IOException e) {
			System.out.println(e);
		} finally {
			if (wr != null) {
				wr.close();
			}
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ignored) {}
			}
		}
	}

	// Checks if a participant format from the participant list is valid
	private boolean verifyParticipantFormat(String participant) {
		String[] split = participant.split("[,]");

		boolean matches = false;
	
		if (split.length == 2) {
			String robot = split[0].trim();
			String link = split[1].trim();
	
			if (robot.matches("[\\w\\.]+[ ][\\w\\.-]+")) {
				if (link.startsWith("http")) {
					try {
						new URL(link);
						matches = true;
					} catch (MalformedURLException e) {
						matches = false;
					}
				} else if (link.matches("[\\d]+")) {
					matches = true;
				}
			}
		}
		return matches;
	}
}
