package net.mc_pandacraft.java.plugin.pandacraftutils.modules;

import java.sql.Date;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import mkremins.fanciful.FancyMessage;
import net.mc_pandacraft.java.plugin.pandacraftutils.PandacraftUtils;
import net.mc_pandacraft.java.plugin.pandacraftutils.commands.AbstractCommandExecutor;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPGroupElement;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPGroupTable;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPGroupUserElement;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPGroupUserTable;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPMessageElement;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPMessageTable;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPWebSessionElement;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.MPWebSessionTable;
import net.mc_pandacraft.java.plugin.pandacraftutils.data_model.ORM;
import net.mc_pandacraft.java.plugin.pandacraftutils.players.OffPlayer;
import net.mc_pandacraft.java.plugin.pandacraftutils.players.OnlinePlayer;
import net.mc_pandacraft.java.plugin.pandacraftutils.players.OnlinePlayerManager;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;

import de.luricos.bukkit.xAuth.events.xAuthLoginEvent;

public class PrivateMessagesManager extends BukkitRunnable {
	private PandacraftUtils plugin = PandacraftUtils.getInstance();
	
	
	private static final long TIMEOUT_WEB_SESSION = 10000; // msec

	
	
	public PrivateMessagesManager()
	{
        plugin.getServer().getScheduler().runTaskTimer(plugin, this, 2L, 10L);
        
        
        
        
		// récupération des /mail
        /*
        plugin.getServer().getScheduler().runTaskLater(plugin, new Runnable() {

			@Override
			public void run() {
				UserMap players = EssentialsInterface.getPlugin().getUserMap();
				plugin.getLogger().info("Importing player's /mail from Essentials data ...");
				if (players == null) return;
				plugin.getLogger().info("There are "+players.getUniqueUsers()+" players files in Essentials/userdata");
				Set<String> users = players.getAllUniqueUsers();
				
				for (String su : users) {
					User u = players.getUser(su);
					if (u.getBase() != null && u.getBase().isOnline()) continue;
					List<String> mails = u.getMails();
					if (mails.isEmpty()) continue;
					
					for (String mail : mails) {
						String[] split = mail.split(":", 2);
						
						String sourceName = split[0].trim();
						String message = split[1].trim();
						
						try {
							inputMessage(new MessageSender(sourceName), u.getName(), message);
						} catch (MessageSendingException e) {
							plugin.getLogger().severe(e.getMessage());
						}
					}
					
					u.setMails(null);
				}
				players.reloadConfig();
			}
        	
        }, 1L);
        */
	}
	
	
	
	
	
	
	@Override
	public void run() {
		
		// récupération des messages non synchronisé :
		try {
			
			
			
			List<MPMessageElement> messages = ((MPMessageTable)ORM.getTable("mp_message")).getAllUnsyncMessage();
			
			
			
			for (MPMessageElement message : messages) {
				if (message.getViewerNick().equals(message.getSourceNick()))
					tryDisplayMessageToConsole(message);
				
				tryDisplayMessage(message);
				
				message.setServerSync(true);
				message.save();
				
			}
			
			
			
			
			
			
			
			
			
		} catch (SQLException e) {
			plugin.getLogger().severe("Impossible de récupérer les messages privés de la base de donnée");
			e.printStackTrace();
		}
		
		
		
	}
	
	
	
	

