#include "BasicItems.h"

#include "Entity.h"
#include "Game.h"
#include "Item.h"
#include "ModelInfo.h"

BasicItemType::BasicItemType()
    : ItemType(),
      itemName(),
      hp(1.f),
      durability(1.f),
      solid(true),
      hungerRecoveryPerHp(0.f),
      thirstRecoveryPerHp(0.f)
{}

Item* BasicItemType::createItem() const
{
    Item* result = createBasicItem(getId(),
        itemName,
        hp,
        hp,
        durability,
        durability,
        hungerRecoveryPerHp > 0 || thirstRecoveryPerHp > 0,
        0,
        0,
        solid,
        0);
    if (hungerRecoveryPerHp > 0 || thirstRecoveryPerHp > 0)
    {
        result->setFoodEffect(
            [this](Item* zItem, Entity* zEntity) {
                float added = zItem->getHp();
                if (zEntity->getHunger() + added * hungerRecoveryPerHp
                        > zEntity->getMaxHunger()
                    && zEntity->getThirst() + added * thirstRecoveryPerHp
                           > zEntity->getMaxThirst())
                {
                    added = MAX((zEntity->getMaxHunger() - zEntity->getHunger())
                                    / hungerRecoveryPerHp,
                        (zEntity->getMaxThirst() - zEntity->getThirst())
                            / thirstRecoveryPerHp);
                }
                zEntity->setHunger(
                    zEntity->getHunger() + added * hungerRecoveryPerHp);
                zEntity->setThirst(
                    zEntity->getThirst() + added * thirstRecoveryPerHp);
                zItem->setHp(zItem->getHp() - added);
                return added != 0.f;
            },
            [this](const Item* zItem, Entity* zEntity) {
                float addable = zItem->getHp();
                if (zEntity->getHunger() + addable * hungerRecoveryPerHp
                        > zEntity->getMaxHunger()
                    && zEntity->getThirst() + addable * thirstRecoveryPerHp
                           > zEntity->getMaxThirst())
                {
                    addable
                        = MAX((zEntity->getMaxHunger() - zEntity->getHunger())
                                  / hungerRecoveryPerHp,
                            (zEntity->getMaxThirst() - zEntity->getThirst())
                                / thirstRecoveryPerHp);
                }
                return addable >= zItem->getHp();
            });
    }
    return result;
}

void BasicItemType::setItemName(Framework::Text itemName)
{
    this->itemName = itemName;
}

Framework::Text BasicItemType::getItemName() const
{
    return itemName;
}

void BasicItemType::setHp(float hp)
{
    this->hp = hp;
}

float BasicItemType::getHp() const
{
    return hp;
}

void BasicItemType::setDurability(float durability)
{
    this->durability = durability;
}

float BasicItemType::getDurability() const
{
    return durability;
}

void BasicItemType::setSolid(bool solid)
{
    this->solid = solid;
}

bool BasicItemType::isSolid() const
{
    return solid;
}

void BasicItemType::setHungerRecoveryPerHp(float hungerRecoveryPerHp)
{
    this->hungerRecoveryPerHp = hungerRecoveryPerHp;
}

float BasicItemType::getHungerRecoveryPerHp() const
{
    return hungerRecoveryPerHp;
}

void BasicItemType::setThirstRecoveryPerHp(float thirstRecoveryPerHp)
{
    this->thirstRecoveryPerHp = thirstRecoveryPerHp;
}

float BasicItemType::getThirstRecoveryPerHp() const
{
    return thirstRecoveryPerHp;
}

BasicItemTypeFactory::BasicItemTypeFactory()
    : ItemTypeFactoryBase()
{}

BasicItemType* BasicItemTypeFactory::createValue(
    Framework::JSON::JSONObject* zJson) const
{
    return new BasicItemType();
}

BasicItemType* BasicItemTypeFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    BasicItemType* result = ItemTypeFactoryBase::fromJson(zJson);
    result->setItemName(
        Framework::Text(zJson->zValue("itemName")->asString()->getString()));
    result->setHp((float)zJson->zValue("hp")->asNumber()->getNumber());
    result->setDurability(
        (float)zJson->zValue("durability")->asNumber()->getNumber());
    result->setSolid(zJson->zValue("solid")->asBool()->getBool());
    result->setHungerRecoveryPerHp(
        (float)zJson->zValue("hungerRecoveryPerHp")->asNumber()->getNumber());
    result->setThirstRecoveryPerHp(
        (float)zJson->zValue("thirstRecoveryPerHp")->asNumber()->getNumber());
    return result;
}

Framework::JSON::JSONObject* BasicItemTypeFactory::toJsonObject(
    BasicItemType* zObject) const
{
    Framework::JSON::JSONObject* result
        = ItemTypeFactoryBase::toJsonObject(zObject);
    result->addValue(
        "itemName", new Framework::JSON::JSONString(zObject->getItemName()));
    result->addValue("hp", new Framework::JSON::JSONNumber(zObject->getHp()));
    result->addValue("durability",
        new Framework::JSON::JSONNumber(zObject->getDurability()));
    result->addValue(
        "solid", new Framework::JSON::JSONBool(zObject->isSolid()));
    result->addValue("hungerRecoveryPerHp",
        new Framework::JSON::JSONNumber(zObject->getHungerRecoveryPerHp()));
    result->addValue("thirstRecoveryPerHp",
        new Framework::JSON::JSONNumber(zObject->getThirstRecoveryPerHp()));
    return result;
}

JSONObjectValidationBuilder* BasicItemTypeFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return ItemTypeFactoryBase::addToValidator(
        builder->withRequiredString("itemName")
            ->finishString()
            ->withRequiredNumber("hp")
            ->whichIsGreaterThen(0.0)
            ->withDefault(1.0)
            ->finishNumber()
            ->withRequiredNumber("durability")
            ->whichIsGreaterThen(0.0)
            ->withDefault(1.0)
            ->finishNumber()
            ->withRequiredBool("solid")
            ->withDefault(true)
            ->finishBool()
            ->withRequiredNumber("hungerRecoveryPerHp")
            ->whichIsGreaterOrEqual(0.0)
            ->withDefault(0.0)
            ->finishNumber()
            ->withRequiredNumber("thirstRecoveryPerHp")
            ->whichIsGreaterOrEqual(0.0)
            ->withDefault(0.0)
            ->finishNumber());
}

const char* BasicItemTypeFactory::getTypeToken() const
{
    return "basic";
}