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


package engine.db.archive;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import engine.gameManager.ConfigManager;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.LinkedBlockingQueue;

import static engine.Enum.DataRecordType;

public class DataWarehouse implements Runnable {

    public static final Hasher hasher = new Hasher("Cthulhu Owns Joo");
    private static final LinkedBlockingQueue<DataRecord> recordQueue = new LinkedBlockingQueue<>();
    public static HikariDataSource connectionPool = null;
    public static HikariDataSource remoteConnectionPool = null;

    public DataWarehouse() {

        Logger.info("Configuring local Database Connection Pool...");

        configureConnectionPool();

        // If WarehousePush is disabled
        // then early exit

        if ( ConfigManager.MB_WORLD_WAREHOUSE_PUSH.getValue().equals("false")) {
            Logger.info("Warehouse Remote Connection disabled along with push");
            return;
        }

        Logger.info( "Configuring remote Database Connection Pool...");
        configureRemoteConnectionPool();

    }

    public static void bootStrap() {
        Thread warehousingThread;
        warehousingThread = new Thread(new DataWarehouse());

        warehousingThread.setName("DataWarehouse");
        warehousingThread.setPriority(Thread.NORM_PRIORITY - 1);
        warehousingThread.start();
    }

    public static void pushToWarehouse(DataRecord dataRecord) {

        DataWarehouse.recordQueue.add(dataRecord);
    }

