package cat.nyaa.playtimetracker;

import com.google.common.io.LineReader;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class DatabaseManager {
    private final File dbFile;
    private Map<UUID, DatabaseRecord> recordMap;
    private final File recurrenceFile;
    public Map<UUID, Map<String, Long>> recurrenceMap; //Map<playerId, Map<ruleName, accumulatedTime-ms>>

    public DatabaseManager(File db_file, File recurFile) {
        dbFile = db_file;
        recurrenceFile = recurFile;
        recordMap = new HashMap<>();
        if (dbFile.isFile()) { // Read in
            ConfigurationSection cfg = YamlConfiguration.loadConfiguration(dbFile);
            for (String uuid_str : cfg.getKeys(false)) {
                ConfigurationSection sec = cfg.getConfigurationSection(uuid_str);
                UUID id = UUID.fromString(uuid_str);
                recordMap.put(id, DatabaseRecord.deserialize(id, sec));
            }
        }
        setupRecurrenceMap();
    }

    public DatabaseManager(File db_file, File old_db_file, File recurFile) {
        dbFile = db_file;
        recurrenceFile = recurFile;
        recordMap = new HashMap<>();
        if (old_db_file.isFile()) {
            FileReader fr = null;
            LineReader lr;
            try {
                fr = new FileReader(old_db_file);
                lr = new LineReader(fr);
                String line;
                while ((line = lr.readLine()) != null) {
                    String[] tmp = line.split(" ", 2);
                    if (tmp.length != 2) continue;
                    UUID a = null;
                    try {
                        a = UUID.fromString(tmp[0]);
                    } catch (IllegalArgumentException ex) {
                        Main.log("Illegal data line: " + line);
                        continue;
                    }
                    recordMap.put(a, DatabaseRecord.deserialize_legacy(a, tmp[1]));
                }
            } catch (IOException ex) {
                Main.log("Failed to parse legacy database");
                ex.printStackTrace();
            } finally {
                try {
                    if (fr != null) fr.close();
                } catch (IOException ex) {
                    Main.log("Failed to parse legacy database");
                    ex.printStackTrace();
                }
            }
        }
        setupRecurrenceMap();
    }

    private void setupRecurrenceMap() {
        recurrenceMap = new HashMap<>();
        if (recurrenceFile.isFile()) {
            ConfigurationSection cfg = YamlConfiguration.loadConfiguration(recurrenceFile);
            for (String uuid_str : cfg.getKeys(false)) {
                ConfigurationSection sec = cfg.getConfigurationSection(uuid_str);
                UUID id = UUID.fromString(uuid_str);
                Map<String, Long> ruleMap = new HashMap<>();
                for (String ruleName : sec.getKeys(false)) {
                    ruleMap.put(ruleName, sec.getLong(ruleName));
                }
                recurrenceMap.put(id, ruleMap);
            }
        }
    }

    public void save() {
        final Map<UUID, DatabaseRecord> clonedMap = new HashMap<>();
        final File clonedFile = new File(dbFile.toURI());
        for (Map.Entry<UUID, DatabaseRecord> entry : recordMap.entrySet()) {
            clonedMap.put(entry.getKey(), entry.getValue().clone());
        }
        final File clonedRecFile = new File(recurrenceFile.toURI());
        final Map<UUID, Map<String, Long>> clonedRecurrence = new HashMap<>();
        for (UUID id : recurrenceMap.keySet()) {
            Map<String, Long> tmp = new HashMap<>();
            tmp.putAll(recurrenceMap.get(id));
            clonedRecurrence.put(id, tmp);
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronizeSave(clonedFile, clonedMap, clonedRecFile, clonedRecurrence);
            }
        }).start();
    }

    public void synchronizeSave() {
        synchronizeSave(dbFile, recordMap, recurrenceFile, recurrenceMap);
    }

    private static synchronized void synchronizeSave(final File dbFile, final Map<UUID, DatabaseRecord> records,
                                                     final File recFile, final Map<UUID, Map<String, Long>> recurrenceMap) {
        YamlConfiguration cfg = new YamlConfiguration();
        for (UUID id : records.keySet()) {
            records.get(id).serialize(cfg.createSection(id.toString()));
        }
        try {
            cfg.save(dbFile);
        } catch (IOException ex) {
            System.out.print(">>>>>> PlayTimeTracker Database Emergency Dump <<<<<<\n" +
                    cfg.saveToString() +
                    "\n>>>>>> Emergency dump ends <<<<<<"
            );
            ex.printStackTrace();
        }

        cfg = new YamlConfiguration();
        for(UUID id : recurrenceMap.keySet()) {
            if (recurrenceMap.get(id).size() <= 0) continue;
            cfg.createSection(id.toString(), recurrenceMap.get(id));
        }
        try {
            cfg.save(recFile);
        } catch (IOException ex) {
            System.out.print(">>>>>> PlayTimeTracker Database Emergency Dump <<<<<<\n" +
                    cfg.saveToString() +
                    "\n>>>>>> Emergency dump ends <<<<<<"
            );
            ex.printStackTrace();
        }
    }

    public DatabaseRecord getRecord(UUID id) {
        return recordMap.get(id);
    }

    public void createRecord(UUID id, ZonedDateTime time) {
        recordMap.put(id, new DatabaseRecord(id, time));
    }

    public Map<UUID, DatabaseRecord> getAllRecords() {
        return recordMap;
    }

    public void setRecurrenceRule(String ruleName, UUID playerId) {
        Map<String, Long> tmp = recurrenceMap.get(playerId);
        if (tmp == null) {
            tmp = new HashMap<>();
            recurrenceMap.put(playerId, tmp);
        }
        tmp.put(ruleName, 0L);
    }
}