#include "Block.h"

#include "AddEntityUpdate.h"
#include "Game.h"
#include "Inventory.h"
#include "ItemEntity.h"
#include "MultiblockStructure.h"
#include "NoBlock.h"

Block::Block(int typeId,
    const ItemType* zTool,
    Framework::Vec3<int> pos,
    bool hasInventory)
    : Inventory(pos, hasInventory)
{
    transparent = false;
    passable = false;
    hp = 1;
    maxHP = 1;
    hardness = 1;
    this->typeId = typeId;
    this->zTool = zTool;
    speedModifier = 1;
    ticksLeftCounter = 0;
    wasTicked = 0;
    onTickCalled = 0;
    minTickTimeout = -1;
    maxTickTimeout = -1;
    tickSource = 0;
    currentTickTimeout = 0;
    dimensionId = 0;
    interactable = 0;
    deadAndRemoved = 0;
    memset(zNeighbours, 0, sizeof(Block*) * 6);
    memset(lightEmisionColor, 0, 3);
}

Block::~Block() {}

void Block::onDestroy()
{
    if (!deadAndRemoved)
    {
        for (int i = 0; i < 6; i++)
        {
            if (neighbourTypes[i] == BlockTypeEnum::NO_BLOCK)
            {
                Framework::Vec3<int> pos
                    = getPos() + getDirection(getDirectionFromIndex(i));
                Game::INSTANCE->zDimension(dimensionId)
                    ->placeBlock(pos,
                        Game::INSTANCE->zGenerator()->generateSingleBlock(
                            pos, dimensionId));
            }
        }
        Item* blockItem = zBlockType()->getItemFromBlock(this);
        if (blockItem)
        {
            ItemEntity* itemEntity
                = (ItemEntity*)StaticRegistry<EntityType>::INSTANCE
                      .zElement(EntityTypeEnum::ITEM)
                      ->createEntity(
                          location + Framework::Vec3<float>(0.5f, 0.5f, 0.5f),
                          dimensionId,
                          Game::INSTANCE->getNextEntityId());
            ItemStack* stack
                = new ItemStack(blockItem, 1, blockItem->getMaxStackSize());
            itemEntity->unsaveAddItem(stack, NO_DIRECTION);
            stack->release();
            Game::INSTANCE->requestWorldUpdate(
                new AddEntityUpdate(itemEntity, dimensionId));
            deadAndRemoved = 1;
        }
        for (MultiblockStructure* structure : structures)
            structure->onBlockRemoved(this);
        Game::INSTANCE->zDimension(dimensionId)
            ->placeBlock(
                getPos(), BlockTypeEnum::AIR); // this will be deleted here
    }
}

void Block::tick(TickQueue* zQueue)
{
    if (wasTicked) return;
    wasTicked = 1;
    ticksLeftCounter++;
    if (minTickTimeout >= 0)
    {
        if (currentTickTimeout < ticksLeftCounter)
        {
            onTickCalled = 1;
            bool blocked = 0;
            bool result = onTick(zQueue, ticksLeftCounter, blocked);
            if (blocked)
            {
                wasTicked = 0;
                ticksLeftCounter--;
                onTickCalled = 0;
                return;
            }
            if (result)
                currentTickTimeout
                    = MAX(MIN(currentTickTimeout - 1, maxTickTimeout),
                        MAX(minTickTimeout, 0));
            else
                currentTickTimeout
                    = MAX(MIN(currentTickTimeout + 1, maxTickTimeout),
                        MAX(minTickTimeout, 0));
            ticksLeftCounter = 0;
        }
    }
}

void Block::postTick()
{
    wasTicked = 0;
    if (onTickCalled)
    {
        onPostTick();
        onTickCalled = 0;
    }
}

void Block::setNeighbour(
    Direction dir, Framework::Either<Block*, int> neighbour)
{
    if (neighbour.isA())
        setNeighbourBlock(dir, neighbour);
    else
    {
        setNeighbourBlock(dir, 0);
        setNeighbourType(dir, neighbour);
    }
}

void Block::setNeighbourBlock(Direction dir, Block* zN)
{
    if (zN) setNeighbourType(dir, zN->zBlockType()->getId());
    zNeighbours[getDirectionIndex(dir)] = zN;
}

void Block::setNeighbourType(Direction dir, int type)
{
    neighbourTypes[getDirectionIndex(dir)] = type;
}

void Block::setDimensionId(int id)
{
    dimensionId = id;
}

void Block::addToStructure(MultiblockStructure* structure)
{
    if (structure->isBlockMember(this))
        structures.add(structure);
    else
        structure->release();
}

void Block::onLoaded()
{
    for (MultiblockStructure* structure : structures)
        structure->onBlockLoaded(dynamic_cast<Block*>(getThis()));
}

void Block::onUnloaded()
{
    for (MultiblockStructure* structure : structures)
        structure->onBlockUnloaded(this);
}

void api(Framework::StreamReader* zRequest, NetworkMessage* zResponse)
{
    // TODO: answer api requests
}

bool Block::isTickSource() const
{
    return tickSource;
}

const BlockType* Block::zBlockType() const
{
    return StaticRegistry<BlockType>::INSTANCE.zElement(typeId);
}

