#include "Recipie.h"

#include <Logging.h>

#include "CraftingStorage.h"
#include "Game.h"
#include "Item.h"

RecipieInput::RecipieInput()
    : ReferenceCounter(),
      filter(0),
      modifier(0),
      amount(0)
{}

RecipieInput::~RecipieInput()
{
    if (filter) filter->release();
    if (modifier) modifier->release();
}

void RecipieInput::setFilter(ItemFilter* filter)
{
    if (this->filter) this->filter->release();
    this->filter = filter;
}

ItemFilter* RecipieInput::zFilter() const
{
    return filter;
}

void RecipieInput::setModifier(ItemModifier* modifier)
{
    if (this->modifier) this->modifier->release();
    this->modifier = modifier;
}

ItemModifier* RecipieInput::zModifier() const
{
    return modifier;
}

void RecipieInput::setAmount(int amount)
{
    this->amount = amount;
}

int RecipieInput::getAmount() const
{
    return amount;
}

RecipieInputFactory::RecipieInputFactory()
    : ObjectTypeFactory()
{}

RecipieInput* RecipieInputFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    RecipieInput* result = new RecipieInput();
    result->setFilter(Game::INSTANCE->zTypeRegistry()->fromJson<ItemFilter>(
        zJson->asObject()->zValue("filter")));
    result->setModifier(Game::INSTANCE->zTypeRegistry()->fromJson<ItemModifier>(
        zJson->asObject()->zValue("modifier")));
    result->setAmount(
        (int)zJson->asObject()->zValue("amount")->asNumber()->getNumber());
    return result;
}

Framework::JSON::JSONObject* RecipieInputFactory::toJsonObject(
    RecipieInput* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    result->addValue(
        "filter", Game::INSTANCE->zTypeRegistry()->toJson(zObject->zFilter()));
    result->addValue("modifier",
        Game::INSTANCE->zTypeRegistry()->toJson(zObject->zModifier()));
    result->addValue("amount",
        new Framework::JSON::JSONNumber((double)zObject->getAmount()));
    return result;
}

JSONObjectValidationBuilder* RecipieInputFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    Framework::JSON::JSONObject* defaultModifier
        = new Framework::JSON::JSONObject();
    defaultModifier->addValue(
        "type", new Framework::JSON::JSONString("consume"));
    return builder
        ->withRequiredAttribute("filter",
            Game::INSTANCE->zTypeRegistry()->getValidator<ItemFilter>())
        ->withRequiredAttribute("modifier",
            Game::INSTANCE->zTypeRegistry()->getValidator<ItemModifier>())
        ->withRequiredObject("modifier")
        ->withRequiredString("type")
        ->withExactMatch("consume")
        ->finishString()
        ->withDefault(defaultModifier)
        ->finishObject()
        ->withRequiredNumber("amount")
        ->whichIsGreaterOrEqual(1.0)
        ->withDefault(1.0)
        ->finishNumber();
}

RecipieOutput::RecipieOutput()
    : ReferenceCounter(),
      itemTypeId(0),
      amount(0),
      modifier(0)
{}

RecipieOutput::~RecipieOutput()
{
    modifier->release();
}

void RecipieOutput::setItemTypeId(int itemTypeId)
{
    this->itemTypeId = itemTypeId;
}

int RecipieOutput::getItemTypeId() const
{
    return itemTypeId;
}

void RecipieOutput::setAmount(int amount)
{
    this->amount = amount;
}

int RecipieOutput::getAmount() const
{
    return amount;
}

void RecipieOutput::setModifier(ItemModifier* modifier)
{
    if (this->modifier) this->modifier->release();
    this->modifier = modifier;
}

ItemModifier* RecipieOutput::zModifier() const
{
    return modifier;
}

Item* RecipieOutput::createItem() const
{
    Item* result = Game::INSTANCE->zItemType(itemTypeId)->createItem();
    if (result)
    {
        modifier->applyOn(result);
    }
    return result;
}

RecipieOutputFactory::RecipieOutputFactory()
    : ObjectTypeFactory()
{}

