package dev.peerat.backend.repository;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dev.peerat.backend.Configuration;

public class DatabaseRepository extends BaseDatabaseQuery{

	private Connection con;	//refractor chain
	private Configuration config;
	
	private DatabasePuzzleRepository puzzleRepo;
	private DatabaseChapterRepository chapterRepo;
	private DatabaseAdminRepository adminRepo;
	private DatabaseAuthRepository authRepo;
	private DatabaseBadgeRepository badgeRepo;
	private DatabaseCompletionRepository completionRepo;
	private DatabaseGroupRepository groupRepo;
	private DatabaseLeaderboardRepository leaderboardRepo;
	private DatabasePlayerRepository playerRepo;

	public DatabaseRepository(ConnectionManager con, Configuration config) throws Exception {
		super(con);
		this.config = config;
		
		this.puzzleRepo = new DatabasePuzzleRepository(con, config);
		this.chapterRepo = new DatabaseChapterRepository(con, config, puzzleRepo);
		this.adminRepo = new DatabaseAdminRepository(con, config);
		this.authRepo = new DatabaseAuthRepository(con, config);
		this.badgeRepo = new DatabaseBadgeRepository(con, config);
		this.completionRepo = new DatabaseCompletionRepository(con, config, chapterRepo);
		this.groupRepo = new DatabaseGroupRepository(con, config);
		this.leaderboardRepo = new DatabaseLeaderboardRepository(con, config);
		this.playerRepo = new DatabasePlayerRepository(con, config);
		
		loadConfig(config);
	}
	
	private void loadConfig(Configuration config) throws Exception{
		String name = DatabaseRepository.class.getPackage().getName();
		InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(name.replace(".", "/"));
		File folder = new File(config.getSqlFolder());
		try{
			BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
			String line;
			while((line = reader.readLine()) != null){
				if(line.endsWith(".class")){
					Class<?> clazz = Class.forName(name+"."+line.substring(0, line.length()-6));
					if(BaseDatabaseQuery.class.isAssignableFrom(clazz)){
						for(Class<?> subClazz : clazz.getDeclaredClasses()){
							if(subClazz.isEnum()){
								configure(subClazz, folder);
							}
						}
					}
					continue;
				}
			}
			reader.close();
		}catch(Exception e){
			System.err.println("Failed to read "+name);
			e.printStackTrace();
		}
		new Thread(() -> {
			try{
				WatchService watchService = FileSystems.getDefault().newWatchService();
				Path path = folder.toPath();
				path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
				while (true) {
	                WatchKey key = watchService.take();
	                for(WatchEvent<?> event : key.pollEvents()){
	                	Path edited = (Path) event.context();
	                	String targetClazz = edited.toFile().getName().split("\\.")[0];
	                	Class<?> clazz = Class.forName(name+"."+targetClazz);
	                	if(BaseDatabaseQuery.class.isAssignableFrom(clazz)){
							for(Class<?> subClazz : clazz.getDeclaredClasses()){
								if(subClazz.isEnum()){
									bind(subClazz, new File(folder, edited.toFile().getName()));
								}
							}
						}
	                }
	                key.reset();
				}
			}catch(Exception ex){
				ex.printStackTrace();
			}
		}).start();
	}
	
	private void configure(Class<?> clazz, File folder) throws Exception{
		String name = clazz.getName().split("\\$")[0];
		String[] split = name.split("\\.");
		File file = new File(folder, split[split.length-1]+".txt");
		if(file.exists()){
			bind(clazz, file);
		}else{
			File parent = file.getParentFile();
			if(!parent.exists()) parent.mkdirs();
			file.createNewFile();
			BufferedWriter writer = new BufferedWriter(new FileWriter(file));
			for(Object obj : clazz.getEnumConstants()){
				Enum instance = (Enum) obj;
				writer.write(instance.name()+"="+instance.toString().replace("\n", " ").replace("\r", " ")+"\n");
			}
			writer.flush();
			writer.close();
		}
	}
	
	private void bind(Class<?> clazz, File file) throws Exception{
		Map<String, String> map = new HashMap<>();
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line;
		while((line = reader.readLine()) != null){
			int index = line.indexOf('=');
			String key = line.substring(0, index);
			String value = line.substring(index+1);
			map.put(key, value);
		}
		reader.close();
		
		for(Object obj : clazz.getEnumConstants()){
			Enum instance = (Enum) obj;
			String value = map.get(instance.name());
			Field field = obj.getClass().getDeclaredField("request");
			field.setAccessible(true);
			field.set(instance, value);
		}
	}
	
	public DatabasePuzzleRepository getPuzzleRepository(){
		return this.puzzleRepo;
	}
	
	public DatabaseChapterRepository getChapterRepository(){
		return this.chapterRepo;
	}
	
	public DatabaseAdminRepository getAdminRepository(){
		return this.adminRepo;
	}
	
	public DatabaseAuthRepository getAuthRepository(){
		return this.authRepo;
	}
	
	public DatabaseBadgeRepository getBadgeRepository(){
		return this.badgeRepo;
	}
	
	public DatabaseCompletionRepository getCompletionRepository(){
		return this.completionRepo;
	}
	
	public DatabaseGroupRepository getGroupRepository(){
		return this.groupRepo;
	}
	
	public DatabaseLeaderboardRepository getLeaderboardRepository(){
		return this.leaderboardRepo;
	}
	
	public DatabasePlayerRepository getPlayerRepository(){
		return this.playerRepo;
	}
	
	
	public void hotfix() throws Exception{
		ensureConnection();
		
		PreparedStatement stmt = con.prepareStatement("SELECT c.*, g.* FROM completions c JOIN puzzles p ON c.fk_puzzle = p.id_puzzle JOIN containsGroups cg ON cg.fk_player = c.fk_player JOIN groups g ON g.id_group = cg.fk_group WHERE g.fk_chapter = 12 AND p.fk_chapter = 12;");
		ResultSet results = stmt.executeQuery();
		List<Completionz> list = new ArrayList<>();
		while(results.next()){
			Completionz complete = new Completionz(results);
			list.add(complete);
		}
		Map<Integer, Map<Integer, Completionz>> map = new HashMap<>();
		for(Completionz comp : list){
			Map<Integer,Completionz> puz = map.get(comp.puzzle);
			if(puz == null){
				puz = new HashMap<>();
				map.put(comp.puzzle, puz);
			}
			Completionz c = puz.get(comp.groups);
			if(c == null){
				puz.put(comp.groups, comp);
			}else{
				if(comp.score >= c.score){
					puz.put(comp.groups, comp);
					System.out.println("remove compl "+c.id);
				}
			}
		}
	}
	
	private static class Completionz{
		
		public int id;
		public int puzzle;
		public int player;
		public int tries;
		public int score;
		public int groups;
		
		public Completionz(ResultSet result) throws Exception{
			id = result.getInt("id_completion");
			puzzle = result.getInt("fk_puzzle");
			player = result.getInt("fk_player");
			tries = result.getInt("tries");
			score = result.getInt("score");
			groups = result.getInt("id_group");
			System.out.println(id);
		}
		
	}

}