package org.inria.bmajwatcher.server;

import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.UUID;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import org.apache.log4j.Logger;
import org.inria.biomaj.exe.bank.BankFactory;
import org.inria.biomaj.singleton.BiomajInformation;
import org.inria.biomaj.sql.SQLConnection;
import org.inria.biomaj.sql.SQLConnectionFactory;
import org.inria.biomaj.utils.BiomajException;
import org.inria.bmajwatcher.client.services.UserManagerService;
import org.inria.bmajwatcher.server.utils.Utils;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * Service for user and group management.
 * 
 * @author rsabas
 *
 */
public class UserManagerServiceImpl extends RemoteServiceServlet implements UserManagerService {

	private static final long serialVersionUID = -7056662771110795686L;
	
	public static final String BANK_VISIBILITY = "visibility";
	public static final String BANK_USER = "ref_iduser";
	
	public static final String USER_ID = "iduser";
	public static final String USER_LOGIN = "login";
	public static final String USER_PASSWORD = "password";
	public static final String USER_AUTH = "auth_type";
	
	public static final String GROUP_ID = "idgroup";
	public static final String GROUP_NAME = "name";
	
	private static String adminLogin = "";
	
	// <userkey+bankName,{time,access} >
	private static Map<String, List<String>> bankAccessCache = new HashMap<String, List<String>>();
	private static final int CACHE_DURATION = 30000; //in milliseconds
	
	
	private static Logger log = Logger.getLogger(UserManagerServiceImpl.class);
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		
		String tmp = getServletContext().getInitParameter("ADMIN_LOGIN");
		
