package net.novucs.ftop.database;

import net.novucs.ftop.entity.IdentityCache;
import org.bukkit.entity.EntityType;

import java.sql.*;
import java.util.*;

public class FactionSpawnerModel {

    private static final String UPDATE = "UPDATE `faction_spawner_count` SET `count` = ? WHERE `id` = ?";
    private static final String INSERT = "INSERT INTO `faction_spawner_count` (`faction_id`, `spawner_id`, `count`) VALUES (?, ?, ?)";
    private static final String DELETE = "DELETE FROM `faction_spawner_count` WHERE `faction_id` = ?";

    private final List<Map.Entry<String, Integer>> insertionQueue = new LinkedList<>();
    private final IdentityCache identityCache;
    private final PreparedStatement update;
    private final PreparedStatement insert;
    private final PreparedStatement delete;

    private FactionSpawnerModel(IdentityCache identityCache, PreparedStatement update, PreparedStatement insert, PreparedStatement delete) {
        this.identityCache = identityCache;
        this.update = update;
        this.insert = insert;
        this.delete = delete;
    }

    public static FactionSpawnerModel of(Connection connection, IdentityCache identityCache) throws SQLException {
        PreparedStatement update = connection.prepareStatement(UPDATE);
        PreparedStatement insert = connection.prepareStatement(INSERT, Statement.RETURN_GENERATED_KEYS);
        PreparedStatement delete = connection.prepareStatement(DELETE);
        return new FactionSpawnerModel(identityCache, update, insert, delete);
    }

    public void executeBatch() throws SQLException {
        // Execute all batched update and insert operations.
        update.executeBatch();
        insert.executeBatch();
        delete.executeBatch();

        // Add newly created faction-spawner relations to the identity cache.
        ResultSet resultSet = insert.getGeneratedKeys();

        for (Map.Entry<String, Integer> entry : insertionQueue) {
            if (resultSet.next()) {
                int id = resultSet.getInt(1);
                String factionId = entry.getKey();
                int spawnerId = entry.getValue();
                identityCache.setFactionSpawnerId(factionId, spawnerId, id);
            }
        }

        resultSet.close();

        insertionQueue.clear();
    }

    public void close() throws SQLException {
        delete.close();
        update.close();
        insert.close();
    }

    public void addBatch(String factionId, Map<EntityType, Integer> spawners) throws SQLException {
        // Persist all spawner counters for this specific faction worth.
        for (Map.Entry<EntityType, Integer> entry : spawners.entrySet()) {
            EntityType spawner = entry.getKey();
            int count = entry.getValue();
            int spawnerId = identityCache.getSpawnerId(spawner.name());
            addBatch(factionId, spawnerId, count);
        }
    }

    public void addBatch(String factionId, int spawnerId, int count) throws SQLException {
        Integer relationId = identityCache.getFactionSpawnerId(factionId, spawnerId);
        Map.Entry<String, Integer> insertionKey = new AbstractMap.SimpleImmutableEntry<>(factionId, spawnerId);

        if (relationId == null) {
            if (!insertionQueue.contains(insertionKey)) {
                insertCounter(factionId, spawnerId, count);
                insertionQueue.add(insertionKey);
            }
        } else {
            updateCounter(count, relationId);
        }
    }

    private void insertCounter(String factionId, int spawnerId, int count) throws SQLException {
        insert.setString(1, factionId);
        insert.setInt(2, spawnerId);
        insert.setInt(3, count);
        insert.addBatch();
    }

    private void updateCounter(int count, Integer relationId) throws SQLException {
        update.setInt(1, count);
        update.setInt(2, relationId);
        update.addBatch();
    }

    public void addBatchDelete(Collection<String> factions) throws SQLException {
        for (String factionId : factions) {
            addBatchDelete(factionId);
        }
    }

    public void addBatchDelete(String factionId) throws SQLException {
        delete.setString(1, factionId);
        delete.addBatch();
    }
}