package me.despawningbone.arithtrain;

import java.awt.GraphicsEnvironment;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map.Entry;

import javafx.animation.FadeTransition;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * The initializing class for the arithmetic trainer.
 * @author despawningbone
 *
 */
public class Main extends Application {
	/**
	 * Global precision value for answers accepted for the trainer mode.
	 */
	public static int precision = 2;
	
	public static void main(String[] args) {  //no need to document this function in javadoc as it is the universal entry point of java programs
		System.setProperty("prism.lcdtext", "false");  //fix anti aliasing
		
		initConfig();
		
		File pfile = new File(System.getProperty("user.dir") + File.separator + "users.data");
		try {
			if (pfile.createNewFile()) {
				System.out.println("Cannot find user database, created a new one.");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		launch(args);
	}
	
	/**
	 * Initializes the welcome menu and the UIHelper for subsequent GUI pages.<br>
	 * See the parent method for more information.
	 * @param main the main window stage to operate on
	 */
	@Override
	public void start(Stage main) throws Exception {
		Font.loadFont(Main.class.getClassLoader().getResourceAsStream("resources/GothamBook.ttf"), 10);
		Font.loadFont(Main.class.getClassLoader().getResourceAsStream("resources/GothamBold.ttf"), 10);
		GraphicsEnvironment g = GraphicsEnvironment.getLocalGraphicsEnvironment(); //resize according to screen
		int height = g.getDefaultScreenDevice().getDisplayMode().getHeight() / 2;
		int width = g.getDefaultScreenDevice().getDisplayMode().getWidth() / 2;
		if(height <= width) width = height / 9 * 16;  //bind width to height with 16:9 so that 4:3 and 21:9 wont break the program
		else height = width / 16 * 9;   //bind to height if height is longer than width to avoid going out of bounds 
		
		UIHelper helper = new UIHelper(main, width, height);
		main.setTitle("Arithmetic Trainer");
		main.setResizable(false);  //to avoid issues with resizing; since the window is already proportional
		main.getIcons().add(new Image("resources/globe.png"));
		
		main.setHeight(height);
		main.setWidth(width);
		
		//welcome
		BorderPane welcome = new BorderPane();
		Text t = new Text("Welcome to the Arithmetic Trainer");
		t.setStyle("-fx-font-size: 3em; -fx-font-weight: bold;");
		t.setFill(Color.WHITE);
		welcome.setStyle("-fx-background-image: url(\"resources/bg-darken.png\"); -fx-background-size: cover;");
		welcome.setCenter(t);
		helper.display(welcome, true);
		main.show();
		
		//main menu
		
		
		//transition from welcome to main menu
		PauseTransition pause = new PauseTransition(Duration.millis(800));
		pause.setOnFinished(p -> {
			FadeTransition ft = new FadeTransition(Duration.millis(1200), t);
			ft.setFromValue(1.0);
			ft.setToValue(0.0);
			ft.play();
			ft.setOnFinished(f -> helper.showMainMenu());
		});
		pause.play();
	}
	
	
	/**
	 * Initializes the configuration, parsing the values into respective variables and generating one if one is not present.
	 */
	public static void initConfig() {
        try { 
			File pfile = new File(System.getProperty("user.dir") + File.separator + "trainer.conf");
			if (pfile.createNewFile()) {  //create config 
				BufferedWriter writer = new BufferedWriter(new FileWriter(pfile));
				writer.append("######################\n");
				writer.append("# Arithmetic Trainer  \n");
				writer.append("#  By despawningbone  \n");
				writer.append("#       v1.0          \n");
				writer.append("######################\n");
				writer.append("\n");
				writer.append("# At least how many decimal places should the precision be\n");
				writer.append("# for the answers inputted to be accepted? (Integer only)\n");
				writer.append("# This excludes answers that doesnt need to be rounded,\n");
				writer.append("# And does not alter the results in evaluation mode.\n");
				writer.append("Precision=2\n");
				writer.append("\n");
				writer.append("# What percentage will each operator appear?\n");
				writer.append("# They should add up to 100.\n");
				writer.append("# [+, -, *, /, %]\n");
				writer.append("OperatorWeight=35, 25, 17, 13, 10\n");
				writer.append("\n");
				writer.append("# How many levels should there be?\n");
				writer.append("# Each new line represent a level, with the top one being the first.\n");
				writer.append("# [requirement, bracketChance, operatorCount, lowerRange, upperRange]\n");
				writer.append("# where requirement = score * accuracy,\n");
				writer.append("# bracketChance = chance for brackets to appear (0-100),\n");
				writer.append("# operatorCount = how many operators the generated expression will have,\n");
				writer.append("# lowerRange & upperRange = how large the operand range will be (lower - upper inclusive).\n");
				writer.append("# only when the next level is harder and contains only Integer values (except for requirement) will it be valid.\n");
				writer.append("# The first level must start from 0.\n");
				writer.append("Levels:\n");
				writer.append("0, 0, 3, 0, 9\n");
				writer.append("50, 10, 4, -10, 20\n");
				writer.append("150, 20, 5, -20, 50\n");
				writer.close();
				System.out.println("Cannot find configuration, created a new one.");  //use debug?
				initLevels();
				initOperators();
			} else {  //parse config
				boolean levelsConfig = false;
				for(String line : Files.readAllLines(Paths.get(pfile.getPath()))) {
					if(line.isEmpty() || line.startsWith("#")) {  //either comments or new line
						continue;
					} else {
						if(line.contains("=")) {  //the 2 options
							String[] split = line.split("=");
							switch(split[0].toLowerCase()) {
							case "precision": 
								try {
									precision = Integer.parseInt(split[1].trim());
									if(precision < 0) {
										precision = 2;
										throw new NumberFormatException(); //pass to exception handler
									}
								} catch (NumberFormatException e) { 
									System.out.println("Invalid precision value! Using default... (2 d.p.)");
								}
								break;
							case "operatorweight":
								try {
									int total = 0;
									String[] vals = split[1].split(", ?");
									String[] ops = {"+", "-", "*", "/", "%"};
									for(int i = 0; i < 5; i++) {
										int weight = Integer.parseInt(vals[i]);
										ExpressionGenerator.operators.put(total, ops[i]);
										total += weight;
									}
									if(total != 100) {
										throw new NumberFormatException();
									}
								} catch (NumberFormatException e) {
									System.out.println("Invalid operator weights! Using default... (35, 25, 17, 13, 10)");
									initOperators();
								}
								break;
							}
						} else if(line.equalsIgnoreCase("Levels:")) {  //start of levels config section
							levelsConfig = true;
						} else if(levelsConfig) {  //levels config section
							try {
								String[] vals = line.split(", ?");
								double req = Double.parseDouble(vals[0]);
								ExpressionGenerator newGen = new ExpressionGenerator(Integer.parseInt(vals[1]), Integer.parseInt(vals[2]), Integer.parseInt(vals[3]), Integer.parseInt(vals[4]));
								Entry<Double, ExpressionGenerator> lowerGen = User.levels.floorEntry(req);
								Entry<Double, ExpressionGenerator> higherGen = User.levels.ceilingEntry(req);
								if((lowerGen == null || newGen.isHarder(lowerGen.getValue())) && (higherGen == null || higherGen.getValue().isHarder(newGen))) {  //verify if the level makes sense with the requirement
									User.levels.put(req, newGen);
								} else {
									throw new NumberFormatException();
								}
							} catch (NumberFormatException e) {
								System.out.println("Invalid levels! Using default...");
								initLevels();
							}
						}
					}
				}
				if(User.levels.isEmpty() || User.levels.firstKey() != 0) {
					System.out.println("Invalid levels! Using default...");
					User.levels.clear();
					initLevels();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Initializes the operator weights with default value. 
	 */
	static void initOperators() {
		int[] vals = {0, 35, 60, 77, 90};
		String[] ops = {"+", "-", "*", "/", "%"};
		for(int i = 0; i < 5; i++) {
			ExpressionGenerator.operators.put(vals[i], ops[i]);
		}
	}
	
	/**
	 * Initializes the levels with default value.
	 */
	static void initLevels() {
		User.levels.put((double) 0, new ExpressionGenerator(0, 3, 0 ,9));  //initialize levels
		User.levels.put((double) 50, new ExpressionGenerator(10, 4, -10, 20));
		User.levels.put((double) 150, new ExpressionGenerator(20, 5, -20, 50));
	}
}