	/**
	 * @param sender l'origine du message
	 * @param message le message
	 * @throws MessageSendingException 
	 */
	public void respondMessage(MessageSender sender, String message) throws MessageSendingException {
		OnlinePlayer op = OnlinePlayerManager.get(Bukkit.getPlayer(sender.senderName));
		if (op == null)
			throw new MessageSendingException("Seul les joueurs en ligne peuvent utiliser /r");
		if (op.getLastMessageTarget() == null)
			throw new MessageSendingException("Vous n'avez pas encore envoyé de message. Utilisez /msg");
		inputMessage(sender, op.getLastMessageTarget(), message);
	}
	
	
	/**
	 * @param sender l'origine du message
	 * @param dest pseudo du destinataire ou "g:" suivi du nom du groupe destinataire (insensible à la casse)
	 * @param message le message
	 * @throws MessageSendingException 
	 */
	public void inputMessage(MessageSender sender, String dest, String message) throws MessageSendingException {
		if (dest.startsWith("g:") || dest.startsWith("G:")) {
			// groupe
			String groupe = dest.substring(2);
			
			if (!AbstractCommandExecutor.isValidPlayerName(groupe))
				throw new MessageSendingException("Le nom du groupe n'est pas valide (lettres, chiffres, §7_§r et entre 2 et 16 caractères)");
			
			MPGroupElement groupEl = null;
			
			try {
				groupEl = ((MPGroupTable)ORM.getTable("mp_group")).getFirst("groupName LIKE '"+groupe+ "'", "id");
				
				if (groupEl == null)
					throw new MessageSendingException("Ce groupe n'existe pas");
					
				// on vérifie si le joueur source est dans le groupe
				if (sender.isPlayer()) {
					MPGroupUserElement userInGroup = ((MPGroupUserTable)ORM.getTable("mp_group_user")).getPlayerInGroup(groupEl, sender.senderName);
					
					if (userInGroup == null)
						throw new MessageSendingException("Vous n'êtes pas dans ce groupe");
				}
				
				
				List<MPGroupUserElement> groupUsers = groupEl.getUsers();
				
				long time = System.currentTimeMillis();
				int secKey = new Random().nextInt();
				
				for (MPGroupUserElement userInGroup : groupUsers) {
					MPMessageElement messEl = new MPMessageElement(time, secKey, userInGroup.getPlayerName(), sender.senderName, message, (userInGroup.getPlayerName().equalsIgnoreCase(sender.senderName)), true);
					messEl.setDestGroup(groupEl.getId());
					messEl.save();
					tryDisplayMessage(messEl);
					if (userInGroup.getPlayerName().equalsIgnoreCase(sender.senderName)
							|| (!(sender.isPlayer()) && userInGroup == groupUsers.get(0)))	// affiche aussi les messages provenant de la console, vers les groupes
						tryDisplayMessageToConsole(messEl);
				}
				
				if (sender.isPlayer()) {
					OnlinePlayer op = OnlinePlayerManager.get(plugin.getServer().getPlayer(sender.senderName));
					if (op != null)
						op.setLastMessageTarget("g:"+groupEl.getGroupName());
				}
					
				
			} catch (SQLException e) {
				e.printStackTrace();
				throw new MessageSendingException("Erreur lors de la récupération du groupe ou de ses membres");
			}
			
			
			
			
		}
		else {
			// joueur
			if (!AbstractCommandExecutor.isValidPlayerName(dest))
				throw new MessageSendingException("Le pseudo n'est pas valide");
			
			OffPlayer offP = new OffPlayer(dest);
			if (!offP.hasPlayedBefore()) {
				throw new MessageSendingException("Le joueur n'a jamais existé");
			}
			
			String destUserName = offP.getCastCorrectedName();
			
			long time = System.currentTimeMillis();
			int secKey = new Random().nextInt();
			
			
			if (sender.isPlayer() && !destUserName.equalsIgnoreCase(sender.senderName)) {
				MPMessageElement el = new MPMessageElement(time, secKey, sender.senderName, sender.senderName, message, true, true);
				el.setDestNick(destUserName);
				el.save();
				tryDisplayMessage(el);
			}
			
			MPMessageElement elCible = new MPMessageElement(time, secKey, destUserName, sender.senderName, message, false, true);
			elCible.setDestNick(destUserName);
			elCible.save();
			tryDisplayMessage(elCible);
			
			
			tryDisplayMessageToConsole(elCible);
			
			if (sender.isPlayer()) {
				OnlinePlayer op = OnlinePlayerManager.get(plugin.getServer().getPlayer(sender.senderName));
				if (op != null)
					op.setLastMessageTarget(destUserName);
			}
			
		}
			
	}
	
	
	
	
	
	
	/**
	 * 
	 * @param player le joueur qui veut voir ses messages non lus
	 * @return vrai si le joueur a bien des messages non lu, faux sinon. Retourne vrai en cas d'erreur lors de la récupération des messages
	 */
	public boolean displayUnreadMessages(Player player) {
		try {
			List<MPMessageElement> messages = ((MPMessageTable)ORM.getTable("mp_message")).getAllUnreadForPlayer(player.getName());
			if (messages.size() == 0) {
				player.sendMessage(ChatColor.GRAY+"Vous n'avez pas de messages non lus");
				return false;
			}
			
			for(MPMessageElement mess : messages)
				tryDisplayMessage(mess);
			
			return true;
			
		} catch (SQLException e) {
			e.printStackTrace();
			player.sendMessage(ChatColor.RED+"Impossible de lire les messages non lus");
			return true;
		}
	}
	
	
	
	
	
	
	
	
	
	
	
