package net.novucs.ftop.database;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.novucs.ftop.WorthType;
import net.novucs.ftop.entity.ChunkPos;
import net.novucs.ftop.entity.ChunkWorth;
import net.novucs.ftop.entity.IdentityCache;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

public class ChunkLoader {

    private static final String SELECT_CHUNK = "SELECT * FROM `chunk`";
    private static final String SELECT_CHUNK_MATERIAL = "SELECT * FROM `chunk_material_count`";
    private static final String SELECT_CHUNK_SPAWNER = "SELECT * FROM `chunk_spawner_count`";
    private static final String SELECT_CHUNK_WORTH = "SELECT * FROM `chunk_worth`";

    private final IdentityCache identityCache;
    private final PreparedStatement selectChunk;
    private final PreparedStatement selectChunkMaterial;
    private final PreparedStatement selectChunkSpawner;
    private final PreparedStatement selectChunkWorth;

    private ChunkLoader(IdentityCache identityCache,
                        PreparedStatement selectChunk,
                        PreparedStatement selectChunkMaterial,
                        PreparedStatement selectChunkSpawner,
                        PreparedStatement selectChunkWorth) {
        this.identityCache = identityCache;
        this.selectChunk = selectChunk;
        this.selectChunkMaterial = selectChunkMaterial;
        this.selectChunkSpawner = selectChunkSpawner;
        this.selectChunkWorth = selectChunkWorth;
    }

    public static ChunkLoader of(Connection connection, IdentityCache identityCache) throws SQLException {
        PreparedStatement selectChunk = connection.prepareStatement(SELECT_CHUNK);
        PreparedStatement selectChunkMaterial = connection.prepareStatement(SELECT_CHUNK_MATERIAL);
        PreparedStatement selectChunkSpawner = connection.prepareStatement(SELECT_CHUNK_SPAWNER);
        PreparedStatement selectChunkWorth = connection.prepareStatement(SELECT_CHUNK_WORTH);
        return new ChunkLoader(identityCache, selectChunk, selectChunkMaterial, selectChunkSpawner, selectChunkWorth);
    }

    public Map<ChunkPos, ChunkWorth> load() throws SQLException {
        Map<ChunkPos, ChunkWorth> target = new HashMap<>();
        Map<Integer, ChunkPos> chunks = loadChunk();
        Table<Integer, Material, Integer> globalMaterialCount = loadChunkMaterial();
        Table<Integer, EntityType, Integer> globalSpawnerCount = loadChunkSpawner();
        Table<Integer, WorthType, Double> globalWorth = loadChunkWorth();

        for (Map.Entry<Integer, ChunkPos> entry : chunks.entrySet()) {
            int chunkId = entry.getKey();

            Map<Material, Integer> chunkMaterialCount = new EnumMap<>(Material.class);
            chunkMaterialCount.putAll(globalMaterialCount.row(chunkId));

            Map<EntityType, Integer> chunkSpawnerCount = new EnumMap<>(EntityType.class);
            chunkSpawnerCount.putAll(globalSpawnerCount.row(chunkId));

            Map<WorthType, Double> chunkWorth = new EnumMap<>(WorthType.class);
            chunkWorth.putAll(globalWorth.row(chunkId));

            ChunkPos chunk = entry.getValue();
            ChunkWorth worth = new ChunkWorth(chunkWorth, chunkMaterialCount, chunkSpawnerCount);

            target.put(chunk, worth);
        }

        return target;
    }

    public void close() throws SQLException {
        selectChunk.close();
        selectChunkMaterial.close();
        selectChunkSpawner.close();
        selectChunkWorth.close();
    }

    private Map<Integer, ChunkPos> loadChunk() throws SQLException {
        Map<Integer, ChunkPos> target = new HashMap<>();
        ResultSet resultSet = selectChunk.executeQuery();

        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            int worldId = resultSet.getInt("world_id");
            int x = resultSet.getInt("x");
            int z = resultSet.getInt("z");

            identityCache.setChunkPosId(worldId, x, z, id);
            identityCache.getWorldName(worldId).ifPresent((worldName) ->
                    target.put(id, ChunkPos.of(worldName, x, z)));
        }

        resultSet.close();
        return target;
    }

    private Table<Integer, Material, Integer> loadChunkMaterial() throws SQLException {
        Table<Integer, Material, Integer> target = HashBasedTable.create();
        ResultSet resultSet = selectChunkMaterial.executeQuery();

        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            int chunkId = resultSet.getInt("chunk_id");
            int materialId = resultSet.getInt("material_id");
            int count = resultSet.getInt("count");

            identityCache.setChunkMaterialId(chunkId, materialId, id);
            identityCache.getMaterial(materialId).ifPresent(material ->
                    target.put(chunkId, material, count));
        }

        resultSet.close();
        return target;
    }

    private Table<Integer, EntityType, Integer> loadChunkSpawner() throws SQLException {
        Table<Integer, EntityType, Integer> target = HashBasedTable.create();
        ResultSet resultSet = selectChunkSpawner.executeQuery();

        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            int chunkId = resultSet.getInt("chunk_id");
            int spawnerId = resultSet.getInt("spawner_id");
            int count = resultSet.getInt("count");

            identityCache.setChunkSpawnerId(chunkId, spawnerId, id);
            identityCache.getSpawner(spawnerId).ifPresent(spawner ->
                    target.put(chunkId, spawner, count));
        }

        resultSet.close();
        return target;
    }

    private Table<Integer, WorthType, Double> loadChunkWorth() throws SQLException {
        Table<Integer, WorthType, Double> target = HashBasedTable.create();
        ResultSet resultSet = selectChunkWorth.executeQuery();

        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            int chunkId = resultSet.getInt("chunk_id");
            int worthId = resultSet.getInt("worth_id");
            double worth = resultSet.getDouble("worth");

            identityCache.setChunkWorthId(chunkId, worthId, id);
            identityCache.getWorthType(worthId).ifPresent(worthType ->
                    target.put(chunkId, worthType, worth));
        }

        resultSet.close();
        return target;
    }
}