#include "ItemFilter.h"

#include "Game.h"
#include "Item.h"
#include "ItemSlot.h"
#include "ItemStack.h"
#include "ItemType.h"

ItemFilter::ItemFilter()
    : ReferenceCounter()
{}

bool ItemFilter::matchItem(const Item* zItem) const
{
    return 1;
}

bool ItemFilter::matchSourceSlot(ItemSlot* zSlot) const
{
    return zSlot->zStack() ? matchItem(zSlot->zStack()->zItem()) : 0;
}

bool ItemFilter::matchTargetSlot(ItemSlot* zSlot) const
{
    return 1;
}

CombinedItemFilter::CombinedItemFilter()
    : ItemFilter(),
      op([](bool a, bool b) { return false; })
{}

CombinedItemFilter::CombinedItemFilter(
    Framework::RCArray<ItemFilter> filters, std::function<bool(bool, bool)> op)
    : ItemFilter(),
      filters(filters),
      op(op)
{}

bool CombinedItemFilter::matchItem(const Item* zItem) const
{
    bool result = false;
    for (int i = 0; i < filters.getEintragAnzahl(); i++)
    {
        if (i == 0)
            result = filters.z(i)->matchItem(zItem);
        else
            result = op(result, filters.z(i)->matchItem(zItem));
    }
    return result;
}

bool CombinedItemFilter::matchSourceSlot(ItemSlot* zSlot) const
{
    bool result = false;
    for (int i = 0; i < filters.getEintragAnzahl(); i++)
    {
        if (i == 0)
            result = filters.z(i)->matchSourceSlot(zSlot);
        else
            result = op(result, filters.z(i)->matchSourceSlot(zSlot));
    }
    return result;
}

bool CombinedItemFilter::matchTargetSlot(ItemSlot* zSlot) const
{
    bool result = false;
    for (int i = 0; i < filters.getEintragAnzahl(); i++)
    {
        if (i == 0)
            result = filters.z(i)->matchTargetSlot(zSlot);
        else
            result = op(result, filters.z(i)->matchTargetSlot(zSlot));
    }
    return result;
}

Framework::Text CombinedItemFilter::getLogicUIML() const
{
    if (filters.getEintragAnzahl() == 0)
    {
        return "";
    }
    if (filters.getEintragAnzahl() == 1)
    {
        return filters.z(0)->getLogicUIML();
    }
    else
    {
        Framework::Text result = "<operator result_0_0=\"";
        result.append() << (int)op(0, 0) << "\" result_0_1=\"" << (int)op(0, 1)
                        << "\" result_1_0=\"" << (int)op(1, 0)
                        << "\" result_1_1=\"" << (int)op(1, 1) << "\">";
        int openCount = 1;
        for (int i = 0; i < filters.getEintragAnzahl() - 1; i++)
        {
            if (i < filters.getEintragAnzahl() - 2)
            {
                result.append()
                    << filters.z(i)->getLogicUIML() << "<operator result_0_0=\""
                    << (int)op(0, 0) << "\" result_0_1=\"" << (int)op(0, 1)
                    << "\" result_1_0=\"" << (int)op(1, 0) << "\" result_1_1=\""
                    << (int)op(1, 1) << "\">";
            }
            else
            {
                filters.z(i)->getLogicUIML();
                filters.z(i + 1)->getLogicUIML();
            }
        }
        return result;
    }
}

void CombinedItemFilter::addFilter(ItemFilter* filter)
{
    filters.add(filter);
}

const Framework::RCArray<ItemFilter>& CombinedItemFilter::getFilters() const
{
    return filters;
}

void CombinedItemFilter::setOp(std::function<bool(bool, bool)> op)
{
    this->op = op;
}

std::function<bool(bool, bool)> CombinedItemFilter::getOp() const
{
    return op;
}

CombinedItemFilterFactory::CombinedItemFilterFactory()
    : SubTypeFactory()
{}

