#include "collection.h"
#include "spawner.h"
#include "emitter.h"
#include "effect.h"
#include "def.h"
#include "manager.h"
#include <tinyxml2.h>
#include <utils/vectorParser.h>
#include <log.h>

using namespace tinyxml2;

inline vec2f parseVec2f(const char *str) noexcept;
inline vec3f parseVec3f(const char *str) noexcept;
inline void parseVector(const char *srt, std::vector<uint32_t> &data);

namespace render{
namespace fx{

bool collection::load(const std::string &path)
{
    Logger::Debug("Loading gfx collection %s: ", path.c_str());
    XMLDocument doc;
    if(doc.LoadFile(path.c_str()) != XML_SUCCESS)
    {
        return 0;
    }

    if(XMLElement *sourceElement = doc.RootElement()->FirstChildElement("sources"))
        loadAtlases(sourceElement);
    if(XMLElement *objectElement = doc.RootElement()->FirstChildElement("objects"))
        loadObjects(objectElement);
    if(XMLElement *effectElement = doc.RootElement()->FirstChildElement("effects"))
        loadEffects(effectElement);

    return 1;
}

const objectInfo *collection::getObject(const std::string &id) const
{
    for(auto i: objects)
        if(i->id == id)
            return i;
    return nullptr;
}


const render::sprite::textureAtlas *collection::getAtlas(const std::string &id) const
{
    for(int i = 0; i < atlases.size(); i++)
        if(atlases[i].id == id)
            return &atlases[i].atlas;
    return nullptr;
}

object *collection::createObject(const std::string &id, float time, effect *root) const
{
    for(auto i: objects)
        if(i->id == id)
            return i->create(time, root);
    return nullptr;
}

effect *collection::createEffect(const std::string &id)
{
    for(auto &i: data)
        if(i.id == id)
            return i.create();
    return nullptr;
}

std::shared_ptr<effect> &collection::createSharedEffect(const std::string &id)
{
    for(auto &i: data)
        if(i.id == id)
            return addManagedEffect(i.create());
    throw std::runtime_error("can't find desired effect");
}

const effectInfo *collection::getEffectDef(const std::string &id)
{
    for(auto &i: data)
        if(i.id == id)
            return &i;

    return nullptr;
}

}}

using namespace render::fx;

objectInfo* parseEmitter(XMLElement *element);

void collection::loadAtlases(XMLElement *element)
{
    if(XMLElement *atlasElement = element->FirstChildElement("atlas"))
    do
    {
        atlases.emplace_back();
        atlases.back().atlas.load(atlasElement->Attribute("path"), 1, 1);
        atlases.back().id = XMLUtil::SkipWhiteSpace(atlasElement->Attribute("id"), 0);
    }while(atlasElement = atlasElement->NextSiblingElement("atlas"));
}

void collection::loadObjects(XMLElement *element)
{
    if(XMLElement *objectElement = element->FirstChildElement())
    do
    {
        if(!strcmp(objectElement->Name(), "emitter"))
        {
            objects.push_back(parseEmitter(objectElement));
            objects.back()->root = this;
        }
        else
            Logger::Warning("fx: Unknown object type\n");

    }while(objectElement = objectElement->NextSiblingElement());
}

void collection::loadEffects(XMLElement *element)
{
    if(XMLElement *effectElement = element->FirstChildElement())
    do
    {
        data.emplace_back();
        effectInfo &info = data.back();
        info.root = this;
        info.id = effectElement->Attribute("id");
        info.looped = effectElement->BoolAttribute("looped");
        if(effectElement->Attribute("next"))
            info.next = effectElement->Attribute("next");

        if(XMLElement *objectElement = effectElement->FirstChildElement())
        do
        {
            info.objects.emplace_back();
            info.objects.back().id = objectElement->Attribute("id");
            info.objects.back().workTime = objectElement->FloatAttribute("time");
            if(XMLElement *nodeElement = objectElement->FirstChildElement("position")->FirstChildElement())
            do
            {
                info.objects.back().position.values.emplace_back(
                    nodeElement->FloatAttribute("time"),
                    parseVec2f(nodeElement->Attribute("value")));
            }while(nodeElement = nodeElement->NextSiblingElement());

        }while(objectElement = objectElement->NextSiblingElement());
    }while(effectElement = effectElement->NextSiblingElement());
}

