// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.db.archive;

import engine.gameManager.DbManager;
import engine.gameManager.ZoneManager;
import engine.math.Vector3fImmutable;
import engine.objects.Guild;
import engine.objects.PlayerCharacter;
import engine.objects.Zone;
import engine.workthreads.WarehousePushThread;
import org.pmw.tinylog.Logger;

import java.sql.*;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;

import static engine.Enum.DataRecordType;
import static engine.Enum.PvpHistoryType;

public class PvpRecord extends DataRecord {

    private static final LinkedBlockingQueue<PvpRecord> recordPool = new LinkedBlockingQueue<>();

    private PlayerCharacter player;
    private PlayerCharacter victim;
    private Vector3fImmutable location;
    private boolean pvpExp;

    private PvpRecord(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) {
        this.recordType = DataRecordType.PVP;
        this.player = player;
        this.victim = victim;
        this.location = new Vector3fImmutable(location);
        this.pvpExp = pvpExp;
    }

    public static PvpRecord borrow(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) {

        PvpRecord pvpRecord;

        pvpRecord = recordPool.poll();

        if (pvpRecord == null) {
            pvpRecord = new PvpRecord(player, victim, location, pvpExp);
        } else {
            pvpRecord.recordType = DataRecordType.PVP;
            pvpRecord.player = player;
            pvpRecord.victim = victim;
            pvpRecord.location = new Vector3fImmutable(location);
            pvpRecord.pvpExp = pvpExp;
        }

        return pvpRecord;
    }

    private static PreparedStatement buildHistoryStatement(Connection connection, int charUUID, PvpHistoryType historyType) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "";

        switch (historyType) {
            case KILLS:
                queryString = "SELECT DISTINCT `victim_id`, `datetime` FROM warehouse_pvphistory where char_id = ? " +
                        "ORDER BY `datetime` DESC LIMIT 10";
                break;
            case DEATHS:
                queryString = "SELECT DISTINCT `char_id`,`datetime` FROM warehouse_pvphistory where `victim_id` = ? " +
                        "ORDER BY `datetime` DESC LIMIT 10";
                break;
        }