CombinedItemFilter* CombinedItemFilterFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    CombinedItemFilter* result = new CombinedItemFilter();
    std::function<bool(bool, bool)> func;
    Framework::Text op = zJson->zValue("operator")->asString()->getString();
    if (op.istGleich("or"))
    {
        func = [](bool a, bool b) { return a || b; };
    }
    else if (op.istGleich("and"))
    {
        func = [](bool a, bool b) { return a && b; };
    }
    else if (op.istGleich("xor"))
    {
        func = [](bool a, bool b) { return (!a || !b) && (a || b); };
    }
    else if (op.istGleich("nor"))
    {
        func = [](bool a, bool b) { return !(a || b); };
    }
    else if (op.istGleich("nand"))
    {
        func = [](bool a, bool b) { return !(a && b); };
    }
    else if (op.istGleich("left"))
    {
        func = [](bool a, bool b) { return a; };
    }
    else if (op.istGleich("right"))
    {
        func = [](bool a, bool b) { return b; };
    }
    else if (op.istGleich("onlyLeft"))
    {
        func = [](bool a, bool b) { return a && !b; };
    }
    else if (op.istGleich("onlyRight"))
    {
        func = [](bool a, bool b) { return !a && b; };
    }
    else if (op.istGleich("notLeft"))
    {
        func = [](bool a, bool b) { return !a; };
    }
    else if (op.istGleich("notRight"))
    {
        func = [](bool a, bool b) { return !b; };
    }
    else if (op.istGleich("eq"))
    {
        func = [](bool a, bool b) { return a == b; };
    }
    else if (op.istGleich("leftOrEq"))
    {
        func = [](bool a, bool b) { return a || (a == b); };
    }
    else if (op.istGleich("rightOrEq"))
    {
        func = [](bool a, bool b) { return b || (a == b); };
    }
    else if (op.istGleich("true"))
    {
        func = [](bool a, bool b) { return true; };
    }
    else
    {
        func = [](bool a, bool b) { return false; };
    }
    result->setOp(func);
    Framework::RCArray<ItemFilter> filters;
    for (Framework::JSON::JSONValue* value :
        *zJson->zValue("filters")->asArray())
    {
        result->addFilter(
            Game::INSTANCE->zTypeRegistry()->fromJson<ItemFilter>(value));
    }
    return result;
}

Framework::JSON::JSONObject* CombinedItemFilterFactory::toJsonObject(
    CombinedItemFilter* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    Framework::Text op;
    std::function<bool(bool, bool)> func = zObject->getOp();
    if (func(0, 0))
    {
        if (func(0, 1))
        {
            if (func(1, 0))
            {
                if (func(1, 1))
                {
                    op = "true";
                }
                else
                {
                    op = "nand";
                }
            }
            else
            {
                if (func(1, 1))
                {
                    op = "rightOrEq";
                }
                else
                {
                    op = "notLeft";
                }
            }
        }
        else
        {
            if (func(1, 0))
            {
                if (func(1, 1))
                {
                    op = "leftOrEq";
                }
                else
                {
                    op = "notRight";
                }
            }
            else
            {
                if (func(1, 1))
                {
                    op = "eq";
                }
                else
                {
                    op = "nor";
                }
            }
        }
    }
    else
    {
        if (func(0, 1))
        {
            if (func(1, 0))
            {
                if (func(1, 1))
                {
                    op = "or";
                }
                else
                {
                    op = "xor";
                }
            }
            else
            {
                if (func(1, 1))
                {
                    op = "right";
                }
                else
                {
                    op = "onlyRight";
                }
            }
        }
        else
        {
            if (func(1, 0))
            {
                if (func(1, 1))
                {
                    op = "left";
                }
                else
                {
                    op = "onlyLeft";
                }
            }
            else
            {
                if (func(1, 1))
                {
                    op = "and";
                }
                else
                {
                    op = "false";
                }
            }
        }
    }
    result->addValue("operator", new Framework::JSON::JSONString(op));
    Framework::JSON::JSONArray* filters = new Framework::JSON::JSONArray();
    for (ItemFilter* filter : zObject->getFilters())
    {
        filters->addValue(Game::INSTANCE->zTypeRegistry()->toJson(filter));
    }
    result->addValue("filters", filters);
    return result;
}

