package me.despawningbone.discordbot.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import me.despawningbone.discordbot.DiscordBot;
import me.despawningbone.discordbot.command.Command;
import me.despawningbone.discordbot.command.Command.BotUserLevel;
import me.despawningbone.discordbot.command.admin.Settings;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;

public class MiscUtils {

	public static String convertMillis(long m) {
		String c = null;
		if (TimeUnit.MILLISECONDS.toHours(m) == 0) {
			c = String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(m) % TimeUnit.HOURS.toMinutes(1),
					TimeUnit.MILLISECONDS.toSeconds(m) % TimeUnit.MINUTES.toSeconds(1));
		} else {
			c = String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(m),
					TimeUnit.MILLISECONDS.toMinutes(m) % TimeUnit.HOURS.toMinutes(1),
					TimeUnit.MILLISECONDS.toSeconds(m) % TimeUnit.MINUTES.toSeconds(1));
		}
		return c;
	}

	public static String appendZeroToMinute(String time) {
		if (time.split(":")[1].split(" ")[0].length() != 2) {
			time = new StringBuilder(time).insert(time.indexOf(":") + 1, "0").toString();
		}
		return time;
	}

	public static String ordinal(int i) {
		String suffix;
		int lastTwoDigits = i % 100;
		int lastDigit = lastTwoDigits % 10;
		switch (lastDigit) {
		case 1:
			suffix = "st";
			break;

		case 2:
			suffix = "nd";
			break;

		case 3:
			suffix = "rd";
			break;

		default:
			suffix = "th";
			break;
		}
		if (11 <= lastTwoDigits && lastTwoDigits <= 13) {
			suffix = "th";
		}
		return i + suffix;
	}

	//can use delete().queueAfter() instead
	public static void delayDeleteMessage(Message msg, long millis) {
		new Thread() {
			public void run() {
				try {
					Thread.sleep(millis);
					msg.delete().queue();
				} catch (InterruptedException v) {
					v.printStackTrace();
					;
				}
			}
		}.start();
	}

	public static Long nameToBase(String sbase) throws IllegalArgumentException {
		long base = 10;
		if (sbase.equals("hex") || sbase.equals("hexadecimal")) {
			base = 16;
		} else if (sbase.equals("oct") || sbase.equals("octal")) {
			base = 8;
		} else if (sbase.equals("bin") || sbase.equals("binary")) {
			base = 2;
		} else if (sbase.equals("dec") || sbase.equals("decimal") || sbase.equals("denary")) {
			base = 10;
		} else if (sbase.equals("duodecimal") || sbase.equals("dozenal")) {
			base = 12;
		} else if (sbase.equals("vigesimal")) {
			base = 20;
		} else {
			throw new IllegalArgumentException("Please enter a valid base.");
		}
		return base;
	}

	public static String longToSubscript(long i) {
		StringBuilder sb = new StringBuilder();
		for (char ch : String.valueOf(i).toCharArray()) {
			sb.append((char) ('\u2080' + (ch - '0')));
		}
		return sb.toString();
	}
	
	public static String countryNameToUnicode(String shorthand) {
		StringBuffer unibuff = new StringBuffer();
		char[] ch = shorthand.toLowerCase().toCharArray();
		for (char c : ch) {
			int temp = (int) c;
			int temp_integer = 96; //for lower case
			if (temp <= 122 & temp >= 97)
				unibuff.append(Character.toChars(127461 + (temp - temp_integer)));
		}
		return unibuff.toString();
	}

	public static long calcPermOverrides(long base, long override) {
		long oDeny = (int) (override >> 32);
		long oAllow = (int) override;
		base &= ~oDeny;
		base |= oAllow;
		return base;
	}
	
	public static String getMissingPerms(long rawPerms, int botUserLevel, Member member, TextChannel channel) {
		if(hasDisabled(rawPerms)) return "DISABLED";   //dont return but append?
		EnumSet<Permission> perms = Permission.getPermissions(rawPerms);
		boolean metBotUserLevel = Math.abs(botUserLevel) > 1 ? DiscordBot.OwnerID.equals(member.getUser().getId()) : Math.abs(botUserLevel) > 0 ? DiscordBot.ModID.contains(member.getUser().getId()) : true;
		boolean metPerms = member.hasPermission(channel, perms);
		if(rawPerms == 0 && botUserLevel < 0) botUserLevel = Math.abs(botUserLevel);  //fall back in case misconfiguration
		if(botUserLevel > 0 && !metBotUserLevel) return BotUserLevel.values()[botUserLevel].name();  //level > 0 = the command is specifically for BotMod+
		else if(botUserLevel == 0 && !metPerms) return perms.stream().map(p -> p.name()).collect(Collectors.joining(", "));  //not specified for BotMod+, check perms
		else if(botUserLevel < 0 && !(metBotUserLevel || metPerms)) return BotUserLevel.values()[Math.abs(botUserLevel)].name() + " OR " +  perms.stream().map(p -> p.name()).collect(Collectors.joining(", ")); //since BotMod+ overrides perms, either they have perm or BotMod+ suffices
		else return null;
	}
	
	public static String getRequiredPerms(long rawPerms, int botUserLevel) {
		if(hasDisabled(rawPerms)) return "DISABLED";
		EnumSet<Permission> perms = Permission.getPermissions(rawPerms);
		if(botUserLevel > 0) return BotUserLevel.values()[botUserLevel].name();  //level > 0 = the command is specifically for BotMod+
		else if(botUserLevel == 0 && !perms.isEmpty()) return perms.stream().map(p -> p.name()).collect(Collectors.joining(", "));  //not specified for BotMod+, check perms
		else if(botUserLevel < 0) return BotUserLevel.values()[Math.abs(botUserLevel)].name() + (!perms.isEmpty() ? " OR " +  perms.stream().map(p -> p.name()).collect(Collectors.joining(", ")) : ""); //since BotMod+ overrides perms, either they have perm or BotMod+ suffices
		else return null;
	}
	
	public static boolean hasDisabled(long rawPerms) {
		return (rawPerms & Settings.DISABLED) == Settings.DISABLED;
	}
	
	//TODO move these to a new SQLUtils class?
	public static String getPrefix(Statement s, String guildId) throws SQLException {
		ResultSet gRs = s.executeQuery("SELECT prefix FROM settings WHERE id = " + guildId + ";");
		String prefix = "!desp ";
		if(gRs.next()) prefix = gRs.getString(1);
		gRs.close();
		return prefix;
	}
	
	public static long getActivePerms(Statement s, TextChannel channel, Command cmd) throws SQLException {
		long perm = 0;
		ArrayList<Long> defs = new ArrayList<>();
		ArrayList<String> components = new ArrayList<>(); 
		int c = 2;
		
		components.add(cmd.getName());
		defs.add(Permission.getRaw(cmd.getDefaultPerms()));
		while(cmd.getParent() != null) {
			cmd = cmd.getParent();
			components.add(0, cmd.getName());
			defs.add(Permission.getRaw(cmd.getDefaultPerms()));
			c++;
		}
		String nodes = "\"" + components.get(0) + "\"";
		for(int i = 2; i <= components.size(); i++) {
			nodes += ", \"" + String.join(".", components.subList(0, i)) + "\"";
		}
		
		//System.out.println(nodes);
		ResultSet pRs = s.executeQuery("SELECT _GLOBAL_, " + nodes + " FROM perms_" + cmd.getCategory().toLowerCase() + " WHERE id = " + channel.getGuild().getId() + ";");
		if(pRs.next()) {  //parse and calc overrides from db settings
			for(int i = 1; i <= c; i++) {
				//System.out.println(pRs.getString(i));
				perm = getPermOverrides(pRs.getString(i), perm, channel.getId());  //cat(guild) < cmd(guild < channel) < subcmd(guild < channel) ...
			}
		} else {  //directly calc overrides for defaults
			Collections.reverse(defs);
			for(long def : defs) perm = calcPermOverrides(perm, def);
		}
		
		pRs.close();
		return perm;
	}
	
	private static Long getPermOverrides(String parse, long base, String cId) {
		long gPerms = 0, cPerms = 0; 
		if(parse != null) {
			String g = parse.substring(0, parse.indexOf("\n"));
			gPerms = Long.parseLong(g);  //gPerms should always be present
			if(parse.indexOf(cId + ":") != -1) {
				String cut = parse.substring(parse.indexOf(cId + ":") + cId.length() + 1);
				cPerms = Long.parseLong(cut.substring(0, cut.indexOf("\n")));
			}							
		}
		return calcPermOverrides(calcPermOverrides(base, gPerms), cPerms);
	}
	
	
	public static void copy(final InputStream in, final OutputStream out) {
		byte[] buffer = new byte[1024];
		int count;

		try {
			while ((count = in.read(buffer)) != -1) {
				out.write(buffer, 0, count);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Flush out stream, to write any remaining buffered data
		try {
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