        outStatement = connection.prepareStatement(queryString);
        outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID));

        return outStatement;
    }

    public static LinkedList<Integer> getCharacterPvPHistory(int charUUID, PvpHistoryType historyType) {

        // Member variable declaration

        LinkedList<Integer> outList = new LinkedList<>();

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildHistoryStatement(connection, charUUID, historyType);
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {

                switch (historyType) {
                    case KILLS:
                        outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("victim_id"))[0]);
                        break;
                    case DEATHS:
                        outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("char_id"))[0]);
                        break;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return outList;
    }

    private static PreparedStatement buildLuaHistoryQueryStatement(Connection connection, int charUUID) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "CALL `pvpHistory`(?)";

        outStatement = connection.prepareStatement(queryString);
        outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID));

        return outStatement;
    }

    public static String getPvpHistoryString(int charUUID) {

        String outString;
        String dividerString;

        String newLine = System.getProperty("line.separator");

        outString = "[LUA_PVP() DATA WAREHOUSE]" + newLine;
        dividerString = "--------------------------------" + newLine;

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildLuaHistoryQueryStatement(connection, charUUID);
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {

                int killCount;
                int deathCount;
                float killRatio;

                outString += "Total Magicbane murdered souls: " + rs.getInt("TOTALDEATHS") + newLine;
                outString += dividerString;
                outString += String.format("%-8s %-8s %-8s %-8s %n", "Period", "Kills", "Deaths", "K/D");
                outString += dividerString;

                killCount = rs.getInt("KILLCOUNT");
                deathCount = rs.getInt("DEATHCOUNT");

                if (deathCount == 0)
                    killRatio = (float) killCount;
                else
                    killRatio = (float) killCount / deathCount;

                try {
                    outString += String.format("%-8s %-8d %-8d %.2f %n", "Total", killCount, deathCount, killRatio);

                    killCount = rs.getInt("DAILYKILLS");
                    deathCount = rs.getInt("DAILYDEATHS");

                    if (deathCount == 0)
                        killRatio = (float) killCount;
                    else
                        killRatio = (float) killCount / deathCount;

                    outString += String.format("%-8s %-8d %-8d %.2f %n", "24hrs", killCount, deathCount, killRatio);

                    killCount = rs.getInt("HOURLYKILLS");
                    deathCount = rs.getInt("HOURLYDEATHS");

                    if (deathCount == 0)
                        killRatio = (float) killCount;
                    else
                        killRatio = (float) killCount / deathCount;

                    outString += String.format("%-8s %-8d %-8d %.2f %n", "1hr", killCount, deathCount, killRatio);
                } catch (Exception e) {
                    Logger.error(e.toString());
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return outString;
    }

    public static PreparedStatement buildPvpPushStatement(Connection connection, ResultSet rs) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "INSERT INTO `warehouse_pvphistory` (`event_number`, `char_id`, `char_guild_id`, `char_nation_id`, `char_level`," +
                " `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," +
                " `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " +
                " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

        outStatement = connection.prepareStatement(queryString);

        // Bind record data

        outStatement.setInt(1, rs.getInt("event_number"));
        outStatement.setString(2, rs.getString("char_id"));
        outStatement.setString(3, rs.getString("char_guild_id"));
        outStatement.setString(4, rs.getString("char_nation_id"));
        outStatement.setInt(5, rs.getInt("char_level"));

        // Bind victim data

        outStatement.setString(6, rs.getString("victim_id"));
        outStatement.setString(7, rs.getString("victim_guild_id"));
        outStatement.setString(8, rs.getString("victim_nation_id"));
        outStatement.setInt(9, rs.getInt("victim_level"));

        outStatement.setString(10, rs.getString("zone_id"));
        outStatement.setString(11, rs.getString("zone_name"));
        outStatement.setFloat(12, rs.getFloat("loc_x"));
        outStatement.setFloat(13, rs.getFloat("loc_y"));
        outStatement.setBoolean(14, rs.getBoolean("gave_exp"));
        outStatement.setTimestamp(15, rs.getTimestamp("datetime"));

        return outStatement;
    }

    public static PreparedStatement buildPvpQueryStatement(Connection connection) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "SELECT * FROM `warehouse_pvphistory` WHERE `event_number` > ?";
        outStatement = connection.prepareStatement(queryString);
        outStatement.setInt(1, WarehousePushThread.pvpIndex);
        return outStatement;
    }

    void reset() {
        this.player = null;
        this.victim = null;
        this.location = Vector3fImmutable.ZERO;
        pvpExp = false;
    }

    public void release() {
        this.reset();
        recordPool.add(this);
    }

    private PreparedStatement buildPvPInsertStatement(Connection connection) throws SQLException {

        Guild charGuild;
        Guild victimGuild;
        Zone zone;
        PreparedStatement outStatement = null;

        String queryString = "INSERT INTO `warehouse_pvphistory` (`char_id`, `char_guild_id`, `char_nation_id`, `char_level`," +
                " `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," +
                " `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " +
                " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

        outStatement = connection.prepareStatement(queryString);

        charGuild = this.player.getGuild();
        victimGuild = this.victim.getGuild();

        // Use a proxy in the situation where a char guild is null (errant)


        // Retrieve the zone name where the PvP event occurred

        zone = ZoneManager.findSmallestZone(this.location);

        outStatement.setString(1, DataWarehouse.hasher.encrypt(this.player.getObjectUUID()));
        outStatement.setString(2, DataWarehouse.hasher.encrypt(charGuild.getObjectUUID()));
        outStatement.setString(3, DataWarehouse.hasher.encrypt(charGuild.getNation().getObjectUUID()));
        outStatement.setInt(4, this.player.getLevel());

        // Bind victim data

        outStatement.setString(5, DataWarehouse.hasher.encrypt(this.victim.getObjectUUID()));
        outStatement.setString(6, DataWarehouse.hasher.encrypt(victimGuild.getObjectUUID()));
        outStatement.setString(7, DataWarehouse.hasher.encrypt(victimGuild.getNation().getObjectUUID()));
        outStatement.setInt(8, this.victim.getLevel());

        outStatement.setString(9, DataWarehouse.hasher.encrypt(zone.getObjectUUID()));
        outStatement.setString(10, zone.zoneName);
        outStatement.setFloat(11, this.location.getX());
        outStatement.setFloat(12, -this.location.getZ()); // flip sign on 'y' coordinate
        outStatement.setBoolean(13, this.pvpExp);
        outStatement.setTimestamp(14, Timestamp.valueOf(LocalDateTime.now()));

        return outStatement;
    }


    public void write() {

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildPvPInsertStatement(connection)) {

            statement.execute();

        } catch (SQLException e) {
            Logger.error(e.toString());
        }

        // Warehouse record for this pvp event written if code path reaches here.
        // Time to update the respective kill counters.

        CharacterRecord.advanceKillCounter(this.player);
        CharacterRecord.advanceDeathCounter(this.victim);

    }
}