RecipieOutput* RecipieOutputFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    RecipieOutput* result = new RecipieOutput();
    result->setItemTypeId(Game::INSTANCE->getItemTypeId(
        zJson->asObject()->zValue("itemType")->asString()->getString()));
    result->setAmount(
        (int)zJson->asObject()->zValue("amount")->asNumber()->getNumber());
    result->setModifier(Game::INSTANCE->zTypeRegistry()->fromJson<ItemModifier>(
        zJson->asObject()->zValue("modifier")));
    return result;
}

Framework::JSON::JSONObject* RecipieOutputFactory::toJsonObject(
    RecipieOutput* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    result->addValue("itemType",
        new Framework::JSON::JSONString(
            Game::INSTANCE->zItemType(zObject->getItemTypeId())->getName()));
    result->addValue("amount",
        new Framework::JSON::JSONNumber((double)zObject->getAmount()));
    result->addValue("modifier",
        Game::INSTANCE->zTypeRegistry()->toJson(zObject->zModifier()));
    return result;
}

JSONObjectValidationBuilder* RecipieOutputFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    Framework::JSON::JSONObject* defaultModifier
        = new Framework::JSON::JSONObject();
    defaultModifier->addValue(
        "type", new Framework::JSON::JSONString("doNothing"));
    return builder
        ->withRequiredAttribute("itemType",
            Game::INSTANCE->zTypeRegistry()->getValidator<Framework::Text>(
                ItemTypeNameFactory::TYPE_ID))
        ->withRequiredAttribute("modifier",
            Game::INSTANCE->zTypeRegistry()->getValidator<ItemModifier>())
        ->withRequiredObject("modifier")
        ->withRequiredString("type")
        ->withExactMatch("doNothing")
        ->finishString()
        ->withDefault(defaultModifier)
        ->finishObject()
        ->withRequiredNumber("amount")
        ->whichIsGreaterOrEqual(1.0)
        ->withDefault(1.0)
        ->finishNumber();
}

Recipie::Recipie()
    : ReferenceCounter()
{}

void Recipie::addOutput(RecipieOutput* outputs)
{
    this->outputs.add(outputs);
}

Framework::Array<ItemInfo> Recipie::getOutput() const
{
    Framework::Array<ItemInfo> result;
    for (const RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            result.add({item->zItemType()->getId(),
                output->getAmount(),
                item->getHp(),
                item->getMaxHp(),
                item->getDurability(),
                item->getMaxDurability()});
            item->release();
        }
    }
    return result;
}

bool Recipie::createsOutput(int itemTypeId)
{
    for (RecipieOutput* output : outputs)
    {
        if (output->getItemTypeId() == itemTypeId)
        {
            return 1;
        }
    }
    return 0;
}

void Recipie::setGroupName(Framework::Text groupName)
{
    this->groupName = groupName;
}

const Framework::RCArray<RecipieOutput>& Recipie::getOutputs() const
{
    return outputs;
}

Framework::Text Recipie::getGroupName() const
{
    return groupName;
}

UnshapedRecipie::UnshapedRecipie()
    : Recipie()
{}

bool UnshapedRecipie::testApplicability(CraftingStorage* zStorage)
{
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            if (!zStorage->hasFreeSpace(item, output->getAmount()))
            {
                item->release();
                return 0;
            }
            item->release();
        }
    }
    return zStorage->isAllAvailable(inputs);
}

void UnshapedRecipie::apply(CraftingStorage* zStorage)
{
    zStorage->consume(inputs);
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            ItemStack* stack = new ItemStack(item, output->getAmount());
            zStorage->addCraftingResult(stack);
            stack->release();
        }
    }
}

Framework::Text UnshapedRecipie::getRecipieUIML()
{
    Framework::Text result = "<recipie type=\"unshaped\"><ingredients>";
    for (RecipieInput* input : inputs)
    {
        result.append() << "<ingredient amount=\"" << input->getAmount()
                        << "\">";
        if (input->zFilter())
        {
            result.append()
                << "<logic>" << input->zFilter()->getLogicUIML() << "</logic>";
        }
        result += "</ingredient>";
        // TODO: add modifiers
    }
    result += "</ingredients><outputs>";
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            result.append()
                << "<output amount=\"" << output->getAmount()
                << "\" itemType=\"" << item->zItemType()->getId() << "\" hp=\""
                << item->getHp() << "\" durability=\"" << item->getDurability()
                << "\" maxHp=\"" << item->getMaxHp() << "\" maxDurability=\""
                << item->getMaxDurability() << "\">" << item->getTooltipUIML()
                << "</output>";
            item->release();
        }
    }
    result += "</outputs></recipie>";
    return result;
}