objectInfo* parseEmitter(XMLElement *element)
{
    emitterInfo *em = new emitterInfo;
    em->id = element->Attribute("id");
    if(XMLElement *emissionElement = element->FirstChildElement("emission"))
    {
        if(XMLElement *nodeElement = emissionElement->FirstChildElement("life")->FirstChildElement())
        do
        {
            em->lifeTime.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                vec2f(nodeElement->FloatAttribute("value"),
                      nodeElement->FloatAttribute("spread")), 0);
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = emissionElement->FirstChildElement("emission")->FirstChildElement())
        do
        {
            em->emission.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                nodeElement->FloatAttribute("value"), 0);
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = emissionElement->FirstChildElement("direction")->FirstChildElement())
        do
        {
            em->direction.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                vec2f(nodeElement->FloatAttribute("value")*DTOR,
                      nodeElement->FloatAttribute("spread")*DTOR), 0);
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *spawnerElement = emissionElement->FirstChildElement("spawner"))
        {
            constexpr uint32_t spawnerTypes = 1;
            constexpr char *spawnerNames[] = {"rect"};
            for(uint32_t i = 0; i < spawnerTypes; i++)
                if(!strcmp(spawnerElement->Attribute("type"), spawnerNames[i]))
                {
                    switch(i)
                    {
                    case 0: // rect
                    {
                        particleRectSpawnerInfo *spawner = new particleRectSpawnerInfo;
                        if(XMLElement *nodeElement = spawnerElement->FirstChildElement())
                        do
                        {
                            spawner->sizeParam.values.emplace_back(
                                nodeElement->FloatAttribute("time"),
                                parseVec2f(nodeElement->Attribute("value")),
                                parseVec2f(nodeElement->Attribute("spread")));
                        }while(nodeElement = nodeElement->NextSiblingElement());
                        em->spawnerInfo = spawner;
                    }
                    break;
                    }
                    break;
                }
        }

    }

    if(XMLElement *particlesElement = element->FirstChildElement("particles"))
    {
        if(XMLElement *blendElement = particlesElement->FirstChildElement("blend"))
        {
            if(!strcmp("add", blendElement->GetText()))
                em->blend = render::blendMode::add;
            else
                em->blend = render::blendMode::normal;
        }else em->blend = render::blendMode::normal;

        if(XMLElement *atlasElement = particlesElement->FirstChildElement("atlas"))
        {
            em->texAtlas = atlasElement->Attribute("id");
            if(XMLElement *nodeElement = atlasElement->FirstChildElement())
            do
            {
                em->texNodes.emplace_back(nodeElement->Attribute("name"));
            }while(nodeElement = nodeElement->NextSiblingElement());
        }

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("animation")->FirstChildElement())
        do
        {
            textureAnimationInfo info;
            nodeElement->QueryAttribute("time", &info.time);
            parseVector(nodeElement->Attribute("value"), info.nodes);
            em->animation.push_back(info);
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("alpha")->FirstChildElement())
        do
        {
            em->alpha.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                nodeElement->FloatAttribute("value"),
                nodeElement->FloatAttribute("spread"));
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("angle")->FirstChildElement())
        do
        {
            em->angle.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                nodeElement->FloatAttribute("value"),
                nodeElement->FloatAttribute("spread"));
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("speed")->FirstChildElement())
        do
        {
            em->speed.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                nodeElement->FloatAttribute("value"),
                nodeElement->FloatAttribute("spread"));
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("scale")->FirstChildElement())
        do
        {
            em->scale.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                parseVec2f(nodeElement->Attribute("value")),
                parseVec2f(nodeElement->Attribute("spread")));
        }while(nodeElement = nodeElement->NextSiblingElement());

        if(XMLElement *nodeElement = particlesElement->FirstChildElement("color")->FirstChildElement())
        do
        {
            em->color.values.emplace_back(
                nodeElement->FloatAttribute("time"),
                parseVec3f(nodeElement->Attribute("value")),
                parseVec3f(nodeElement->Attribute("spread")));
        }while(nodeElement = nodeElement->NextSiblingElement());
    }
    return em;
}
