package me.despawningbone.discordbot.command.info;

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.EmptyStackException;

import org.apache.commons.math3.special.Gamma;

import me.despawningbone.discordbot.command.Command;
import me.despawningbone.discordbot.command.CommandResult;
import me.despawningbone.discordbot.command.CommandResult.CommandResultType;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import net.objecthunter.exp4j.function.Function;
import net.objecthunter.exp4j.operator.Operator;

public class Calculator extends Command {
	public Calculator() {
		this.alias = Arrays.asList("calc");
		this.desc = "Do some calculations!";
		this.usage = "<operation>";
		this.remarks = Arrays.asList("This calculator is using exp4j.",
				"You can get the built in operators and functions at:", "http://projects.congrace.de/exp4j/",
				"`!` - factorials", "`logx(base, num)` - logarithm (base x)", "are supported too.");
		this.examples = Arrays.asList("3*4-2", "4 * (sin(3 - 5)) + 5!", "log(e) + logx(10, 100)");
	}

	Function logb = new Function("logx", 2) {
		@Override
		public double apply(double... args) {
			return Math.log(args[1]) / Math.log(args[0]);
		}
	};
	
	Function digamma = new Function("digamma", 1) {
		@Override
		public double apply(double... args) {
			return Gamma.digamma(args[0]);
		}
	};
	
	Operator factorial = new Operator("!", 2, true, Operator.PRECEDENCE_POWER + 1) {
		@Override
		public double apply(double... args) {
			final long arg = (long) args[0];
			if ((double) arg != args[0]) {
				throw new IllegalArgumentException("Operand for factorial has to be an integer");
			}
			if (arg < 0) {
				throw new IllegalArgumentException("The operand of the factorial can not " + "be " + "less than zero");
			}
			double result = 1;
			for (int i = 1; i <= arg; i++) {
				result *= i;
			}
			return result;
		}
	};

	@Override
	public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
		if (args.length < 1) {
			return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an operation.");
		}
		String splitted = String.join(" ", args);
		if (msg.getContentDisplay().toLowerCase().contains("the meaning of life, the universe, and everything")) { // easter egg
			channel.sendMessage("The answer is: `42`").queue();
			return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
		}
		if (args[0].equals("9+10") || args[0].equals("9 + 10")) { // easter egg
			channel.sendMessage("The answer is: `21`\n\n *i am smart ;)*").queue();
			return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
		}
		if (args[0].equals("666") || splitted.equals("333")) { // easter egg
			channel.sendMessage("No you don't").queue();
			return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
		} else {
			String operation = String.join("", args);
			if (operation.contains("!")) {
				int index = operation.indexOf("!");
				while (index >= 0) { // indexOf returns -1 if no match found
					operation = new StringBuilder(operation).insert(index + 1, "(1)").toString();
					index = operation.indexOf("!", index + 1);
				}
			}
			DecimalFormat format = new DecimalFormat();
			format.setDecimalSeparatorAlwaysShown(false);
			// System.out.println(operation);
			Expression e = null;
			String ans = null;
			double planckConstant = 6.62607004 * Math.pow(10, -34);
			double eulerMascheroni = 0.57721566490153286060651209008240243104215933593992;
			try {
				e = new ExpressionBuilder(operation).variable("h").variable("γ").function(digamma).function(logb).operator(factorial).build()
						.setVariable("h", planckConstant).setVariable("γ", eulerMascheroni);
				ans = Double.toString(e.evaluate());
				// String ans = format.format(e.evaluate());
			} catch (EmptyStackException e1) {
				return new CommandResult(CommandResultType.INVALIDARGS, "You have imbalanced parentheses.");
			} catch (ArithmeticException e1) {
				return new CommandResult(CommandResultType.INVALIDARGS, "You cannot divide by zero.");
			} catch (IllegalArgumentException e1) {
				return new CommandResult(CommandResultType.FAILURE, "An error has occured: " + e1.getMessage());
			}
			if (ans.equals("NaN")) {
				ans = "Undefined";
			}
			channel.sendMessage("The answer is: `" + ans + "`").queue();
			return new CommandResult(CommandResultType.SUCCESS);
		}
	}
}