void UnshapedRecipie::addInput(RecipieInput* input)
{
    inputs.add(input);
}

const Framework::RCArray<RecipieInput>& UnshapedRecipie::getInputs() const
{
    return inputs;
}

UnshapedRecipieFactory::UnshapedRecipieFactory()
    : RecipieFactory()
{}

UnshapedRecipie* UnshapedRecipieFactory::createValue(
    Framework::JSON::JSONObject* zJson) const
{
    return new UnshapedRecipie();
}

UnshapedRecipie* UnshapedRecipieFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    UnshapedRecipie* result = RecipieFactory::fromJson(zJson);
    for (Framework::JSON::JSONValue* input :
        *zJson->zValue("inputs")->asArray())
    {
        result->addInput(
            Game::INSTANCE->zTypeRegistry()->fromJson<RecipieInput>(input));
    }
    return result;
}

Framework::JSON::JSONObject* UnshapedRecipieFactory::toJsonObject(
    UnshapedRecipie* zObject) const
{
    Framework::JSON::JSONObject* result = RecipieFactory::toJsonObject(zObject);
    Framework::JSON::JSONArray* inputs = new Framework::JSON::JSONArray();
    for (RecipieInput* input : zObject->getInputs())
    {
        inputs->addValue(Game::INSTANCE->zTypeRegistry()->toJson(input));
    }
    result->addValue("inputs", inputs);
    return result;
}

JSONObjectValidationBuilder* UnshapedRecipieFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return RecipieFactory::addToValidator(
        builder->withRequiredArray("inputs")
            ->addAcceptedTypeInArray(
                Game::INSTANCE->zTypeRegistry()->getValidator<RecipieInput>())
            ->finishArray());
}

const char* UnshapedRecipieFactory::getTypeToken() const
{
    return "unshaped";
}

ShapedRecipie::ShapedRecipie()
    : Recipie(),
      width(0),
      height(0)
{}

bool ShapedRecipie::testApplicability(CraftingStorage* zStorage)
{
    ShapedCraftingStorage* zShapedStorage
        = dynamic_cast<ShapedCraftingStorage*>(zStorage);
    if (!zShapedStorage) return 0;
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            if (!zShapedStorage->hasFreeSpace(item, output->getAmount()))
            {
                item->release();
                return 0;
            }
            item->release();
        }
    }
    return zShapedStorage->isAllAvailable(inputs, width, height);
}

void ShapedRecipie::apply(CraftingStorage* zStorage)
{
    ShapedCraftingStorage* zShapedStorage
        = dynamic_cast<ShapedCraftingStorage*>(zStorage);
    zShapedStorage->consume(inputs, width, height);
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            ItemStack* stack = new ItemStack(item, output->getAmount());
            zStorage->addCraftingResult(stack);
            stack->release();
        }
    }
}

Framework::Text ShapedRecipie::getRecipieUIML()
{
    Framework::Text result = "<recipie type=\"shaped\" width=\"";
    result.append() << width << "\" height=\"" << height << "\"><ingredients>";
    for (RecipieInput* input : inputs)
    {
        result.append() << "<ingredient amount=\"" << input->getAmount()
                        << "\">";
        if (input->zFilter())
        {
            result.append()
                << "<logic>" << input->zFilter()->getLogicUIML() << "</logic>";
        }
        result += "</ingredient>";
        // TODO: add modifiers
    }
    result += "</ingredients><outputs>";
    for (RecipieOutput* output : outputs)
    {
        Item* item = output->createItem();
        if (item)
        {
            result.append()
                << "<output amount=\"" << output->getAmount()
                << "\" itemType=\"" << item->zItemType()->getId() << "\" hp=\""
                << item->getHp() << "\" durability=\"" << item->getDurability()
                << "\" maxHp=\"" << item->getMaxHp() << "\" maxDurability=\""
                << item->getMaxDurability() << "\">" << item->getTooltipUIML()
                << "</output>";
            item->release();
        }
    }
    result += "</outputs></recipie>";
    return result;
}

