#include "TreeSeblingBlock.h"

#include "BasicBlocks.h"
#include "Dimension.h"
#include "Game.h"
#include "NoBlock.h"
#include "RandNoise.h"
#include "TreeTemplate.h"
#include "WorldGenerator.h"

TreeSeblingBlock::TreeSeblingBlock(int typeId,
    Framework::Vec3<int> pos,
    int dimensionId,
    const BlockType* wood,
    const BlockType* leaves)
    : Block(typeId, pos, dimensionId, 0),
      seblingTicks(0),
      seblingTicksMax(10000),
      wood(wood),
      leaves(leaves)
{}

bool TreeSeblingBlock::onTick(TickQueue* zQueue, int numTicks, bool& blocked)
{
    int lastPercentage = (int)(seblingTicks / (float)seblingTicksMax * 100.f);
    seblingTicks += 1;
    if ((int)(seblingTicks / (float)seblingTicksMax * 100.f) != lastPercentage)
    {
        Game::INSTANCE->blockTargetChanged(this);
    }
    return 0;
}

void TreeSeblingBlock::onPostTick()
{
    if ((int)seblingTicks >= seblingTicksMax)
    {
        Game::INSTANCE->doLater([wood = wood,
                                    leaves = leaves,
                                    pos = getPos(),
                                    dim = getDimensionId()]() {
            // the tree sebling object will be deleted during this operation
            RandNoise noise((int)time(0));
            if (!Game::INSTANCE->zGenerator()->spawnStructure(pos,
                    dim,
                    [wood = wood, leaves = leaves](GeneratorTemplate* tmpl) {
                        TreeTemplate* tree = dynamic_cast<TreeTemplate*>(tmpl);
                        return tree && tree->zWoodType() == wood
                            && tree->zLeavesType() == leaves;
                    }))
            {
                Game::INSTANCE->zDimension(dim)->placeBlock(
                    pos, BlockTypeEnum::AIR);
            }
        });
    }
}

TickSourceType TreeSeblingBlock::isTickSource() const
{
    return TickSourceType::EACH_TICK;
}

Framework::Text TreeSeblingBlock::getTargetUIML()
{
    return Framework::Text("<targetInfo><text width=\"auto\" height=\"auto\">")
         + Game::INSTANCE->zBlockType(typeId)->getName() + "\n" + "Growth: "
         + Framework::Text((int)(seblingTicks / (float)seblingTicksMax * 100.f))
         + "%</text></targetInfo>";
}

TreeSeblingBlockType::TreeSeblingBlockType()
    : BlockType(),
      transparent(true),
      passable(true),
      speedModifier(0.5f),
      interactable(1)
{}

bool TreeSeblingBlockType::initialize(Game* zGame)
{
    if (itemTypeName.getLength())
    {
        itemTypeId = zGame->getItemTypeId(itemTypeName);
    }
    else
    {
        itemTypeId = 0;
    }
    woodTypeId = zGame->getBlockTypeId(woodTypeName);
    leavesTypeId = zGame->getBlockTypeId(leavesTypeName);
    return itemTypeId >= 0 && BlockType::initialize(zGame);
}

void TreeSeblingBlockType::setItemTypeName(Framework::Text itemTypeName)
{
    this->itemTypeName = itemTypeName;
}

Framework::Text TreeSeblingBlockType::getItemTypeName() const
{
    return itemTypeName;
}

void TreeSeblingBlockType::setWoodTypeName(Framework::Text woodTypeName)
{
    this->woodTypeName = woodTypeName;
}

Framework::Text TreeSeblingBlockType::getWoodTypeName() const
{
    return woodTypeName;
}

void TreeSeblingBlockType::setLeavesTypeName(Framework::Text leavesTypeName)
{
    this->leavesTypeName = leavesTypeName;
}

Framework::Text TreeSeblingBlockType::getLeavesTypeName() const
{
    return leavesTypeName;
}

void TreeSeblingBlockType::setTransparent(bool transparent)
{
    this->transparent = transparent;
}

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

void TreeSeblingBlockType::setPassable(bool passable)
{
    this->passable = passable;
}

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

void TreeSeblingBlockType::setSpeedModifier(float speedModifier)
{
    this->speedModifier = speedModifier;
}

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

void TreeSeblingBlockType::setInteractable(bool interactable)
{
    this->interactable = interactable;
}

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

ItemType* TreeSeblingBlockType::createItemType() const
{
    return new BasicBlockItemType(getItemTypeName(),
        new ModelInfo(zModel()->getModelPath(),
            zModel()->getTexturePaths(),
            zModel()->isTransparent(),
            zModel()->getSize() / 2.f),
        transparent,
        passable,
        getHardness(),
        speedModifier,
        getName(),
        0,
        50,
        getGroupNames());
}

void TreeSeblingBlockType::createSuperBlock(Block* zBlock, Item* zItem) const
{
    TreeSeblingBlock* block = dynamic_cast<TreeSeblingBlock*>(zBlock);
    block->transparent = transparent;
    block->passable = passable;
    block->hp = (float)getInitialMaxHP();
    block->maxHP = (float)getInitialMaxHP();
    block->hardness = getHardness();
    block->speedModifier = speedModifier;
    block->interactable = interactable;
    BlockType::createSuperBlock(zBlock, zItem);
}

