package me.despawningbone.discordbot.command.info;

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.List;

import org.knowm.xchart.BitmapEncoder;
import org.knowm.xchart.XYChart;
import org.knowm.xchart.XYChartBuilder;
import org.knowm.xchart.BitmapEncoder.BitmapFormat;
import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;

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;

public class Graph extends Command {
	public Graph() {
		this.desc = "Plot a graph with a mathematical equation!";
		this.usage = "<eqn>";
		this.remarks = Arrays.asList("Please use \"x\" for the variables.");
		this.isDisabled = true;
		this.examples = Arrays.asList("1+x/(x+100)");
	}
	
	Function logb = new Function("logx", 2) {
		@Override
		public double apply(double... args) {
			return Math.log(args[1]) / Math.log(args[0]);
		}
	};

	@Override 
	public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
		double low = -10; double high = 10; boolean hasRange = false;
		if(args[0].contains("..")) {
			try {
				String[] range = args[0].split("\\.\\.");
				low = Double.parseDouble(range[0]);
				high = Double.parseDouble(range[1]);
				if(low < -200 || high > 200) {
					return new CommandResult(CommandResultType.INVALIDARGS, "Range specified is too large.");
				}
				hasRange = true;
			} catch (ArrayIndexOutOfBoundsException e) {
				e.printStackTrace();   //should never trigger
			} catch (IllegalStateException e) {
				if(!e.getMessage().equals("No match found")) {  //= not include range
					e.printStackTrace();
				}
			} catch (NumberFormatException e) {
				return new CommandResult(CommandResultType.INVALIDARGS, "Invalid range.");
			}	
		}
		if(hasRange) args[0] = "";
		String eqn = String.join(" ", args);
		XYChart chart = new XYChartBuilder().width(800).height(600).title("Graph for " + eqn).build();
		chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
		chart.getStyler().setLegendVisible(false);
		chart.getStyler().setPlotContentSize(0.91);
		chart.getStyler().setMarkerSize(0);
		//chart.getStyler().setAxisTitlePadding(24);
		chart.getStyler().setChartTitlePadding(12);
		chart.getStyler().setChartPadding(12);
		chart.getStyler().setChartBackgroundColor(new Color(200, 220, 230));
		List<Double> xSeries = new ArrayList<>();
		List<Double> ySeries = new ArrayList<>();
		try {
			Expression e = new ExpressionBuilder(eqn).function(logb).variables("x").build();
			for(double i = low; i <= high; i+=0.01) {  //tan graphs looks wrong
				if(i != 0) {
					double val = e.setVariable("x", i).evaluate();
					if(val == Double.POSITIVE_INFINITY) {
						val = 1e100;
					} else if(val == Double.NEGATIVE_INFINITY) {
						val = 1e-100;
					}
					if(/*val > low && val < high &&*/ !Double.isNaN(val) && val != Double.POSITIVE_INFINITY && val != Double.NEGATIVE_INFINITY) {
						ySeries.add(val);
						xSeries.add(i);	
					}
				}
			}
			chart.addSeries("data", xSeries, ySeries);
			// 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());
		}
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			BitmapEncoder.saveBitmap(chart, os, BitmapFormat.PNG);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		channel.sendFile(new ByteArrayInputStream(os.toByteArray()), "graph.png").queue();	
		return new CommandResult(CommandResultType.SUCCESS);
	}
}