void ShapedRecipie::setWidth(int width)
{
    this->width = width;
}

int ShapedRecipie::getWidth() const
{
    return width;
}

void ShapedRecipie::setHeight(int height)
{
    this->height = height;
}

int ShapedRecipie::getHeight() const
{
    return height;
}

void ShapedRecipie::addInput(RecipieInput* input)
{
    inputs.add(input);
}

void ShapedRecipie::setInput(int index, RecipieInput* input)
{
    inputs.set(input, index);
}

const Framework::RCArray<RecipieInput>& ShapedRecipie::getInputs() const
{
    return inputs;
}

ShapedRecipieFactory::ShapedRecipieFactory()
    : RecipieFactory()
{}

ShapedRecipie* ShapedRecipieFactory::createValue(
    Framework::JSON::JSONObject* zJson) const
{
    return new ShapedRecipie();
}

ShapedRecipie* ShapedRecipieFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    ShapedRecipie* result = RecipieFactory::fromJson(zJson);
    int width = (int)zJson->zValue("width")->asNumber()->getNumber();
    int height = (int)zJson->zValue("height")->asNumber()->getNumber();
    for (int i = 0; i < width * height; i++)
    {
        result->addInput(new RecipieInput());
    }
    for (Framework::JSON::JSONValue* input :
        *zJson->zValue("inputs")->asArray())
    {
        int x = (int)input->asObject()->zValue("x")->asNumber()->getNumber();
        int y = (int)input->asObject()->zValue("y")->asNumber()->getNumber();
        if (x >= width || y >= height)
        {
            Framework::Logging::warning()
                << "Invalid input position in shaped recipie with width="
                << width << ", height=" << height << ", x=" << x << ", y=" << y
                << " input " << input->toString() << " will be ignored.";
            continue;
        }
        result->setInput(y * width + x,
            Game::INSTANCE->zTypeRegistry()->fromJson<RecipieInput>(
                input->asObject()->zValue("input")));
    }
    result->setWidth((int)zJson->zValue("width")->asNumber()->getNumber());
    result->setHeight((int)zJson->zValue("height")->asNumber()->getNumber());
    return result;
}

Framework::JSON::JSONObject* ShapedRecipieFactory::toJsonObject(
    ShapedRecipie* zObject) const
{
    Framework::JSON::JSONObject* result = RecipieFactory::toJsonObject(zObject);
    result->addValue(
        "width", new Framework::JSON::JSONNumber(zObject->getWidth()));
    result->addValue(
        "height", new Framework::JSON::JSONNumber(zObject->getHeight()));
    Framework::JSON::JSONArray* inputs = new Framework::JSON::JSONArray();
    for (int i = 0; i < zObject->getWidth() * zObject->getHeight(); i++)
    {
        Framework::JSON::JSONObject* input = new Framework::JSON::JSONObject();
        input->addValue(
            "x", new Framework::JSON::JSONNumber(i % zObject->getHeight()));
        input->addValue(
            "y", new Framework::JSON::JSONNumber(i / zObject->getHeight()));
        input->addValue("input",
            Game::INSTANCE->zTypeRegistry()->toJson(zObject->getInputs().z(i)));
        inputs->addValue(input);
    }
    result->addValue("inputs", inputs);
    return result;
}

JSONObjectValidationBuilder* ShapedRecipieFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return RecipieFactory::addToValidator(
        builder->withRequiredArray("inputs")
            ->addAcceptedObjectInArray()
            ->withRequiredNumber("x")
            ->whichIsGreaterOrEqual(0.0)
            ->finishNumber()
            ->withRequiredNumber("y")
            ->whichIsGreaterOrEqual(0.0)
            ->finishNumber()
            ->withRequiredAttribute("input",
                Game::INSTANCE->zTypeRegistry()->getValidator<RecipieInput>())
            ->finishObject()
            ->finishArray()
            ->withRequiredNumber("width")
            ->whichIsGreaterOrEqual(1.0)
            ->finishNumber()
            ->withRequiredNumber("height")
            ->whichIsGreaterOrEqual(1.0)
            ->finishNumber());
}

const char* ShapedRecipieFactory::getTypeToken() const
{
    return "shaped";
}