JSONObjectValidationBuilder* CombinedItemFilterFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return builder->withRequiredString("operator")
        ->whichIsOneOf({"or",
            "and",
            "xor",
            "nor",
            "nand",
            "left",
            "right",
            "onlyLeft",
            "onlyRight",
            "notLeft",
            "notRight",
            "eq",
            "leftOrEq",
            "rightOrEq",
            "true",
            "false"})
        ->finishString()
        ->withRequiredArray("filters")
        ->addAcceptedTypeInArray(
            Game::INSTANCE->zTypeRegistry()->getValidator<ItemFilter>())
        ->finishArray();
}

const char* CombinedItemFilterFactory::getTypeToken() const
{
    return "operator";
}

AnyItemFilter::AnyItemFilter()
    : ItemFilter()
{}

bool AnyItemFilter::matchItem(const Item* zItem) const
{
    return true;
}

Framework::Text AnyItemFilter::getLogicUIML() const
{
    return "<anyItem/>";
}

AnyItemFilterFactory::AnyItemFilterFactory()
    : SubTypeFactory()
{}

AnyItemFilter* AnyItemFilterFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    return new AnyItemFilter();
}

Framework::JSON::JSONObject* AnyItemFilterFactory::toJsonObject(
    AnyItemFilter* zObject) const
{
    return new Framework::JSON::JSONObject();
}

JSONObjectValidationBuilder* AnyItemFilterFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return builder;
}

const char* AnyItemFilterFactory::getTypeToken() const
{
    return "anyItem";
}

TypeItemFilter::TypeItemFilter()
    : ItemFilter(),
      type(0)
{}

bool TypeItemFilter::matchItem(const Item* zItem) const
{
    return zItem->zItemType() == type;
}

Framework::Text TypeItemFilter::getLogicUIML() const
{
    Framework::Text result = "<attribute name=\"Type\" operator=\"=\" value=\"";
    result.append() << type->getId() << "\"/>";
    return result;
}

void TypeItemFilter::setType(const ItemType* type)
{
    this->type = type;
}

const ItemType* TypeItemFilter::zType() const
{
    return type;
}

TypeItemFilterFactory::TypeItemFilterFactory()
    : SubTypeFactory()
{}

TypeItemFilter* TypeItemFilterFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    TypeItemFilter* result = new TypeItemFilter();
    result->setType(Game::INSTANCE->zItemType(Game::INSTANCE->getItemTypeId(
        zJson->zValue("itemType")->asString()->getString())));
    return result;
}

Framework::JSON::JSONObject* TypeItemFilterFactory::toJsonObject(
    TypeItemFilter* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    result->addValue("itemType",
        new Framework::JSON::JSONString(zObject->zType()->getName()));
    return result;
}

JSONObjectValidationBuilder* TypeItemFilterFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return builder->withRequiredAttribute("itemType",
        Game::INSTANCE->zTypeRegistry()->getValidator<Framework::Text>(
            ItemTypeNameFactory::TYPE_ID));
}

const char* TypeItemFilterFactory::getTypeToken() const
{
    return "type";
}

SpecificSlotFilter::SpecificSlotFilter(int sourceSlotId, int targetSlotId)
    : ItemFilter(),
      sourceSlotId(sourceSlotId),
      targetSlotId(targetSlotId)
{}

bool SpecificSlotFilter::matchSourceSlot(ItemSlot* zSlot) const
{
    return sourceSlotId == zSlot->getId();
}

bool SpecificSlotFilter::matchTargetSlot(ItemSlot* zSlot) const
{
    return targetSlotId == zSlot->getId();
}

Framework::Text SpecificSlotFilter::getLogicUIML() const
{
    return "<anyItem/>";
}

SourceSlotBlacklistFilter::SourceSlotBlacklistFilter()
    : ItemFilter()
{}