    public static void writeHash(DataRecordType recordType, int uuid) {

        // Member variable declaration

        Connection connection = null;
        PreparedStatement statement = null;
        String queryString;
        String hashString;

        try {
            connection = DataWarehouse.connectionPool.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        if (connection == null) {
            Logger.error("Null connection when writing zone hash.");
            return;
        }

        // Build query string

        switch (recordType) {
            case CHARACTER:
                queryString = "UPDATE `obj_character` SET hash = ? WHERE `UID` = ?";
                break;
            case GUILD:
                queryString = "UPDATE `obj_guild` SET hash = ? WHERE `UID` = ?";
                break;
            case ZONE:
                queryString = "UPDATE `obj_zone` SET hash = ? WHERE `UID` = ?";
                break;
            case CITY:
                queryString = "UPDATE `obj_city` SET hash = ? WHERE `UID` = ?";
                break;
            case REALM:
                queryString = "UPDATE `obj_realm` SET hash = ? WHERE `realmID` = ?";
                break;
            default:
                queryString = null;
                break;
        }

        hashString = hasher.encrypt(uuid);

        // Write this record to the warehouse

        try {

            statement = connection.prepareStatement(queryString);

            statement.setString(1, hashString);
            statement.setLong(2, uuid);
            statement.execute();
        } catch (SQLException e) {
            Logger.error("Error writing hash for uuid" + uuid + " of type " + recordType.name() + ' ' + e.toString());
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static boolean recordExists(DataRecordType recordType, int uuid) {

        // Member variable declaration

        Connection connection = null;
        PreparedStatement statement = null;
        String queryString;
        ResultSet resultSet;

        try {
            connection = DataWarehouse.connectionPool.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        if (connection == null) {
            Logger.error("Null connection during char record lookup");
            return true;  // False positive here, so as not to try and write the record twice.
            // will refactor out once we write hashes to object tables
        }

        // Build query string

        switch (recordType) {
            case CHARACTER:
                queryString = "SELECT COUNT(*) from warehouse_characterhistory where char_id = ?";
                break;
            case GUILD:
                queryString = "SELECT COUNT(*) from warehouse_guildhistory where guild_id = ?";
                break;
            case CITY:
                queryString = "SELECT COUNT(*) from warehouse_cityhistory where city_id = ?";
                break;
            case REALM:
                queryString = "SELECT COUNT(*) from warehouse_realmhistory where realm_id = ?";
                break;
            case BANE:
                queryString = "SELECT COUNT(*) from warehouse_banehistory where city_id = ? AND `resolution` = 'PENDING'";
                break;
            case ZONE: // Does not really exist but enum acts as a proxy for hash lookup
            case MINE: // Does not really exist but enum acts as a proxy for hash lookup
            default:
                queryString = null;
                break;
        }

        try {
            statement = connection.prepareStatement(queryString);
            statement.setString(1, DataWarehouse.hasher.encrypt(uuid));
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                return resultSet.getInt("COUNT(*)") > 0;
            }

        } catch (SQLException e) {
            Logger.error("Error in record lookup for " + recordType.name() + " of uuid:" + uuid + e.toString());
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    public void run() {

        // Working variable set

        DataRecord dataRecord;
        PvpRecord pvpRecord;
        GuildRecord guildRecord;
        CharacterRecord characterRecord;
        CityRecord cityRecord;
        BaneRecord baneRecord;
        RealmRecord realmRecord;
        MineRecord mineRecord;

        Logger.info( "DataWarehouse is running.");

        while (true) {

            dataRecord = null;
            pvpRecord = null;
            guildRecord = null;
            characterRecord = null;
            cityRecord = null;
            baneRecord = null;
            realmRecord = null;
            mineRecord = null;

            try {
                dataRecord = recordQueue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Write record to appropriate warehousing table

            if (dataRecord != null) {

                switch (dataRecord.recordType) {
                    case PVP:
                        pvpRecord = (PvpRecord) dataRecord;
                        pvpRecord.write();
                        pvpRecord.release();
                        break;
                    case CHARACTER:
                        characterRecord = (CharacterRecord) dataRecord;
                        characterRecord.write();
                        characterRecord.release();
                        break;
                    case GUILD:
                        guildRecord = (GuildRecord) dataRecord;
                        guildRecord.write();
                        guildRecord.release();
                        break;
                    case CITY:
                        cityRecord = (CityRecord) dataRecord;
                        cityRecord.write();
                        cityRecord.release();
                        break;
                    case BANE:
                        baneRecord = (BaneRecord) dataRecord;
                        baneRecord.write();
                        baneRecord.release();
                        break;
                    case REALM:
                        realmRecord = (RealmRecord) dataRecord;
                        realmRecord.write();
                        realmRecord.release();
                        break;
                    case MINE:
                        mineRecord = (MineRecord) dataRecord;
                        mineRecord.write();
                        mineRecord.release();
                        break;
                    default:
                        Logger.error( "Unhandled record type");
                        break;

                } // end switch
            }
        }
    }

    private static void configureConnectionPool() {

        HikariConfig config = new HikariConfig();

        config.setMaximumPoolSize(10);

        config.setJdbcUrl("jdbc:mysql://" +  ConfigManager.MB_DATABASE_ADDRESS.getValue() +
                ":" +  ConfigManager.MB_DATABASE_PORT.getValue() + "/" +
                ConfigManager.MB_DATABASE_NAME.getValue());
        config.setUsername(ConfigManager.MB_DATABASE_USER.getValue());
        config.setPassword( ConfigManager.MB_DATABASE_PASS.getValue());
        config.addDataSourceProperty("characterEncoding", "utf8");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

        connectionPool = new HikariDataSource(config); // setup the connection pool

        Logger.info("Local warehouse database connection configured");
    }

    private static void configureRemoteConnectionPool() {

        HikariConfig config = new HikariConfig();

        config.setMaximumPoolSize(1); // Only the server talks to remote, so yeah.
        config.setJdbcUrl(ConfigManager.MB_WAREHOUSE_ADDR.getValue());
        config.setUsername(ConfigManager.MB_WAREHOUSE_USER.getValue());
        config.setPassword(ConfigManager.MB_WAREHOUSE_PASS.getValue());
        config.addDataSourceProperty("characterEncoding", "utf8");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

        remoteConnectionPool = new HikariDataSource(config); // setup the connection pool

        Logger.info("remote warehouse connection configured");
    }

}