	/**
	 * Tente d'afficher le message dans le cas où le joueur cible est en ligne. Si ce
	 * joueur est en ligne, le message est marqué comme lu. Si le joueur cible ignore
	 * la source du message, la copie du message est supprimé de la base.
	 * @param message le message à afficher
	 */
	private void tryDisplayMessage(MPMessageElement message) {
		
		try {
			
			OffPlayer offP = new OffPlayer(message.getViewerNick());
			
			MessageSender sender = new MessageSender(message.getSourceNick());
			
			boolean isIgnoring = sender.isPlayer() && offP.getEssentialsUser().isIgnoredPlayer(new OffPlayer(message.getSourceNick()).getEssentialsUser());
			
			
			if (isIgnoring) {
				message.delete();
				return;
			}
			
			OnlinePlayer op = OnlinePlayerManager.get(offP.getBukkitOnlinePlayer());
			
			if (op == null) return; // la cible n'est pas en ligne
			
			if (!op.isAuthenticated()) return; // la cible n'a pas fait son /login
		
		
			message.setRead(true);
			message.save();
		
			// le message est affiché sur l'écran de la source du message
			boolean dispToSender = (message.getViewerNick().equalsIgnoreCase(message.getSourceNick()));
			
			// -------
			// tooltip
			// -------
			
			Date d = new Date(message.getTime());
			DateFormat date = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.FRANCE);
			DateFormat hour = DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.FRANCE);
			
			String affDate = date.format(d) + " à " + hour.format(d);
			
			List<String> tooltipLines = new ArrayList<String>();
			tooltipLines.add(ChatColor.GRAY+affDate);
			tooltipLines.add(ChatColor.GRAY+"de "+ChatColor.RESET+new MessageSender(message.getSourceNick()).getDisplayName()+ChatColor.GRAY+" ("+displayablePlayerStatus(getPlayerStatut(message.getSourceNick()))+ChatColor.GRAY+")");
			if (message.getDestGroup() == null)	// vers un joueur
				tooltipLines.add(ChatColor.GRAY+"vers "+ChatColor.RESET+new OffPlayer(message.getDestNick()).getDisplayName()+ChatColor.GRAY+" ("+displayablePlayerStatus(getPlayerStatut(message.getDestNick()))+ChatColor.GRAY+")");
			else { // vers un groupe
				MPGroupElement groupEl = message.getDestGroupElement();
				
				tooltipLines.add(ChatColor.GRAY+"vers le groupe "+ChatColor.GOLD+groupEl.getGroupName());
				
				List<MPGroupUserElement> groupUsers = groupEl.getUsers();
				
				for (MPGroupUserElement gUser : groupUsers) {
					if (!gUser.getPlayerName().equalsIgnoreCase(message.getSourceNick()))
						tooltipLines.add(ChatColor.GRAY+"- "+ChatColor.RESET+new OffPlayer(gUser.getPlayerName()).getDisplayName()+ChatColor.GRAY+" ("+displayablePlayerStatus(getPlayerStatut(gUser.getPlayerName()))+ChatColor.GRAY+")");
				}
				
				
			}
			
