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


package engine.net.client.msg;

import engine.Enum;
import engine.gameManager.DbManager;
import engine.net.AbstractConnection;
import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter;
import engine.net.Network;
import engine.net.client.Protocol;
import engine.objects.AbstractGameObject;
import engine.objects.City;
import engine.objects.Mine;
import engine.objects.Runegate;
import engine.session.Session;
import org.pmw.tinylog.Logger;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

public class CityDataMsg extends ClientNetMsg {

    public static final long wdComp = 0xFF00FF0000000003L;
    private static final byte ver = 1;
    private static ByteBuffer cachedEnterWorld;
    private static long cachedExpireTime;
    private final boolean forEnterWorld;
    private Session s;
    private boolean updateCities = false;
    private boolean updateRunegates = false;
    private boolean updateMines = false;

    /**
     * This is the general purpose constructor.
     *
     * @param s             Session
     * @param forEnterWorld boolean flag
     */
    public CityDataMsg(Session s, boolean forEnterWorld) {
        super(Protocol.CITYDATA);
        this.s = s;
        this.forEnterWorld = forEnterWorld;
    }

    public CityDataMsg(boolean updateCities, boolean updateRunegates, boolean updateMines) {
        super(Protocol.CITYDATA);
        this.s = null;
        this.forEnterWorld = false;
        this.updateCities = updateCities;
        this.updateRunegates = updateRunegates;
        this.updateMines = updateMines;
    }

    /**
     * This constructor is used by NetMsgFactory. It attempts to deserialize the
     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
     * past the limit) then this constructor Throws that Exception to the
     * caller.
     */
    public CityDataMsg(AbstractConnection origin, ByteBufferReader reader) {
        super(Protocol.CITYDATA, origin, reader);
        this.forEnterWorld = false;
    }

    private static void attemptSerializeForEnterWorld(ByteBuffer bb) {
        bb.clear();
        ByteBufferWriter temp = new ByteBufferWriter(bb);
        temp.put((byte) 0); // PAD


        ArrayList<City> cityList = new ArrayList<>();
        ConcurrentHashMap<Integer, AbstractGameObject> map = DbManager.getMap(Enum.GameObjectType.City);
        for (AbstractGameObject ago : map.values())

            if (ago.getObjectType().equals(Enum.GameObjectType.City))
                cityList.add((City) ago);

        temp.putInt(cityList.size());

        for (City city : cityList)
            City.serializeForClientMsg(city, temp);
        temp.put((byte) 0); // PAD

        // Serialize runegates

        temp.putInt(Runegate._runegates.values().size());

        for (Runegate runegate : Runegate._runegates.values()) {
            runegate._serializeForEnterWorld(temp);
        }

        ArrayList<Mine> mineList = new ArrayList<>();
        for (Mine toAdd : Mine.mineMap.keySet()) {
            mineList.add(toAdd);
        }

        temp.putInt(mineList.size());
        for (Mine mine : mineList) {
            if(mine.getParentZone() != null && !mine.getParentZone().isContinent())
                Mine.serializeForClientMsg(mine, temp);
        }
        temp.put((byte) 0); // PAD
    }

    @Override
    protected int getPowerOfTwoBufferSize() {
        return (18); // 2^14 == 16384
    }

    /**
     * Serializes the subclass specific items to the supplied NetMsgWriter.
     */
    @Override
    protected void _serialize(ByteBufferWriter writer) {
        if (this.forEnterWorld)
            serializeForEnterWorld(writer);
        else
            serializeForMapUpdate(writer);
    }

    /**
     * Specific use serializer
     *
     * @param writer
     */
    private void serializeForMapUpdate(ByteBufferWriter writer) {

        //Handle City updates

        if (this.updateCities) {
            writer.put((byte) 0);
            ArrayList<City> cityList = new ArrayList<>();
            ConcurrentHashMap<Integer, AbstractGameObject> map = DbManager.getMap(Enum.GameObjectType.City);
            if (map != null) {
                for (AbstractGameObject ago : map.values())
                    if (ago.getObjectType().equals(Enum.GameObjectType.City))
                        cityList.add((City) ago);

                writer.putInt(cityList.size());
                for (City city : cityList) {
                    City.serializeForClientMsg(city, writer);
                }

            } else {
                Logger.error("missing city map");
                writer.putInt(0);
            }
        } else
            writer.put((byte) 1);


        //Handle Runegate updates
        if (this.updateRunegates) {

            writer.put((byte) 0);
            writer.putInt(Runegate._runegates.values().size());

            for (Runegate runegate : Runegate._runegates.values()) {
                runegate._serializeForEnterWorld(writer);
            }
        } else
            writer.put((byte) 1);


        //Handle Mine updates
        try {
            if (this.updateMines) {
                ArrayList<Mine> mineList = new ArrayList<>();
                for (Mine toAdd : Mine.mineMap.keySet()) {
                    mineList.add(toAdd);
                }

                writer.putInt(mineList.size());
                for (Mine mine : mineList) {
                    if(mine.getParentZone() != null && !mine.getParentZone().isContinent())
                        Mine.serializeForClientMsg(mine, writer);
                }
            } else
                writer.putInt(0);
        } catch (Exception e) {
            Logger.error(e);
        }


        writer.put((byte) 0); // PAD
    }

    /**
     * Specific use serializer
     *
     * @param writer
     */
    private void serializeForEnterWorld(ByteBufferWriter writer) {
        if (s == null || s.getPlayerCharacter() == null)
            return;

        long startT = System.currentTimeMillis();

        if (cachedEnterWorld == null) {
            // Never before been cached, so init stuff
            cachedEnterWorld = Network.byteBufferPool.getBuffer(19);
            cachedExpireTime = 0L;
        }

        //Check to see if its time to renew cache.
        if (cachedExpireTime < System.currentTimeMillis()) {
            synchronized (cachedEnterWorld) {
                CityDataMsg.attemptSerializeForEnterWorld(cachedEnterWorld);
            }
            cachedExpireTime = startT + 60000;
        }

        writer.putBB(cachedEnterWorld);

    }

    /**
     * Deserializes the subclass specific items from the supplied NetMsgReader.
     */
    @Override
    protected void _deserialize(ByteBufferReader reader) {
        // Client only sends 11 bytes.

        byte type = reader.get();

        if (type == 1) {
            reader.get();
            reader.get();
            reader.getInt();

        } else {
            reader.get();
            reader.getInt();
            reader.get();
            reader.getInt();
        }

    }

    /**
     * @return the s
     */
    public Session getS() {
        return s;
    }

    /**
     * @return the forEnterWorld
     */
    public boolean isForEnterWorld() {
        return forEnterWorld;
    }

    public void updateCities(boolean value) {
        this.updateCities = value;
    }

    public void updateRunegates(boolean value) {
        this.updateRunegates = value;
    }

    public void updateMines(boolean value) {
        this.updateMines = value;
    }


}