bool Block::isTransparent() const
{
    return transparent;
}

bool Block::isPassable() const
{
    return passable;
}

bool Block::isInteractable() const
{
    return interactable;
}

float Block::getHP() const
{
    return hp;
}

float Block::getMaxHP() const
{
    return maxHP;
}

float Block::getHardness() const
{
    return hardness;
}

const ItemType* Block::zEffectiveTool() const
{
    return zTool;
}

float Block::getSpeedModifier() const
{
    return speedModifier;
}

const Framework::Vec3<int> Block::getPos() const
{
    return (Framework::Vec3<int>)location;
}

int Block::getDimensionId() const
{
    return dimensionId;
}

bool Block::isVisible() const
{
    if (passable || transparent) return 1;
    for (int i = 0; i < 6; i++)
    {
        const Block* neighbour = CONST_BLOCK(zNeighbours[i], neighbourTypes[i]);
        if (neighbour->isPassable() || neighbour->isTransparent()) return 1;
    }
    return 0;
}

void Block::setHP(float hp)
{
    bool isDead = this->hp == 0.f;
    this->hp = MAX(0.f, hp);
    if (!isDead && this->hp == 0.f)
    {
        onDestroy(); // this will be deleted
    }
    else
    {
        NetworkMessage* changeMsg = new NetworkMessage();
        changeMsg->addressBlock(this);
        char* msg = new char[5];
        msg[0] = 0; // hp changed
        *(float*)(msg + 1) = this->hp;
        changeMsg->setMessage(msg, 5);
        Game::INSTANCE->broadcastMessage(changeMsg);
    }
}

bool Block::isDeadAndRemoved() const
{
    return deadAndRemoved;
}

const unsigned char* Block::getLightEmisionColor() const
{
    return lightEmisionColor;
}

void Block::filterPassingLight(unsigned char rgb[3]) const
{
    if (!transparent) // let no light pass intransparent blocks
        memset(rgb, 0, 3);
}

Block* Block::zNeighbor(Direction dir) const
{
    return zNeighbours[getDirectionIndex(dir)];
}

BasicBlockItem::BasicBlockItem(
    int itemTypeId, int blockTypeId, const char* name)
    : Item(itemTypeId, name)
{
    this->blockTypeId = blockTypeId;
    placeable = 1;
}

bool BasicBlockItem::canBeStackedWith(const Item* zItem) const
{
    const BasicBlockItem* item = dynamic_cast<const BasicBlockItem*>(zItem);
    if (item)
    {
        return Item::canBeStackedWith(zItem) && transparent == item->transparent
            && passable == item->passable && hardness == item->hardness
            && toolId == item->toolId && speedModifier == item->speedModifier
            && interactable == item->interactable;
    }
    return 0;
}

BasicBlockItemType::BasicBlockItemType(int id,
    const char* name,
    ItemSkillLevelUpRule* levelUpRule,
    int brokenTypeId,
    ModelInfo model,
    int blockTypeId)
    : ItemType(id, name, levelUpRule, brokenTypeId, model),
      transparent(0),
      passable(0),
      hardness(1.f),
      toolId(0),
      speedModifier(1.f),
      blockTypeId(blockTypeId)
{}

void BasicBlockItemType::loadSuperItem(
    Item* zItem, Framework::StreamReader* zReader) const
{
    ItemType::loadSuperItem(zItem, zReader);
    BasicBlockItem* item = dynamic_cast<BasicBlockItem*>(zItem);
    if (!item)
        throw "BasicBlockItemType::loadSuperItem was called with an invalid "
              "item";
    zReader->lese((char*)&item->transparent, 1);
    zReader->lese((char*)&item->passable, 1);
    zReader->lese((char*)&item->hardness, 4);
    zReader->lese((char*)&item->toolId, 4);
    zReader->lese((char*)&item->speedModifier, 4);
    zReader->lese((char*)&item->interactable, 1);
}

void BasicBlockItemType::saveSuperItem(
    const Item* zItem, Framework::StreamWriter* zWriter) const
{
    ItemType::saveSuperItem(zItem, zWriter);
    const BasicBlockItem* item = dynamic_cast<const BasicBlockItem*>(zItem);
    if (!item)
        throw "BasicBlockItemType::saveSuperItem was called with an invalid "
              "item";
    zWriter->schreibe((char*)&item->transparent, 1);
    zWriter->schreibe((char*)&item->passable, 1);
    zWriter->schreibe((char*)&item->hardness, 4);
    zWriter->schreibe((char*)&item->toolId, 4);
    zWriter->schreibe((char*)&item->speedModifier, 4);
    zWriter->schreibe((char*)&item->interactable, 1);
}

Item* BasicBlockItemType::createItem() const
{
    BasicBlockItem* item = new BasicBlockItem(id, blockTypeId, name);
    item->transparent = transparent;
    item->passable = passable;
    item->hardness = hardness;
    item->toolId = toolId;
    item->speedModifier = speedModifier;
    return item;
}

BasicBlockItemType* BasicBlockItemType::setHardness(float hardness)
{
    this->hardness = hardness;
    return this;
}