		if (tmp != null)
			adminLogin = tmp;
		
	}
	
	@Override
	public boolean addGroup(String groupName) {
		log.debug("Add group: " + groupName);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "INSERT INTO bw_group(name) VALUES('" + groupName + "');";
		int res = connection.executeUpdate(query, stat);
		
		SQLConnectionFactory.closeConnection(stat);
		
		if (res <= 0) {
			log.warn("No row was inserted in DB");
			return false;
		}
		return true;
	}
	
	@Override
	public List<String> getGroups() {
		log.debug("Get group list");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT name FROM bw_group";
		ResultSet rs = connection.executeQuery(query, stat);
		
		List<String> groups = new ArrayList<String>();
		try {
			while (rs.next()) {
				groups.add(rs.getString(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return groups;

	}

	@Override
	public boolean addUser(String login, Set<String> groups, String authType, String mail) {
		log.debug("Add user: login=" + login + ",authType=" + authType + ",mail=" + mail);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String passwd = generatePassword(8);
		String hash;
		if (authType.equals("local")) {
			hash = getSHA1Hash(passwd);
		} else {
			hash = "";
		}
		
		String query = "INSERT INTO bw_user(login, password, auth_type, auth_key, mail_address) " +
				"VALUES('" + login + "','" + hash + "','" + authType + "','" + generateKey() + "','" + mail + "');";
		
		int userId = connection.executeUpdateAndGetGeneratedKey(query, stat);
		
		if (userId <= 0) {
			log.warn("No row was inserted in DB");
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		if (authType.equals("local")) {
			sendMessage(login, "Account created for BmajWatcher", "You now have access to BmajWatcher with the following credentials :\n" +
					"* Login : " + login + "\n" +
					"* Password : " + passwd + "\n");
		}
		
		if (groups.size() == 0) {
			String group = generateGroup(login);
			addGroup(group);
			addUserToGroup(login, group);
			
		} else {
			for (String group : groups) {
				query = "SELECT idgroup FROM bw_group WHERE name='" + group + "'";
				ResultSet rs = connection.executeQuery(query, stat);
				int groupId = -1;
				try {
					if (rs.next()) {
						log.debug("Group found");
						groupId = rs.getInt(1);
						
					} else {
						log.debug("Group '" + group + "' not found. Group will be added to db");
						query = "INSERT INTO bw_group(name) VALUES('" + group + "');";
						groupId = connection.executeUpdateAndGetGeneratedKey(query, stat);
					}
				} catch (SQLException e) {
					log.error(e);
				}
				
				query = "INSERT INTO bw_user_has_group(ref_iduser, ref_idgroup) VALUES(" + userId + "," + groupId + ")";
				int res = connection.executeUpdate(query, stat);
				
				if (res <= 0) {
					log.warn("No row was inserted in DB");
					SQLConnectionFactory.closeConnection(stat);
					return false;
				}
			}
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return true;
	}

	@Override
	public boolean deleteGroup(String groupName) {
		log.debug("Delete group: " + groupName);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		int groupId = getGroupId(groupName);
		
		String query = "SELECT ref_iduser FROM bw_user_has_group WHERE ref_idgroup=" + groupId;
		
		ResultSet rs = connection.executeQuery(query, stat);
		List<Integer> ids = new ArrayList<Integer>(); 
		try {
			while (rs.next()) {
				ids.add(rs.getInt(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		query = "DELETE FROM bw_user_has_group WHERE ref_idgroup=" + groupId;
		connection.executeUpdate(query, stat);
		
		query = "DELETE FROM bw_group WHERE name='" + groupName + "'";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		// Generate groups
		for (int userId : ids) {
			query = "SELECT * FROM bw_user_has_group WHERE ref_iduser=" + userId;
			rs = connection.executeQuery(query, stat);
			try {
				if (!rs.next()) {
					query = "SELECT login FROM bw_user WHERE iduser=" + userId;
					rs = connection.executeQuery(query, stat);
					try {
						rs.next();
						String login = rs.getString(1);
						String group = generateGroup(login);
						if (addGroup(group)) {
							if (!addUserToGroup(login, group)) {
								SQLConnectionFactory.closeConnection(stat);
								return false;
							}
						} else {
							SQLConnectionFactory.closeConnection(stat);
							return false;
						}
					} catch (SQLException e) {
						log.error(e);					
					}
				}
			} catch (SQLException e) {
				log.error(e);
			}
		}
		
		SQLConnectionFactory.closeConnection(stat);
		

		return true;
	}

	@Override
	public boolean deleteUser(String login) {
		log.debug("Delete user: " + login);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "DELETE FROM bw_user_has_group WHERE ref_iduser=" +
				"(SELECT iduser FROM bw_user WHERE login='" + login + "');";
		connection.executeUpdate(query, stat);
		
		query = "DELETE FROM bw_user WHERE login='" + login + "'";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}

	@Override
	public boolean updateGroup(String oldName, String newName) {
		log.debug("Update group name:old name= " + oldName + ",new name=" + newName);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "UPDATE bw_group SET name='" + newName + "' WHERE name='" + oldName + "';";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}

	@Override
	public boolean addUserToGroup(String login, String group) {
		log.debug("Add user '" + login + "' to group '" + group + "'");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		int groupId = getGroupId(group);
		if (groupId < 0) { // Group doesn't exist
			String query = "INSERT INTO bw_group(name) VALUES('" + group + "');";
			groupId = connection.executeUpdateAndGetGeneratedKey(query, stat);
		}
		
		String query = "INSERT INTO bw_user_has_group(ref_iduser, ref_idgroup) " +
				"VALUES(" + getUserId(login) + "," + groupId + ");";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}

	@Override
	public boolean removeUserFromGroup(String login, String group) {
		log.debug("Remove user '" + login + "' from group '" + group + "'");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		int userId = getUserId(login);
		String query = "DELETE FROM bw_user_has_group WHERE ref_iduser=" + userId + " AND " +
				"ref_idgroup=" + getGroupId(group);
		connection.executeUpdate(query, stat);
		
		// Check if user belongs to another group
		query = "SELECT * FROM bw_user_has_group WHERE ref_iduser=" + userId;
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			if (!rs.next()) {
				String newGroup = generateGroup(login);
				if (addGroup(newGroup)) {
					if (!addUserToGroup(login, newGroup)) {
						SQLConnectionFactory.closeConnection(stat);
						return false;
					}
				} else {
					SQLConnectionFactory.closeConnection(stat);
					return false;
				}
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}
	
	/**
	 * Generates a unique group name based on given user login.
	 * 
	 * @param login
	 * @return generated group
	 */
	private String generateGroup(String login) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String group = login;
		String query = "SELECT * FROM bw_group WHERE name='" + group + "'";
		ResultSet rs = connection.executeQuery(query, stat);
		int i = 1;
		try {
			while (rs.next()) {
				group = login + i++;
				query = "SELECT * FROM bw_group WHERE name='" + group + "'";
				rs = connection.executeQuery(query, stat);
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return group;
	}

	@Override
	public boolean setUserPassword(String login, String password) {
		log.debug("Set password for user '" + login + "'");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String hash;
		if (password != null)
			hash = getSHA1Hash(password);
		else {
			password = generatePassword(8);
			hash = getSHA1Hash(password);
		}
		
		String query = "UPDATE bw_user SET password='" + hash + "' WHERE login='" + login + "';";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		sendMessage(login, "New BmajWatcher password", "Your new password : " + password);
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}
	
	/**
	 * Sends a message to given user.
	 * 
	 * @param login recipient
	 * @param subject message subject
	 * @param content message content
	 * 
	 * @return true if mail was successfully sent
	 */
	private boolean sendMessage(String login, String subject, String content) {
		
		String mail = "";
		
		String query = "SELECT mail_address FROM bw_user WHERE login='" + login + "'";
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			if (rs.next()) {
				mail = rs.getString(1);
			}
		} catch (SQLException ex) {
			log.error(ex);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		Properties globals = BankEditingServiceImpl.getGlobalProperties();
		Properties mailProps = new Properties();
		if (globals.containsKey("mail.smtp.host") && globals.containsKey("mail.from")) {
			mailProps.put("mail.smtp.host", globals.getProperty("mail.smtp.host"));
			mailProps.put("mail.from", globals.getProperty("mail.from"));
			
			Session session = Session.getInstance(mailProps);
			
			try {
				MimeMessage msg = new MimeMessage(session);
				msg.setFrom();
				msg.setRecipients(Message.RecipientType.TO, mail);
				msg.setSubject(subject);
				msg.setSentDate(new Date());
				msg.setText(content);
				Transport.send(msg);
			} catch (MessagingException ex) {
				log.error("Send Failed", ex);
				return false;
			}
		} else {
			log.error("'mail.smtp.host' and/or 'mail.from' properties missing in global.properties");
			return false;
		}
		
		return true;
	}

	@Override
	public Map<String, Map<String, String>> getUsers() {
		log.debug("Get user list");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT iduser,login,auth_type,auth_key,mail_address FROM bw_user";
		ResultSet rs = connection.executeQuery(query, stat);
		Map<String, Map<String, String>> users = new HashMap<String, Map<String,String>>();
		try {
			while (rs.next()) {
				Map<String, String> userInfo = new HashMap<String, String>();
				int userId = rs.getInt(1);
				query = "SELECT name FROM bw_group WHERE idgroup IN " +
						"(SELECT ref_idgroup FROM bw_user_has_group WHERE ref_iduser=" + userId + ");";

				SQLConnection c2 = SQLConnectionFactory.getConnection();
				Statement s2 = c2.getStatement();
				ResultSet rs2 = c2.executeQuery(query, s2);
				String groups = "";
				while (rs2.next()) {
					groups += rs2.getString(1) + "_:_";
				}
				if (groups.length() > 0) // Get rid of last _:_
					groups = groups.substring(0, groups.length() - 3);
				SQLConnectionFactory.closeConnection(s2);
				
				userInfo.put("login", rs.getString(2));
				userInfo.put("auth_type", rs.getString(3));
				userInfo.put("key", rs.getString(4));
				userInfo.put("mail", rs.getString(5));
				userInfo.put("groups", groups);
				
				users.put(rs.getString(2), userInfo);
			}
		} catch (SQLException e) {
			log.error(e);
			SQLConnectionFactory.closeConnection(stat);
			return null;
		}
		SQLConnectionFactory.closeConnection(stat);
		
		return users;
	}
	
	
	@Override
	public List<String> getLogins() {
		log.debug("Get logins");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user";
		ResultSet rs = connection.executeQuery(query, stat);
		List<String> users = new ArrayList<String>();
		try {
			while (rs.next()) {
				users.add(rs.getString(1));
			}
		} catch (SQLException e) {
			log.error(e);
			SQLConnectionFactory.closeConnection(stat);
			return null;
		}
		SQLConnectionFactory.closeConnection(stat);
		
		return users;
	}
	
	@Override
	public boolean changeBankVisibility(String bank, boolean visibility) {
		log.debug("Change visibility for bank '" + bank + "'");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "UPDATE bank SET visibility=" + (visibility ? "true" : "false") + " WHERE name='" + bank + "';";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}
	
	@Override
	public String getAdminLogin() {
		String tmp = getServletContext().getInitParameter("ADMIN_LOGIN");
		if (tmp != null)
			adminLogin = tmp;
		
		return adminLogin;
	}
	
	public static String adminLogin() {
		return adminLogin;
	}
	
	@Override
	public String getUserKey(String login) {
		log.debug("Get key for user '" + login + "'");
		String res = null;
		
		if (login != null) {
			SQLConnection connection = SQLConnectionFactory.getConnection();
			Statement stat = connection.getStatement();
			
			String query = "SELECT auth_key FROM bw_user WHERE login='" + login + "';";
			ResultSet rs = connection.executeQuery(query, stat);
			
			
			try {
				if (rs.next()) {
					res = rs.getString(1);
				}
			} catch (SQLException e) {
				log.error(e);
			}
			
			SQLConnectionFactory.closeConnection(stat);
		}

		return res;
	}
	
	@Override
	public String getUserLoginFromKey(String key) {
		log.debug("Get login for key '" + key + "'");
		String res = null;
		
		if (key != null) {
			SQLConnection connection = SQLConnectionFactory.getConnection();
			Statement stat = connection.getStatement();
			
			String query = "SELECT login FROM bw_user WHERE auth_key='" + key + "';";
			ResultSet rs = connection.executeQuery(query, stat);
			
			try {
				if (rs.next()) {
					res = rs.getString(1);
				}
			} catch (SQLException e) {
				log.error(e);
			}
			
			SQLConnectionFactory.closeConnection(stat);
		}

		return res;
	}
	
	@Override
	public boolean changeBankOwner(String bankName, String newOwnerLogin) {
		log.debug("Set owner for bank '" + bankName + "' to " + newOwnerLogin);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user WHERE iduser=(" +
				"SELECT ref_iduser FROM bank WHERE name='" + bankName + "');";
		ResultSet rs = connection.executeQuery(query, stat);
		
		
		try {
			if (rs.next()) {
				if (!newOwnerLogin.equals(rs.getString(1))) { // Bank doesn't already belongs to user
					
					try {
						File newLocation = new File(BiomajInformation.getInstance().getProperty(BiomajInformation.WORKFLOWSDIR) + "/" + newOwnerLogin);
						if (!newLocation.exists() && !newLocation.mkdir()) {
							log.warn("Could not find user workflow dir nor create it");
							SQLConnectionFactory.closeConnection(stat);
							return false;
						}
						
						File oldLocation = BankFactory.getBankPath(bankName);
						String oldPath = oldLocation.getAbsolutePath();
						
						String newPath = newLocation.getAbsolutePath() + "/" + bankName + ".properties";
						if (!oldLocation.renameTo(new File(newPath))) {
							log.warn("Could not move bank file to new location : " + oldPath + "=>" + newPath);
							SQLConnectionFactory.closeConnection(stat);
							return false;
						}
						
						query = "UPDATE bank SET ref_iduser=(SELECT iduser FROM bw_user WHERE login='" + newOwnerLogin + "') " +
								"WHERE name='" + bankName + "';";
						
						if (connection.executeUpdate(query, stat) <= 0) { // Failed, change back file location
							log.warn("Could not update DB with new bank owner. Moving back file to old location : " + newPath + "=>" + oldPath);
							new File(newPath).renameTo(new File(oldPath));
							SQLConnectionFactory.closeConnection(stat);
							return false;
						}
					} catch (BiomajException e) {
						log.error(e);
						return false;
					}
				}
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		
		return true;
	}
	
	
	@Override
	public boolean setUserMail(String login, String newMail) {
		log.debug("Set mail : '" + newMail + "' for user : " + login);
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		String query = "UPDATE bw_user SET mail_address=? WHERE login='" + login + "';";
		PreparedStatement ps = connection.getPreparedStatement(query);
		try {
			ps.setString(1, newMail);
			if (ps.executeUpdate() <= 0) {
				log.warn("No updated row: " + query);
				SQLConnectionFactory.closeConnection(ps);
				return false;
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(ps);
		
		return true;
	}
	
	@Override
	public boolean resetUID(String login) {
		log.debug("Reset key for user '" + login + "'");
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "UPDATE bw_user SET auth_key='" + generateKey() + "' WHERE login='" + login + "';";
		if (connection.executeUpdate(query, stat) <= 0) {
			log.warn("No updated row: " + query);
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		SQLConnectionFactory.closeConnection(stat);

		return true;
	}
	
	private static String generateKey() {
		return UUID.randomUUID().toString();
	}
	
	public static boolean isAdminKey(String key) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user WHERE auth_key='" + key + "';";
		ResultSet rs = connection.executeQuery(query, stat);
		boolean res = false;
		try {
			if (rs.next()) {
				res = rs.getString(1).equals(adminLogin);
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return res;
	}

	
	private int getUserId(String login) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT iduser FROM bw_user WHERE login='" + login + "';";
		ResultSet rs = connection.executeQuery(query, stat);
		int res = -1;
		try {
			if (rs.next()) {
				res = rs.getInt(1);
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return res;
	}
	
	private int getGroupId(String group) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT idgroup FROM bw_group WHERE name='" + group + "';";
		ResultSet rs = connection.executeQuery(query, stat);
		int res = -1;
		try {
			if (rs.next()) {
				res = rs.getInt(1);
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		
		return res;
	}
	
	/**
	 * Returns a SHA1 hash of the given string.
	 * 
	 * @param str
	 * @return
	 */
	public static String getSHA1Hash(String str) {
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance("SHA1");
			digest.update(str.getBytes());
			byte[] hashedPasswd = digest.digest();
			return Utils.getHexString(hashedPasswd);
		} catch (NoSuchAlgorithmException e) {
			log.error(e);
		}
		
		return null;
	}
	
	private String generatePassword(int length) {
		String alphabet = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		Random rand = new Random();
		StringBuilder sb = new StringBuilder(length);
		int alphabetLength = alphabet.length();
		for (int i = 0; i < length; i++) {
			sb.append(alphabet.charAt(rand.nextInt(alphabetLength)));
		}
		
		return sb.toString();	
	}
	
	
	/**
	 * Returns user login and groups if user found and authentication succeeds.
	 * Returns null otherwise.
	 * 
	 * @param login
	 * @param password
	 * @return
	 */
	public static Map<String, String> getUser(String login, String password) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		Map<String, String> userInfo = null;
		
		String query = "SELECT iduser FROM bw_user WHERE login='" + login + "' AND password='" + getSHA1Hash(password) + "';";
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			if (rs.next()) {
				int userId = rs.getInt(1);
				query = "SELECT name FROM bw_group WHERE idgroup IN " +
						"(SELECT ref_idgroup FROM bw_user_has_group WHERE ref_iduser=" + userId + ");";

				rs = connection.executeQuery(query, stat);
				String groups = "";
				while (rs.next()) {
					groups += rs.getString(1) + "_:_";
				}
				if (groups.length() > 0) // Get rid of last _:_
					groups = groups.substring(0, groups.length() - 3);
				SQLConnectionFactory.closeConnection(stat);
				
				userInfo = new HashMap<String, String>();
				userInfo.put("login", login);
				userInfo.put("groups", groups);
			}
		} catch (SQLException e) {
			log.error(e);
			SQLConnectionFactory.closeConnection(stat);
			return null;
		}
		
		return userInfo;
	}
	
	/**
	 * Returns list of user id belonging to the given user group
	 * specified by its key.
	 * 
	 * @param key
	 * @return
	 */
	public static List<Integer> getGroupUsersFromKey(String key) {
		List<Integer> users = new ArrayList<Integer>();
		
		if (key == null)
			return users;
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "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='" + key + "'));";
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			while (rs.next()) {
				users.add(rs.getInt(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		return users;
	}
	
	/**
	 * Returns list of login of users belonging to the groups the 
	 * given user belong to.
	 * 
	 * @param login
	 * @return
	 */
	public static List<String> getGroupUserFromLogin(String login) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user WHERE 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 login='" + login + "')));";
		ResultSet rs = connection.executeQuery(query, stat);
		List<String> users = new ArrayList<String>();
		try {
			while (rs.next()) {
				users.add(rs.getString(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		return users;
	}
	
	public static List<String> getGroupUserFromKey(String key) {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user WHERE 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='" + key + "')));";
		ResultSet rs = connection.executeQuery(query, stat);
		List<String> users = new ArrayList<String>();
		try {
			while (rs.next()) {
				users.add(rs.getString(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		return users;
	}

	public static List<String> getUserList() {
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		String query = "SELECT login FROM bw_user";
		ResultSet rs = connection.executeQuery(query, stat);
		List<String> users = new ArrayList<String>();
		try {
			while (rs.next()) {
				users.add(rs.getString(1));
			}
		} catch (SQLException e) {
			log.error(e);
		}
		
		SQLConnectionFactory.closeConnection(stat);
		return users;
	}
	
	/**
	 * Returns true if user is allowed to see given bank.
	 * 
	 * @param userKey
	 * @param bankName
	 * @return
	 */
	public static boolean userHasAccessToBank(String userKey, String bankName) {
		
		long start = System.currentTimeMillis();

		Boolean access = getAccessAndClearCache(userKey + bankName);
		if (access != null) {
			return access;
		}
		
		// Is user admin ?
		if (isAdminKey(userKey)) {
			addCacheEntry(userKey + bankName, start, true);
			return true;
		}
		
		SQLConnection connection = SQLConnectionFactory.getConnection();
		Statement stat = connection.getStatement();
		
		// Bank owner is one of user group members ?
		String query = "SELECT * FROM bank WHERE name='" + bankName + "' AND (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='" + userKey + "'))))";
		
		ResultSet rs = connection.executeQuery(query, stat);
		try {
			if (rs.next()) {
				SQLConnectionFactory.closeConnection(stat);
				addCacheEntry(userKey + bankName, start, true);
				return true;
			}
		} catch (SQLException e) {
			log.error(e);
			e.printStackTrace();
			SQLConnectionFactory.closeConnection(stat);
			return false;
		}
		
		addCacheEntry(userKey + bankName, start, false);
		return false;
		
	}
	
	public static void addCacheEntry(String key, long time, boolean access) {
		List<String> cacheValue = new ArrayList<String>();
		cacheValue.add(String.valueOf(time));
		cacheValue.add(String.valueOf(access));
		bankAccessCache.put(key, cacheValue);
	}
	
	/**
	 * Browse cache and remove old entries.
	 * If given entry was found, return related access.
	 * Return null otherwise.
	 * 
	 * @return
	 */
	private synchronized static Boolean getAccessAndClearCache(String key) {
		Boolean res = null;
		
		long time = System.currentTimeMillis();
		List<String> toRemove = new ArrayList<String>();
		for (String value : bankAccessCache.keySet()) {
			if (time - Long.valueOf(bankAccessCache.get(value).get(0)) > CACHE_DURATION) {
				toRemove.add(value);
			} else if (key.equals(value)) {
				res = Boolean.valueOf(bankAccessCache.get(value).get(1));
			}
		}
		
		for (String s : toRemove) {
			bankAccessCache.remove(s);
		}
		
		return res;
	}
	
}
