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


package engine.objects;

import engine.Enum.GameObjectType;
import engine.Enum.GridObjectType;
import engine.Enum.ItemType;
import engine.InterestManagement.WorldGrid;
import engine.exception.SerializationException;
import engine.gameManager.BuildingManager;
import engine.gameManager.DbManager;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.RemoveCorpseJob;
import engine.net.ByteBufferWriter;
import engine.net.DispatchMessage;
import engine.net.client.msg.UnloadObjectsMsg;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class Corpse extends AbstractWorldObject {

    private static AtomicInteger corpseCounter = new AtomicInteger(0);
    public JobContainer cleanup;
    private String firstName;
    private String lastName;
    private int level;
    private int belongsToType;
    private int belongsToID;
    private ArrayList<Item> inventory;
    private boolean asciiLastName = true;
    private boolean hasGold = false;
    private int inBuildingID = 0;
    private int inFloorID = -1;
    private int inBuilding = -1;

    /**
     * No Id Constructor
     */
    public Corpse(int newUUID, AbstractCharacter belongsTo, boolean safeZone, boolean enterWorld) {
        super(newUUID);
        this.setObjectType();
        this.inventory = new ArrayList<>();
        this.gridObjectType = GridObjectType.STATIC;
        this.setObjectTypeMask(MBServerStatics.MASK_CORPSE);
        if (belongsTo != null) {
            this.firstName = belongsTo.getFirstName();
            this.lastName = belongsTo.getLastName();
            this.asciiLastName = belongsTo.asciiLastName();
            this.level = belongsTo.getLevel();
            this.belongsToType = belongsTo.getObjectType().ordinal();
            this.belongsToID = belongsTo.getObjectUUID();
            this.inBuilding = belongsTo.getInBuilding();
            this.inFloorID = belongsTo.getInFloorID();
            this.inBuildingID = belongsTo.getInBuildingID();
            this.setLoc(belongsTo.getLoc());
        } else {
            Logger.error("No player passed in for corpse");
            this.firstName = "";
            this.lastName = "";
            this.level = 1;
            this.belongsToType = 0;
            this.belongsToID = 0;
        }
        this.setObjectTypeMask(MBServerStatics.MASK_CORPSE);

        if (!safeZone)
            transferInventory(belongsTo, enterWorld);


    }

    public static int getNextCorpseCount() {
        return Corpse.corpseCounter.addAndGet(2); //newUUID and runeID
    }

    //Create a new corpse
    public static Corpse makeCorpse(AbstractCharacter belongsTo, boolean enterWorld) {
        boolean safeZone = false;
        if (belongsTo != null && belongsTo.getObjectType() == GameObjectType.PlayerCharacter)
            safeZone = ((PlayerCharacter) belongsTo).isInSafeZone();


        Corpse corpse = new Corpse(Corpse.getNextCorpseCount(), belongsTo, safeZone, enterWorld);

        //create cleanup job
        if (corpse != null) {
            RemoveCorpseJob rcj = new RemoveCorpseJob(corpse);
            corpse.cleanup = JobScheduler.getInstance().scheduleJob(rcj, MBServerStatics.CORPSE_CLEANUP_TIMER_MS);
            DbManager.addToCache(corpse);
        }

        return corpse;
    }

    //Get existing corpse
    public static Corpse getCorpse(int newUUID) {
        return (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID);
    }

    //remove corpse from world
    public static void removeCorpse(int newUUID, boolean fromTimer) {
        Corpse c = (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID);
        if (c == null)
            Logger.error("No corpse found of ID " + newUUID);
        else
            Corpse.removeCorpse(c, fromTimer);
    }

    public static void removeCorpse(Corpse corpse, boolean fromTimer) {
        if (corpse == null)
            return;

        corpse.purgeInventory();

        //cleanup timer
        if (!fromTimer) {
            JobScheduler.getInstance().cancelScheduledJob(corpse.cleanup);
        }
        corpse.cleanup = null;

        //Remove from world
        UnloadObjectsMsg uom = new UnloadObjectsMsg();
        uom.addObject(corpse);
        DispatchMessage.sendToAllInRange(corpse, uom);
        WorldGrid.RemoveWorldObject(corpse);

        //clear from cache
        DbManager.removeFromCache(corpse);
    }

    public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer)
            throws SerializationException {
    }

    public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer, boolean aln)
            throws SerializationException {

        Building building = null;
        if (corpse.inBuildingID != 0)
            building = BuildingManager.getBuildingFromCache(corpse.inBuildingID);

        //Send Rune Count
        writer.putInt(0);
        writer.putInt(0);
        writer.putInt(1);

        //Send Corpse Rune
        writer.putInt(1);
        writer.putInt(0);
        writer.putInt(MBServerStatics.TOMBSTONE);
        writer.putInt(corpse.getObjectType().ordinal());
        writer.putInt((corpse.getObjectUUID() + 1));

        //Send Stats
        writer.putInt(5);
        writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID
        writer.putInt(5000);
        writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID
        writer.putInt(0);
        writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID
        writer.putInt(0);
        writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID
        writer.putInt(0);
        writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID
        writer.putInt(0);

        //Send Name
        writer.putString(corpse.firstName);
        if (aln && !corpse.asciiLastName)
            writer.putString("");
        else
            writer.putString(corpse.lastName);
        writer.putInt(0);
        writer.putInt(0);
        writer.putInt(0);
        writer.put((byte) 1);

        //Send Corpse Info
        writer.putInt(0);
        writer.putInt(corpse.getObjectType().ordinal());
        writer.putInt((corpse.getObjectUUID()));
        writer.putFloat(10f); //FaceDir or scale
        writer.putFloat(10); //FaceDir or scale
        writer.putFloat(10); //FaceDir or scale

        writer.putFloat(corpse.getLoc().x);
        writer.putFloat(corpse.getLoc().y);
        writer.putFloat(corpse.getLoc().z);

        writer.putFloat(6.235f); //1.548146f); //w
        writer.putInt(0);
        writer.putInt(0);

        //Send BelongsToInfo
        writer.putInt(((corpse.level / 10))); //Rank
        writer.putInt(corpse.level); //Level
        writer.putInt(1);
        writer.putInt(1);
        writer.putInt(1); //Missing this?
        writer.putInt(2);
        writer.putInt(1);
        //		writer.putInt(0); //not needed?
        writer.putInt(0);

        writer.putInt(corpse.belongsToType);
        writer.putInt(corpse.belongsToID);

        writer.putInt(0);
        writer.putInt(0);


        for (int i = 0; i < 9; i++)
            writer.putInt(0);
        writer.putShort((short) 0);
        writer.put((byte) 0);

        //Send Errant Guild Info
        for (int i = 0; i < 13; i++)
            writer.putInt(0);
        writer.putInt(16);
        writer.putInt(16);
        writer.putInt(16);
        writer.putInt(0);
        writer.putInt(0); //Missing this?
        writer.putInt(16);
        writer.putInt(16);
        writer.putInt(16);
        writer.putInt(0);
        writer.putInt(0);
        writer.putInt(0);

        //Send unknown counter
        writer.putInt(1);
        writer.putInt(0x047A0E67); //What is this?
        writer.put((byte) 0);

        //Send unknown
        writer.putInt(0);
        writer.putInt(0);
        writer.putFloat(1293.4449f); //Unknown
        writer.putFloat(-100f); //Unknown
        writer.putInt(0);
        writer.put((byte) 0);

        writer.put((byte) 0); //End datablock

    }

    public boolean removeItemFromInventory(Item item) {
        synchronized (this.inventory) {
            if (this.inventory.contains(item)) {
                this.inventory.remove(item);
                return true;
            }
            return false;
        }
    }

    public void transferInventory(AbstractCharacter belongsTo, boolean enterWorld) {
        if (belongsTo == null) {
            Logger.error("Can't find player that corpse " + this.getObjectUUID() + " belongs to");
            return;
        }

        //TODO transfer items from players inventory and trade window to corpse
        CharacterItemManager cim = belongsTo.getCharItemManager();
        if (cim != null)
            cim.transferEntireInventory(this.inventory, this, enterWorld);
        else
            Logger.error("Can't find inventory for player " + belongsTo.getObjectUUID());
    }

    public Item lootItem(Item i, PlayerCharacter looter) {
        //make sure looter exists
        if (looter == null)
            return null;

        //get looters item manager
        CharacterItemManager looterItems = looter.getCharItemManager();
        if (looterItems == null)
            return null;

        synchronized (this.inventory) {

            //make sure player has item in inventory
            if (!this.inventory.contains(i))
                return null;

            //get weight of item
            ItemBase ib = i.getItemBase();
            if (ib == null)
                return null;
            short weight = ib.getWeight();

            //make sure looter has room for item
            if (ib.getType().equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight))
                return null;

            //attempt to transfer item in db
            if (ib.getType().equals(ItemType.GOLD)) {
                if (!looterItems.moveGoldToInventory(i, i.getNumOfItems()))
                    return null;
            } else if (!i.moveItemToInventory(looter))
                return null;

            //db transfer successful, remove from this character
            this.inventory.remove(this.inventory.indexOf(i));
        }

        //add item to looter.
        if (!looterItems.addItemToInventory(i))
            return null;

        //calculate new weights
        looterItems.calculateInventoryWeight();

        return i;
    }

    public boolean hasGold() {
        return this.hasGold;
    }

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

    public ArrayList<Item> getInventory() {
        synchronized (this.inventory) {
            return this.inventory;
        }
    }

    /**
     * Delete and remove all items in the inventory
     */
    private void purgeInventory() {
        //make a copy so we're not inside synchronized{} while waiting for all items to be junked
        ArrayList<Item> inventoryCopy;
        synchronized (this.inventory) {
            inventoryCopy = new ArrayList<>(this.inventory);
            this.inventory.clear();
        }

        for (Item item : inventoryCopy) {
            item.junk();
        }
    }

    @Override
    public void updateDatabase() {
    }

    @Override
    public void runAfterLoad() {
    }

    public int getBelongsToType() {
        return this.belongsToType;
    }

    public int getBelongsToID() {
        return this.belongsToID;
    }

    @Override
    public String getName() {
        if (this.firstName.length() == 0) {
            return "Unknown corpse";
        }
        if (this.lastName.length() == 0) {
            return this.firstName;
        }
        return this.firstName + ' ' + this.lastName;
    }

    public int getInBuilding() {
        return inBuilding;
    }

    public void setInBuilding(int inBuilding) {
        this.inBuilding = inBuilding;
    }

    public int getInFloorID() {
        return inFloorID;
    }

    public void setInFloorID(int inFloorID) {
        this.inFloorID = inFloorID;
    }
}