void TreeSeblingBlockType::loadSuperBlock(
    Block* zBlock, Framework::StreamReader* zReader, int dimensionId) const
{
    TreeSeblingBlock* block = dynamic_cast<TreeSeblingBlock*>(zBlock);
    zReader->lese((char*)&block->seblingTicks, 4);
    zReader->lese((char*)&block->seblingTicksMax, 4);
    int id;
    zReader->lese((char*)&id, 4);
    block->wood = Game::INSTANCE->zBlockType(id);
    zReader->lese((char*)&id, 4);
    block->leaves = Game::INSTANCE->zBlockType(id);
    BlockType::loadSuperBlock(zBlock, zReader, dimensionId);
}

void TreeSeblingBlockType::saveSuperBlock(
    Block* zBlock, Framework::StreamWriter* zWriter) const
{
    TreeSeblingBlock* block = dynamic_cast<TreeSeblingBlock*>(zBlock);
    zWriter->schreibe((char*)&block->seblingTicks, 4);
    zWriter->schreibe((char*)&block->seblingTicksMax, 4);
    int id = block->wood->getId();
    zWriter->schreibe((char*)&id, 4);
    id = block->leaves->getId();
    zWriter->schreibe((char*)&id, 4);
    BlockType::saveSuperBlock(zBlock, zWriter);
}

Item* TreeSeblingBlockType::createItem() const
{
    return Game::INSTANCE->zItemType(itemTypeId)->createItem();
}

Block* TreeSeblingBlockType::createBlock(
    Framework::Vec3<int> position, int dimensionId) const
{
    return new TreeSeblingBlock(getId(),
        position,
        dimensionId,
        Game::INSTANCE->zBlockType(woodTypeId),
        Game::INSTANCE->zBlockType(leavesTypeId));
}

TreeSeblingBlockTypeFactory::TreeSeblingBlockTypeFactory()
    : BlockTypeFactoryBase()
{}

TreeSeblingBlockType* TreeSeblingBlockTypeFactory::createValue(
    Framework::JSON::JSONObject* zJson) const
{
    return new TreeSeblingBlockType();
}

TreeSeblingBlockType* TreeSeblingBlockTypeFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    TreeSeblingBlockType* result = BlockTypeFactoryBase::fromJson(zJson);
    result->setItemTypeName(zJson->zValue("itemType")->asString()->getString());
    result->setWoodTypeName(zJson->zValue("woodType")->asString()->getString());
    result->setLeavesTypeName(
        zJson->zValue("leavesType")->asString()->getString());
    result->setTransparent(zJson->zValue("transparent")->asBool()->getBool());
    result->setPassable(zJson->zValue("passable")->asBool()->getBool());
    result->setSpeedModifier(
        (float)zJson->zValue("speedModifier")->asNumber()->getNumber());
    result->setInteractable(zJson->zValue("interactable")->asBool()->getBool());
    return result;
}

Framework::JSON::JSONObject* TreeSeblingBlockTypeFactory::toJsonObject(
    TreeSeblingBlockType* zObject) const
{
    Framework::JSON::JSONObject* zResult
        = BlockTypeFactoryBase::toJsonObject(zObject);
    zResult->addValue("itemType",
        new Framework::JSON::JSONString(zObject->getItemTypeName()));
    zResult->addValue("woodType",
        new Framework::JSON::JSONString(zObject->getWoodTypeName()));
    zResult->addValue("leavesType",
        new Framework::JSON::JSONString(zObject->getLeavesTypeName()));
    zResult->addValue("transparent",
        new Framework::JSON::JSONNumber(zObject->isTransparent()));
    zResult->addValue(
        "passable", new Framework::JSON::JSONNumber(zObject->isPassable()));
    zResult->addValue("speedModifier",
        new Framework::JSON::JSONNumber(zObject->getSpeedModifier()));
    zResult->addValue("interactable",
        new Framework::JSON::JSONNumber(zObject->isInteractable()));
    return zResult;
}

JSONObjectValidationBuilder* TreeSeblingBlockTypeFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return BlockTypeFactoryBase::addToValidator(
        builder
            ->withRequiredAttribute("itemType",
                Game::INSTANCE->zTypeRegistry()->getValidator<Framework::Text>(
                    ItemTypeNameFactory::TYPE_ID))
            ->withRequiredAttribute("woodType",
                Game::INSTANCE->zTypeRegistry()->getValidator<Framework::Text>(
                    BlockTypeNameFactory::TYPE_ID))
            ->withRequiredAttribute("leavesType",
                Game::INSTANCE->zTypeRegistry()->getValidator<Framework::Text>(
                    BlockTypeNameFactory::TYPE_ID))
            ->withRequiredBool("transparent")
            ->withDefault(true)
            ->finishBool()
            ->withRequiredBool("passable")
            ->withDefault(true)
            ->finishBool()
            ->withRequiredNumber("speedModifier")
            ->withDefault(0.5)
            ->finishNumber()
            ->withRequiredBool("interactable")
            ->withDefault(true)
            ->finishBool());
}

const char* TreeSeblingBlockTypeFactory::getTypeToken() const
{
    return "treeSapling";
}