void SourceSlotBlacklistFilter::addBlackListSlotId(int id)
{
    blackList.add(id);
}

bool SourceSlotBlacklistFilter::matchSourceSlot(ItemSlot* zSlot) const
{
    for (int black : blackList)
    {
        if (black == zSlot->getId()) return 0;
    }
    return 1;
}

bool SourceSlotBlacklistFilter::matchTargetSlot(ItemSlot* zSlot) const
{
    return 1;
}

Framework::Text SourceSlotBlacklistFilter::getLogicUIML() const
{
    return "<anyItem/>";
}

TargetSlotBlacklistFilter::TargetSlotBlacklistFilter()
    : ItemFilter()
{}

void TargetSlotBlacklistFilter::addBlackListSlotId(int id)
{
    blackList.add(id);
}

bool TargetSlotBlacklistFilter::matchSourceSlot(ItemSlot* zSlot) const
{
    return 1;
}

bool TargetSlotBlacklistFilter::matchTargetSlot(ItemSlot* zSlot) const
{
    for (int black : blackList)
    {
        if (black == zSlot->getId()) return 0;
    }
    return 1;
}

Framework::Text TargetSlotBlacklistFilter::getLogicUIML() const
{
    return "<anyItem/>";
}

GroupItemFilter::GroupItemFilter()
    : ItemFilter()
{}

bool GroupItemFilter::matchItem(const Item* zItem) const
{
    for (Framework::Text* group : groups)
    {
        for (Framework::Text* typeGroup : zItem->zItemType()->getGroups())
        {
            if (typeGroup->istGleich(*group)) return true;
        }
    }
    return false;
}

Framework::Text GroupItemFilter::getLogicUIML() const
{
    CombinedItemFilter filter;
    for (int i = 0; i < Game::INSTANCE->getItemTypeCount(); i++)
    {
        if (Game::INSTANCE->zItemType(i))
        {
            bool found = false;
            for (Framework::Text* group : groups)
            {
                for (Framework::Text* typeGroup :
                    Game::INSTANCE->zItemType(i)->getGroups())
                {
                    if (typeGroup->istGleich(*group))
                    {
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
            if (found)
            {
                TypeItemFilter* f = new TypeItemFilter();
                f->setType(Game::INSTANCE->zItemType(i));
                filter.addFilter(f);
            }
        }
    }
    filter.setOp([](bool a, bool b) { return a || b; });
    return filter.getLogicUIML();
}

void GroupItemFilter::addGroup(const Framework::Text group)
{
    groups.add(new Framework::Text(group));
}

const Framework::RCArray<Framework::Text>& GroupItemFilter::getGroups() const
{
    return groups;
}

GroupItemFilterFactory::GroupItemFilterFactory()
    : SubTypeFactory()
{}

GroupItemFilter* GroupItemFilterFactory::fromJson(
    Framework::JSON::JSONObject* zJson) const
{
    GroupItemFilter* result = new GroupItemFilter();
    for (Framework::JSON::JSONValue* group :
        *zJson->zValue("groupNames")->asArray())
    {
        result->addGroup(group->asString()->getString());
    }
    return result;
}

Framework::JSON::JSONObject* GroupItemFilterFactory::toJsonObject(
    GroupItemFilter* zObject) const
{
    Framework::JSON::JSONObject* result = new Framework::JSON::JSONObject();
    Framework::JSON::JSONArray* groups = new Framework::JSON::JSONArray();
    for (Framework::Text* group : zObject->getGroups())
    {
        groups->addValue(new Framework::JSON::JSONString(group->getText()));
    }
    result->addValue("groupNames", groups);
    return result;
}

JSONObjectValidationBuilder* GroupItemFilterFactory::addToValidator(
    JSONObjectValidationBuilder* builder) const
{
    return builder->withRequiredArray("groupNames")
        ->addAcceptedStringInArray()
        ->finishString()
        ->finishArray();
}

const char* GroupItemFilterFactory::getTypeToken() const
{
    return "groups";
}