			// -------
			
			
			if (dispToSender) {
				
				String destAff = (message.getDestGroup() == null)?
						(	(message.getDestNick().equalsIgnoreCase(message.getViewerNick())) ?
								"moi":
								new OffPlayer(message.getDestNick()).getDisplayName()
						):
						ChatColor.GOLD+"g:"+message.getDestGroupElement().getGroupName();
						
				String destCommand = (message.getDestGroup() == null)?
						message.getDestNick():
						"g:"+message.getDestGroupElement().getGroupName();

				tooltipLines.add(0, ChatColor.GRAY+"(Cliquez pour répondre)");
				
				new FancyMessage("")
						.then("§6<§rmoi§6 → §r"+destAff+"§r§6>")
							.suggest("/m "+destCommand+" ")
							.tooltip(tooltipLines)
						.then(" §6"+ChatColor.translateAlternateColorCodes('&', message.getMessage()).replace("§r", "§r§6"))
							.color(ChatColor.GOLD)
						.send(op.getPlayer());
			}
			else {
				
				
				String senderAff = new MessageSender(message.getSourceNick()).getDisplayName();
				

				String destAff = (message.getDestGroup() == null)?
						((message.getDestNick().equalsIgnoreCase(message.getViewerNick()))?
								"moi":
								new OffPlayer(message.getDestNick()).getDisplayName()):
						ChatColor.GOLD+"g:"+message.getDestGroupElement().getGroupName();
						
				String destCommand = (message.getDestGroup() == null)?
						message.getSourceNick():
						"g:"+message.getDestGroupElement().getGroupName();
				
				if (message.getSourceNick() != null)
					tooltipLines.add(0, ChatColor.GRAY+"(Cliquez pour répondre)");
				
				FancyMessage fm = new FancyMessage("")
						.then("§6<§r"+senderAff+"§r§6 → §r"+destAff+"§r§6>");
							if (message.getSourceNick() != null)
								fm.suggest("/m "+destCommand+" ");
							fm.tooltip(tooltipLines)
						.then(" §6"+ChatColor.translateAlternateColorCodes('&', message.getMessage()).replace("§r", "§r§6"))
							.color(ChatColor.GOLD)
						.send(op.getPlayer());
				
				// op.getPlayer().playSound(op.getPlayer().getLocation(), Sound.ORB_PICKUP, 1, 0.3F);
			}
			
			
		
		} catch (SQLException e) {
			plugin.getLogger().severe("Impossible d'afficher un message");
			e.printStackTrace();
		}
		
		
	}
	
	
	
	private void tryDisplayMessageToConsole(MPMessageElement message) {
		
		try {
			
			
			String senderAff = new MessageSender(message.getSourceNick()).getDisplayName();
			

			String destAff = (message.getDestGroup() == null)?
					new OffPlayer(message.getDestNick()).getDisplayName():
					"g:"+message.getDestGroupElement().getGroupName();
			
			
			plugin.getServer().getConsoleSender().sendMessage("§6<§r"+senderAff+"§r§6 → §r"+destAff+"§r§6> §6"+ChatColor.translateAlternateColorCodes('&', message.getMessage()).replace("§r", "§r§6"));
			
		
		} catch (SQLException e) {
			plugin.getLogger().severe("Impossible d'afficher un message");
			e.printStackTrace();
		}
		
		
	}
	
	
	
	
	
	
	
	
	
	public void onxAuthLogin(xAuthLoginEvent event) {
		try {
			List<MPMessageElement> messages = ((MPMessageTable)ORM.getTable("mp_message")).getAllUnreadForPlayer(event.getPlayer().getName());
			
			if (messages.size() == 0) return;
			
			String[] m = new String[] {
					ChatColor.GOLD+"--------------- Messagerie privée ---------------",
					ChatColor.GOLD+"Vous avez "+messages.size()+" message"+((messages.size()>1)?"s":"")+" non lu"+((messages.size()>1)?"s":""),
					ChatColor.GOLD+"Faites "+ChatColor.GRAY+"/m"+ChatColor.GOLD+" pour voir le"+((messages.size()>1)?"s":"")+" message"+((messages.size()>1)?"s":""),
					ChatColor.GOLD+"----------------------------------------------",
			};
			
			
			event.getPlayer().sendMessage(m);
			
		
		} catch(SQLException e) {
			e.printStackTrace();
		}
	}
	
	
	
	
	
	
	
	/**
	 * 
	 * @param playerName le pseudo du joueur, insensible à la casse.
	 * @return Le statut du joueur passé en paramètre.<br/>
	 * <code>null</code> si le paramètre est <code>null</code>.
	 */
	private PlayerStatut getPlayerStatut(String playerName) {
		if (playerName == null)
			return null;
		if (!AbstractCommandExecutor.isValidPlayerName(playerName))
			return PlayerStatut.OFFLINE;
		OnlinePlayer op = OnlinePlayerManager.get(playerName);
		if (op != null)
			return op.isAfk() ? PlayerStatut.AFK_IG : PlayerStatut.ONLINE_IG;
		
		try {
			MPWebSessionElement webSession = ((MPWebSessionTable)ORM.getTable("mp_web_session")).getFirst("playerName LIKE '"+playerName+"'", null);
			
			if (webSession != null) {
				long lastWebActivity = webSession.getLastWebActivity();
				
				if (System.currentTimeMillis() - lastWebActivity < TIMEOUT_WEB_SESSION)
					return PlayerStatut.ONLINE_WEB;
				
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return PlayerStatut.OFFLINE;
	}
	
	
	private String displayablePlayerStatus(PlayerStatut ps) {
		if (ps == PlayerStatut.ONLINE_IG)
			return ChatColor.GREEN+"En ligne, IG";
		if (ps == PlayerStatut.AFK_IG)
			return ChatColor.YELLOW+"AFK, IG";
		if (ps == PlayerStatut.ONLINE_WEB)
			return ChatColor.GOLD+"En ligne, web";
		if (ps == PlayerStatut.OFFLINE)
			return ChatColor.GRAY+"Hors ligne";
		return ChatColor.WHITE+"N/A";
	}
	
	
	
	
	
	public enum PlayerStatut {
		ONLINE_IG, AFK_IG, ONLINE_WEB, OFFLINE
	}
	
	
	
	
	
	
	public static class MessageSender {
		public final String senderName;
		
		
		public MessageSender(String pName) {
			senderName = pName;
		}
		
		public MessageSender() {
			this((String)null);
		}
		
		public MessageSender(OffPlayer p) {
			this(p.getName());
		}
		
		public MessageSender(CommandSender sender) {
			this((sender instanceof Player)?sender.getName():null);
		}
		
		public boolean isPlayer() {
			return senderName != null;
		}
		
		
		public String getDisplayName() {
			if (isPlayer())
				return new OffPlayer(senderName).getDisplayName();
			else
				return ChatColor.GOLD+"Système"+ChatColor.RESET;
		}
		
	}
	
	
	@SuppressWarnings("serial")
	public class MessageSendingException extends Exception {
		private MessageSendingException(String mess) {
			super(mess);
		}
	}
	
	
}