package me.despawningbone.arithtrain;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.NoSuchElementException;

import org.junit.Test;

import junit.framework.TestCase;

public class UserTest extends TestCase {

	//no need to backup user.data since it will reside inside the test environment instead of usual locations that the trainer will be ran
	
	@Override
	public void setUp() {
		File pfile = new File(System.getProperty("user.dir") + File.separator + "users.data");
		try {
			if (!pfile.createNewFile()) {
				PrintWriter pw = new PrintWriter(pfile);  //cleans the user database for test cases
				pw.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Main.initLevels();  //have to init default config since actual config isnt loaded
	}
	
	@Test
	public void testUser() throws IOException {
		//all registers will internally instantiate User, therefore no need to instantiate again to test if user exists
		//normal cases
		User user1 = User.register("te$t", "Te$t1", "Te$t1");  //ASCII symbols
	
		//edge cases
		User user2 = User.register("te|st", "Test1", "Test1");  //delimiter escaping
		User user3 = User.register("ĦħĦħĦħĦħ", "ĦħTt1", "ĦħTt1");  //unicode characters
		
		assertTrue(Files.readAllLines(new File(System.getProperty("user.dir") + File.separator + "users.data").toPath(), StandardCharsets.UTF_8).size() == 3);
		
		assertEquals("te|st", user2.getName());  //check if delimiter escape succeeded
		assertEquals("ĦħĦħĦħĦħ", user3.getName());  //check if unicode can be get with getName correctly
		
		user1.updatePassword("Te$t1", "T$et1", "T$et1");  //uses same algorithm for matching passwords, so no need to double test
		new User("te$t", "T$et1");  //check if updated by login
		
		assertEquals(1.0, user3.getAccuracyRaw());
		
		for(int i = 0; i < 100; i++) {
			user1.updateScore(true);
			user2.updateScore(false);
		}
		assertEquals(2, user1.getLevel());  //according to default config, score 100 should be in level 2
		assertEquals(User.levels.floorEntry(100.0).getValue(), user1.getCurrentGenerator());
		assertEquals(100, user1.getScore());
		assertEquals(0.5, user1.getNextLevelPercent());
		assertEquals(0.0, user2.getAccuracy());
		
		user2.save();
		assertTrue(Files.readAllLines(new File(System.getProperty("user.dir") + File.separator + "users.data").toPath(), StandardCharsets.UTF_8).get(1).endsWith("0|100"));  //check if score update is reflected in database after save
		
		//invalid cases
		testUserThrows(() -> User.register("test", "Test1", "test1"));  //non matching passwords
		testUserThrows(() -> User.register("te$t", "Te$t1", "Te$t1"));  //registered user
		testUserThrows(() -> User.register("test", "test1", "test1"));  //not strong enough password
		testUserThrows(() -> User.register(null, null, null));  //null
		testUserThrows(() -> User.register("", "Test1", "Test1"));  //empty username; empty password doesnt need to be checked since it will violate password strength check anyways
				
		testUserThrows(() -> new User("te$t", "Test1"));  //wrong password
		testUserThrows(() -> new User("test", "Test1"));  //non-existent user
		testUserThrows(() -> new User(null, null));  //null
		
	}
	
	private void testUserThrows(ModifRunnable run) throws IOException {
		try {
			run.run();
		} catch(IllegalArgumentException | NoSuchElementException e) {
			return;
		}
		fail();
	}
	
	@FunctionalInterface public interface ModifRunnable { void run() throws IOException; }  //so that i dont need to handle IOExceptions internally but pass it to JUnit
}
