package org.inria.bmajwatcher.server;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.inria.biomaj.exe.bank.BankFactory;
import org.inria.biomaj.exe.bank.BiomajBank;
import org.inria.biomaj.session.bank.BiomajSQLQuerier;
import org.inria.biomaj.session.bank.ProductionDirectory;
import org.inria.biomaj.sql.SQLConnection;
import org.inria.biomaj.sql.SQLConnectionFactory;
import org.inria.biomaj.utils.BiomajConst;
import org.inria.biomaj.utils.BiomajException;

/**
 * Servlet that returns in JSON format information
 * on banks.
 * 
 * @author rsabas
 *
 */
public class BankQueryingService extends HttpServlet {
	

	private static final String BLAST = "blast";

	private static final long serialVersionUID = 7729370645569188938L;
	private static Logger log = Logger.getLogger(BankQueryingService.class);
	
	private static String FORMAT = "format";
	private static String TYPE = "type";
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		process(req, resp);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		process(req, resp);
	}
	
	private void process(HttpServletRequest req, HttpServletResponse resp) {
		if (req.getParameter("path") != null) {
			download(req, resp);
		} else if (req.getParameter("update") != null) { // Run bank update
			log.debug("REST bank update request...");
			String param = req.getParameter("update");
			String key = req.getParameter("key");
			if (!param.trim().isEmpty() && key != null && !key.trim().isEmpty()
					&& UserManagerServiceImpl.userHasAccessToBank(key, param)) {
				
				log.debug("Start update from REST API for bank " + param);
				new BiomajLauncherServiceImpl().startUpdate(param);
				
			} else {
				sendError(HttpServletResponse.SC_BAD_REQUEST, "Incorrect parameters", resp);
				log.debug("Incorrect parameters. Update not started");
			}
		} else {
			fetchDataFromURL(resp, req);
		}
	}

	private void fetchDataFromURL(HttpServletResponse resp, HttpServletRequest req) {
		
		Map<?, ?> params = req.getParameterMap();
		String type = "ALL";
		String format = "ALL";
		String banks = "ALL";
		String userKey = "";
		boolean noCache = false;
		boolean lightMode = false;
		boolean typeOk = false;
		boolean banksOk = false;
		boolean formatOk = false;
		
		for (Object s : params.keySet()) {
			String paramName = s.toString();
			if (paramName.equalsIgnoreCase("types")) {
				type = req.getParameter(paramName);
				typeOk = true;
			} else if (paramName.equalsIgnoreCase("banks")) {
				banks = req.getParameter(paramName);
				banksOk = true;
			} else if (paramName.equalsIgnoreCase("formats")) {
				format = req.getParameter(paramName);
				formatOk = true;
			} else if (paramName.equalsIgnoreCase("lightmode")) {
				lightMode=true;
			} else if (paramName.equalsIgnoreCase("nocache")) {
				noCache = true;
			} else if (paramName.equalsIgnoreCase("key")) {
				userKey = req.getParameter("key");
			}
			
			if (typeOk && banksOk && formatOk)
				break;
		}
		
		log.debug("REST request parameters = banks : " + banks + " // type : " + type + " // format : " + format + " // lightmode : " + lightMode + " // nocache : " + noCache + " // user key : " + userKey);

		
		// Writing response
		
		String res = "";
		
		if (typeOk && !banksOk && !formatOk) {
			res = collectionToJSON(getDBTypes(userKey), "types");
		} else if (formatOk && !banksOk && !typeOk) {
			res = collectionToJSON(getFormats(userKey), "formats");
		} else if (banksOk) {
			res = getBanks(userKey, type, format, banks, lightMode, noCache);
		}

		resp.setContentType("text/plain");
		resp.setHeader("Content-Disposition", "inline");
		resp.setContentLength(res.length());
		

		byte[] output = res.getBytes();
		BufferedOutputStream bos = null;
		try {
			bos = new BufferedOutputStream(resp.getOutputStream());
			for (byte b : output) {
				bos.write(b);
			}
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	

	/**
	 * Returns the banks with the given formats, types and names.
	 * Each field has to be url encoded.
	 * 
	 * @param userKey
	 * @param _type
	 * @param _format
	 * @param _name
	 * @param lightMode if true, do not list files for each format
	 * @param noCache if true do not use cache to retrieve file listing
	 * @return
	 */
	private String getBanks(String userKey, String _type, String _format, String _name, boolean _lightMode, boolean noCache) {
		String type;
		String format;
		String name;
		try {
			type = URLDecoder.decode(_type, "UTF-8");
			format = URLDecoder.decode(_format, "UTF-8");
			name = URLDecoder.decode(_name, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return "";
		}
		
		StringBuilder allBanks = new StringBuilder();
		allBanks.append("{\"banks\":[");
		List<String> banks;
		List<String> userBanks = getBanksForUser(userKey);
		if (name.equalsIgnoreCase("ALL"))
			banks = new ArrayList<String>(userBanks);
		else {
			banks = new ArrayList<String>();
			for (String bk : name.split("\\|")) {
				if (userBanks.contains(bk))
					banks.add(bk);
			}
		}
		
		Map<String, String> directoryRelease = null;
		for (String bank : banks) {
			
			if ((directoryRelease = getDirectoriesRelease(bank)).size() == 0) {
				continue;
			}
			
			Map<String, String> info = getBankInfo(bank);
			if (format.equalsIgnoreCase("ALL") || containsFormat(format, info.get(FORMAT))) {
				if (type.equalsIgnoreCase("ALL") || typeContains(info.get(TYPE), type)) {
					allBanks.append(infoToJSON(bank, format, info, directoryRelease, _lightMode, noCache) + ",");
				}
			}
		}
		int index = allBanks.lastIndexOf(",");
		if (index > 0)
			allBanks.deleteCharAt(index);
		allBanks.append("]}");
		return allBanks.toString();
	}

	/**
	 * 
	 * @param format format as parameter in the url (f1|f2|...)
	 * @param curFormat format as in a properties file (f1,f2,...
	 * 
	 * @return true if any format in curFormat matches any format in format
	 */
	private boolean containsFormat(String format, String curFormat) {
		String[] curformats = curFormat.split(",");
		String[] formats = format.split("\\|");
		for (String curFmt : curformats) {
			for (String fmt : formats) {
				if (fmt.equalsIgnoreCase(curFmt))
					return true;
			}
		}
		return false;
	}
	
	/**
	 * 
	 * @param type type to compare to
	 * @param types list of types in the url (t1|t2|...)
	 * 
	 * @return
	 */
	private boolean typeContains(String type, String types) {
		String[] split = types.split("\\|");
		for (String s : split) {
			if (s.equalsIgnoreCase(type))
				return true;
		}
		
		return false;
	}
	
	/**
	 * Transforms given info about the bank into a JSON formatted string.
	 * 
	 * @param bankName
	 * @param _format
	 * @param info
	 * @param directories
	 * @param lightMode
	 * @param noCache
	 * @return
	 */
	private String infoToJSON(String bankName, String _format, Map<String, String> info, Map<String, String> directories, boolean lightMode, boolean noCache) {
		
		String[] fmts = info.get(FORMAT).split(",");
		String[] formats = Arrays.copyOf(fmts, fmts.length + 1);
		formats[formats.length - 1] = "flat"; // Add flat as a format
		
		StringBuilder sb = new StringBuilder();
		int bankId = BiomajSQLQuerier.getBankId(bankName);
		
		sb.append("{");
		sb.append("\"name\":\"" + bankName + "\",");
		sb.append("\"session_date\":\"" + BiomajSQLQuerier.getLatestSessionDate(bankId) + "\",");
		sb.append("\"current_release\":\"" + getCurrentRelease(directories) + "\",");
		sb.append("\"releases\":{");
		for (String path : directories.keySet()) {
			
			sb.append("\"" + directories.get(path) + "\":{\"path\":\"" + path + "\",");
			
			sb.append("\"formats\":[");
			int fCount = 0;
			for (String format : formats) {
				if (format.trim().isEmpty())
					continue;
				
				if (_format.equals("ALL") || _format.contains(format)) {
					sb.append("{\"value\":\"" + format + "\"");
					if (!lightMode) {
						sb.append("," + filesToJSON(path, format, noCache));
					}
					sb.append("},");
					fCount++;
				}
			}
			
			if (fCount > 0)
				sb.deleteCharAt(sb.lastIndexOf(","));
			sb.append("]");
			sb.append("},");
		}
		if (directories.size() > 0)
			sb.deleteCharAt(sb.lastIndexOf(","));
		sb.append("},");
		sb.append("\"db_type\":\"" + info.get(TYPE) + "\"");
		
		sb.append("}");
		
		return sb.toString();
	}

	/**
	 * Returns from the given releases, the path to the current one.
	 * 
	 * @param releases map that contains for each production directory path, its release
	 * @return
	 */
	private String getCurrentRelease(Map<String, String> releases) {
		for (String path : releases.keySet()) {
			String parent = path.substring(0, path.lastIndexOf('/'));
			File currentLink = new File(parent + "/current");
			if (currentLink.exists()) {
				try {
					String fullPath = currentLink.getCanonicalPath();
					String release = releases.get(fullPath);
					if (release != null)
						return release;
					else {
						log.warn("Path to current release was not found in db : " + fullPath);
						return "";
					}
				} catch (IOException e) {
					log.error(e);
				}
			} else {
				log.warn("Current release link does not exist : " + currentLink);
				return "";
			}
		}
		return "";
	}
	
	/**
	 * Returns for the given bank a map that contains the pairs
	 * (productionDirectoryPath, productionDirectoryRelease)
	 * 
	 * @param bank
	 * @return
	 */
	private static Map<String, String> getDirectoriesRelease(String bank) {
		Map<String, String> res = new HashMap<String, String>();
		
		Pattern pattern = Pattern.compile("__\\d+$");
		
		String query = "SELECT path,session FROM productionDirectory WHERE ref_idbank=" +
				"(SELECT idbank FROM bank WHERE name='" + bank + "') AND state='available'";
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			while (rs.next()) {
				
				String path = rs.getString(BiomajSQLQuerier.DIR_PATH);
				Matcher matcher = pattern.matcher(path);
				boolean duplicate = false;
				if (matcher.find()) {
					duplicate = true;
				}
				
				query = "SELECT updateRelease FROM updateBank WHERE idupdateBank=" +
						"(SELECT ref_idupdateBank FROM session WHERE idsession=" + rs.getLong(BiomajSQLQuerier.DIR_SESSION) + ")";
				Statement stat2 = connection.getStatement();
				ResultSet rs2 = connection.executeQuery(query, stat2);
				try {
					if (rs2.next()) {
						String release = rs2.getString(BiomajSQLQuerier.UPDATE_RELEASE);
						if (duplicate) {
							matcher = pattern.matcher(release);
							if (!matcher.find()) { // Real duplicate, not a part of the release: add __i to release
								release += path.substring(path.lastIndexOf("__"));
							}
						}
						res.put(path, release);
					}
				} catch (SQLException e) {
					log.error(e);
				}
				SQLConnectionFactory.closeConnection(stat2);
			}
		} catch (SQLException e) {
			log.error(e);
		}
		SQLConnectionFactory.closeConnection(stat);
		
		return res;
	}
	
	/**
	 * Transforms a collection of elements into a JSON formatted string.
	 * 
	 * @param elements
	 * @param type
	 * @return
	 */
	private String collectionToJSON(Collection<String> elements, String type) {
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		sb.append("\"" + type + "\":[");
		for (String elt : elements) {
			sb.append("{\"value\":\"" + elt + "\"},");
		}
		int index = sb.lastIndexOf(",");
		if (index > 0)
			sb.deleteCharAt(index);
		sb.append("]}");
		return sb.toString();
	}
	
	
	/**
	 * Returns all the different bank formats.
	 * 
	 * @param userKey key identifying user banks are to be listed for
	 * 
	 * @return
	 */
	private Collection<String> getFormats(String userKey) {
		List<String> banks = getBanksForUser(userKey);
		Collection<String> formats = new TreeSet<String>();
		for (String bank : banks) {
			String[] fmts = getBankInfo(bank).get(FORMAT).split(",");
			for (String format : fmts)
				if (!format.trim().isEmpty())
					formats.add(format);

		}
		return formats;
	}
	
	/**
	 * Returns the formats of the given bank in the corresponding
	 * properties file.
	 * 
	 * @param bankName
	 * @return
	 */
	private Map<String, String> getBankInfo(String bankName) {
		Map<String, String> res = new HashMap<String, String>();
		res.put(FORMAT, "");
		res.put(TYPE, "");
		try {
			BiomajBank b = new BankFactory().createBank(bankName, false);
			Properties props = b.getPropertiesFromBankFile();
			if (props.containsKey(BiomajConst.dbFormatsProperty))
				res.put(FORMAT, props.getProperty(BiomajConst.dbFormatsProperty));
			if (props.containsKey(BiomajConst.typeProperty))
				res.put(TYPE, props.getProperty(BiomajConst.typeProperty));
		} catch (BiomajException e1) {
			log.error(e1.getMessage());
			e1.printStackTrace();
		}
		
		return res;
	}
	
	/**
	 * Returns all the different bank types.
	 * 
	 * @param userKey key identifying user banks are to be listed for
	 * 
	 * @return
	 */
	public static Collection<String> getDBTypes(String userKey) {
		List<String> banks = getBanksForUser(userKey);
		Collection<String> types = new TreeSet<String>();
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		for (String bankName : banks) {
			int bankId = BiomajSQLQuerier.getBankId(bankName);
			String query = "SELECT dbType FROM remoteInfo WHERE idremoteInfo = (SELECT ref_idremoteInfo FROM configuration " +
					"WHERE ref_idbank=" + bankId + " AND date=(SELECT max(date) FROM configuration WHERE ref_idbank=" + bankId + "))";
			ResultSet rs = connection.executeQuery(query, stat);
			try {
				if (rs != null && rs.next())
					types.add(rs.getString(1));
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		SQLConnectionFactory.closeConnection(stat);
		
		return types;
	}
	
	/**
	 * Transforms a file list into a JSON expression.
	 * Recursively lists from format root directory.
	 * 
	 * @param root directory
	 * @param format sub directory which is usually the format name
	 * @param noCache if true do not use cache to retrieve file list
	 * @return
	 */
	private String filesToJSON(String root, String format, boolean noCache) {
		StringBuilder sections = new StringBuilder();
		File f = new File(root + "/" + format);
		sections.append("\"sections\":[");
		File listingFile = new File(root + "/listing." + format);
		if (f.exists()) {
			if (noCache == false) {
				if (listingFile.exists()) {
					StringBuilder sb = new StringBuilder();
					try {
						log.debug("Retrieve cache from " + listingFile.getAbsolutePath());
						BufferedReader br = new BufferedReader(new FileReader(listingFile));
						String line = null;
						while ((line = br.readLine()) != null) {
							sb.append(line);
						}
						br.close();
						return sb.toString();
					} catch (FileNotFoundException e) {
						log.error(e);
						e.printStackTrace();
					} catch (IOException e) {
						log.error(e);
						e.printStackTrace();
					}
				} else {
					log.debug(listingFile.getAbsolutePath() + " not yet created");
				}
			}
			
			listFiles(f, sections, format);
		} else {
			log.warn(f.getAbsolutePath() + "was not found.");
		}
		sections.append("]");
		String res = sections.toString().replaceAll(",\"sections\":\\[\\]", "");
		
		try {
			if (f.exists()) {
				log.debug("Write JSON result in " + listingFile.getAbsolutePath());
				BufferedWriter bw = new BufferedWriter(new FileWriter(listingFile));
				bw.write(res, 0, res.length());
				bw.close();
			}
		} catch (IOException e) {
			log.error(e);
			e.printStackTrace();
		}
		
		return res;
	}
	
	/**
	 * Recursively lists all the files under given directory.
	 * 
	 * @param parentDir
	 * @param sections object that contains the sections
	 * @param format used for format specfic operations
	 */
	private void listFiles(File parentDir, StringBuilder sections, String format) {
		for (File f : parentDir.listFiles()) {
			
			// If it is a directory or a file directly under the format directory and that format is not blast
			// or is blast but the file ends with .nal
			if (f.isDirectory() || (!f.isDirectory() && f.getParentFile().getName().equals(format) &&
					(!format.equalsIgnoreCase(BLAST) || (format.equals(BLAST) && f.getName().endsWith(".nal"))))) {
				if (sections.charAt(sections.length() - 1) == '}')
					sections.append(",");
				
				sections.append("{");
				sections.append("\"name\":" + "\"" + f.getName() + "\",");
				
				List<String> currentFiles = filzOnly(f, format);
				if (currentFiles.size() > 0) {
					sections.append("\"files\":[");
					for (String s : currentFiles) {
						sections.append("\"" + s + "\",");
					}
					sections.deleteCharAt(sections.lastIndexOf(","));
					sections.append("]");
				} else {
					sections.deleteCharAt(sections.lastIndexOf(","));
				}
				
				if (f.isDirectory()) {
					sections.append(",\"sections\":[");
					listFiles(f, sections, format);
					sections.append("]");
				}
				sections.append("}");
			}
		}
	}

	
	private List<String> filzOnly(File parent, String format) {
		List<String> filz = new ArrayList<String>();
		if (!parent.isDirectory() && parent.getParentFile().getName().equals(format)) { // Files directly under the format directory
			if (format.equalsIgnoreCase(BLAST)) {
				if (parent.getName().endsWith(".nal") || parent.getName().endsWith(".pal")) {
					filz.add(parent.getAbsolutePath());
				}
			} else {
				filz.add(parent.getAbsolutePath());
			}
		} else if (parent.isDirectory()) {
			File[] files = parent.listFiles();
			for (File f : files) {
				if (!f.isDirectory()) {
					if (format.equalsIgnoreCase(BLAST)) {
						if (f.getName().endsWith(".nal") || f.getName().endsWith(".pal")) {
							filz.add(f.getAbsolutePath());
						}
					} else {
						filz.add(f.getAbsolutePath());
					}
				}
			}
		}
		return filz;
	}
	
	/**
	 * Returns bank list for given user group.
	 * 
	 * @param userKey
	 * @return
	 */
	private static List<String> getBanksForUser(String userKey) {
		
		String query = "";
		SQLConnection connection = SQLConnectionFactory.getConnection();
		PreparedStatement stat = null;
		
		if (userKey != null && !userKey.trim().isEmpty() ) {
			query = "SELECT name FROM bank WHERE ref_iduser IN " +
					"(SELECT ref_iduser FROM bw_user_has_group WHERE ref_idgroup IN " +
					"(SELECT ref_idgroup FROM bw_user_has_group WHERE ref_iduser=" +
					"(SELECT iduser FROM bw_user WHERE auth_key=?)))";
			stat = connection.getPreparedStatement(query);
			try {
				stat.setString(1, userKey);
			} catch (SQLException e) {
				log.error(e);
			}
		} else {
			query = "SELECT name FROM bank WHERE visibility=true";
			stat = connection.getPreparedStatement(query);
		}
		
		List<String> banks = new ArrayList<String>();
		try {
			ResultSet rs = stat.executeQuery();
			while (rs.next()) {
				banks.add(rs.getString(1));
			}
		} catch (SQLException e1) {
			log.error(e1);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		return banks;
	}
	
	/**
	 * Writes in the output stream the file requested by the user.
	 * 
	 * @param req
	 * @param resp
	 */
	private void download(HttpServletRequest req, HttpServletResponse resp) {
		
		String path = req.getParameter("path");
		String bank = req.getParameter("bank");
		String version = req.getParameter("version");
		boolean askForZip = req.getParameter("zip") != null && req.getParameter("zip").equals("true");
		String userKey = req.getParameter("key");
		
		
		if (bank == null || !BiomajSQLQuerier.getBanks().contains(bank)) {
			String error = "Bank does not exist";
			log.error(error);
			sendError(HttpServletResponse.SC_NOT_FOUND, error, resp);
			return;
		}
		
		/*
		 * Check access
		 */
		if (!isAuthorized(userKey, bank)) {
			String error = "Access to bank '" + bank + "' not authorized";
			log.error(error);
			sendError(HttpServletResponse.SC_UNAUTHORIZED, error, resp);
			return;
		}
		
		/*
		 * Check version
		 */
		String selectedPath = null;
		if (version != null) {
			for (ProductionDirectory pd : BiomajSQLQuerier.getAvailableProductionDirectories(bank)) {
				String v = getSessionVersion(pd.getSession());
				if (v.equals(version)) {
					selectedPath = pd.getPath();
					break;
				}
			}
			
			if (selectedPath == null) {
				String error = "Unknow version '" + version + "' for bank '" + bank + "'";
				log.error(error);
				sendError(HttpServletResponse.SC_NOT_FOUND, error, resp);
				return;
			}
		} else { // Current
			version = "current";
			ProductionDirectory pd = BiomajSQLQuerier.getLatestProductionDirectory(bank);
			if (pd == null) {
				String error = "Production directory for bank not found";
				log.error(error);
				sendError(HttpServletResponse.SC_NOT_FOUND, error, resp);
				return;
			}
			String p = pd.getPath();
			selectedPath = p.substring(0, p.lastIndexOf('/')) + "/current";
		}
		
		/*
		 * Check selected path
		 */
		if (!new File(selectedPath).exists()) {
			String error = "Version '" + selectedPath + "' not found for bank '" + bank + "'";
			log.error(error);
			sendError(HttpServletResponse.SC_NOT_FOUND, error, resp);
			return;
		}
		
		
		/*
		 * Check file to download
		 */
		String downloadPath = selectedPath + "/" + path;
		
		log.debug("Download '" + downloadPath + "' and zip : " + askForZip);
		
		boolean wasZipped = false;
		
		File toDownload = new File(downloadPath);
		if (!toDownload.exists()) {
			String error = "File to retrieve does not exist";
			log.error(error + " : " + toDownload.getAbsolutePath());
			sendError(HttpServletResponse.SC_NOT_FOUND, error, resp);
		} else {
			
			// Check access to file
			boolean authorized = false;
			String realPath = toDownload.getAbsolutePath();
			authorized = realPath.startsWith(selectedPath);
			
			if (authorized) {
			
				String downloadName = toDownload.getName(); // Regular file with no compression
				if (toDownload.isDirectory()) {
					
					downloadName = bank + "_" + version;
					String newPath = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; // Get rid of last /
					if (!newPath.isEmpty()) {
						if (newPath.contains("/")) {
							downloadName += "_" + newPath.substring(newPath.lastIndexOf('/') + 1) + ".zip";
						} else {
							downloadName += "_" + newPath + ".zip";
						}
					} else {
						downloadName += ".zip";
					}
					
					log.debug("File to download is a directory. A compressed archive will be created");
//					toDownload = zip(realPath, selectedPath);
					wasZipped = true;
				} else if (askForZip) {
					downloadName = toDownload.getName() + ".zip";
//					toDownload = zip(realPath, selectedPath);
					wasZipped = true;
				}
				
				// DL Stuff
	
				resp.setHeader("Content-Disposition", "attachment; filename=" + downloadName + ";");
				if (wasZipped){
					resp.setContentType("application/zip");
					try {
						streamZip(realPath, selectedPath, resp.getOutputStream());
					} catch (IOException e) {
						e.printStackTrace();
					}
				} else {
					resp.setHeader("Content-length", String.valueOf(toDownload.length()));
					OutputStream os = null;
					try {
						FileInputStream fis = new FileInputStream(toDownload);
						os = resp.getOutputStream();
						byte[] buffer = new byte[1024];
						int read = 0;
						while ((read = fis.read(buffer)) > 0) {
							os.write(buffer, 0, read);
						}
						os.close();
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						if (os != null) {
							try {
								os.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
				
			} else {
				String error = "Access denied for " + realPath;
				log.error("Access denied for " + realPath);
				sendError(HttpServletResponse.SC_UNAUTHORIZED, error, resp);
			}
		}
		
	}
	
	private void sendError(int code, String message, HttpServletResponse resp) {
		try {
			resp.sendError(code, message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private boolean isAuthorized(String userKey, String bank) {
		
		String query = "";
		// If admin, get all banks
		if (userKey == null) {
			query = "SELECT * FROM bank WHERE name='" + bank + "' AND visibility=true";
		} else if (UserManagerServiceImpl.isAdminKey(userKey)) {
			return true;
		} else {
			List<Integer> userIds = UserManagerServiceImpl.getGroupUsersFromKey(userKey);
			query = "SELECT * FROM bank WHERE name='" + bank + "' AND (visibility=true";
			if (userIds.size() > 0) {
				for (Integer id : userIds) {
					query += " OR ref_iduser=" + id;
				}
			}
			query += ")";
		}
		
		boolean res = false;
		SQLConnection conn = SQLConnectionFactory.getConnection();
		Statement stat = conn.getStatement();
		ResultSet rs = conn.executeQuery(query, stat);
		try {
			if (rs.next()) {
				res = true;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return res;
	}
	
	/**
	 * Writes the zip directly in the http response.
	 * 
	 * @param pathToZip absolute path to file/directory to zip
	 * @param relativeTo path zip structure should be relative to
	 * @param os HttpResponse outputstream
	 */
	private void streamZip(String pathToZip, String relativeTo, OutputStream os) {
		
		List<File> zips = new ArrayList<File>();
		
		// Determine files to zip
		File toZip = new File(pathToZip);
		boolean singleFile = false;
		
		if (toZip.isDirectory()) {
			listRecursively(zips, toZip);
		} else {
			singleFile = true;
			zips.add(toZip);
		}
		
		byte[] buf = new byte[1024];
		
		ZipOutputStream zip = null;
		try {
			zip = new ZipOutputStream(new BufferedOutputStream(os));
//			zip.setLevel(Deflater.DEFAULT_COMPRESSION);
			zip.setLevel(Deflater.BEST_SPEED);
			for (File f : zips) {
				FileInputStream uncompressed = new FileInputStream(f);
				if (singleFile)
					zip.putNextEntry(new ZipEntry(f.getName()));
				else
					zip.putNextEntry(new ZipEntry(f.getAbsolutePath().substring(relativeTo.length())));
				int count;
				while ((count = uncompressed.read(buf)) > 0) {
					zip.write(buf, 0, count);
				}
				zip.closeEntry();
				uncompressed.close();
			}
			zip.close();
			os.close();
			
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			try {
				if (zip != null) {
					zip.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
			try {
				if (os != null) {
					os.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}
	
	private static void listRecursively(List<File> files, File root) {
		for (File f : root.listFiles()) {
			if (f.isDirectory()) {
				listRecursively(files, f);
			} else {
				files.add(f);
			}
		}
	}
	
	private String getSessionVersion(long session) {
		
		String query = "SELECT updateRelease FROM updateBank WHERE idupdateBank=" +
				"(SELECT ref_idupdateBank FROM session WHERE idsession=" + session + ")";

		SQLConnection conn = SQLConnectionFactory.getConnection();
		Statement stat = conn.getStatement();
		ResultSet rs = conn.executeQuery(query, stat);
		String version = "";
		try {
			if (rs.next()) {
				version = rs.getString(1);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return version;
	}
	
}
