#pragma once

#include <Array.h>
#include <cstdlib>
#include <DataValidator.h>
#include <JSON.h>
#include <Logging.h>
#include <Trie.h>
#include <typeinfo>

class TypeRegistry;

#define JSONObjectValidationBuilder                \
    Framework::Validator::ObjectValidationBuilder< \
        Framework::Validator::DataValidator>

template<typename T> class SimpleTypeFactory
    : public Framework::ReferenceCounter
{
public:
    SimpleTypeFactory()
        : ReferenceCounter() {};
    virtual T fromJson(Framework::JSON::JSONValue* zJson) const = 0;
    virtual Framework::JSON::JSONValue* toJson(T value) const = 0;
    virtual Framework::Validator::DataValidator* getValidator() const = 0;
};

/*
 * Used to convert an object of type T to a JSONValue and vice versa.
 * Can be registered at the TypeRegistry to be used by the JSON system
 */
template<typename T> class ObjectTypeFactory : public SimpleTypeFactory<T*>
{
public:
    ObjectTypeFactory()
        : SimpleTypeFactory<T*>() {};

    T* fromJson(Framework::JSON::JSONValue* zJson) const final override
    {
        return fromJson(zJson->asObject());
    }

    Framework::JSON::JSONValue* toJson(T* value) const final override
    {
        return toJsonObject(value);
    }

    virtual Framework::Validator::DataValidator* getValidator() const override
    {
        return addToValidator(
            Framework::Validator::DataValidator::buildForObject())
            ->finishObject();
    };

    virtual JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const
        = 0;

protected:
    virtual T* fromJson(Framework::JSON::JSONObject* zJson) const = 0;
    virtual Framework::JSON::JSONObject* toJsonObject(T* zObject) const = 0;
};

/*
 * Used to convert an object of type S witch inherits from type T to a
 * JSONValue and vice versa. Can be registered at the TypeRegistry to be
 * used by the JSON
 */
template<typename T,
    typename S,
    typename = std::enable_if<std::is_base_of<T, S>::value>>
class SubTypeFactory : public ObjectTypeFactory<S>
{
public:
    SubTypeFactory()
        : ObjectTypeFactory<S>() {};

    virtual Framework::Validator::DataValidator*
    getValidator() const final override
    {
        Framework::Text referenceId = "_type_";
        referenceId.append() << typeid(T).name() << "_" << getTypeToken();
        return this
            ->addToValidator(
                Framework::Validator::DataValidator::buildForObject()
                    ->withRequiredString("type")
                    ->withExactMatch(getTypeToken())
                    ->finishString()
                    ->setReferenceId(referenceId))
            ->finishObject();
    };

    virtual const char* getTypeToken() const = 0;

    virtual const char* getTypeName() const
    {
        return typeid(S).name();
    }
};

template<typename T> class SubTypeFactoryRef
    : public Framework::ReferenceCounter
{
private:
    std::function<const char*()> typetokenFunc;
    std::function<T*(Framework::JSON::JSONValue*)> fromJsonFunc;
    std::function<Framework::JSON::JSONValue*(T*)> toJsonFunc;
    std::function<Framework::Validator::DataValidator*()> getValidatorFunc;
    Framework::ReferenceCounter* factory;

public:
    SubTypeFactoryRef(std::function<const char*()> typetokenFunc,
        std::function<T*(Framework::JSON::JSONValue*)> fromJsonFunc,
        std::function<Framework::JSON::JSONValue*(T*)> toJsonFunc,
        std::function<Framework::Validator::DataValidator*()> getValidatorFunc,
        Framework::ReferenceCounter* factory)
        : ReferenceCounter(),
          typetokenFunc(typetokenFunc),
          fromJsonFunc(fromJsonFunc),
          toJsonFunc(toJsonFunc),
          getValidatorFunc(getValidatorFunc),
          factory(factory)
    {}

    ~SubTypeFactoryRef()
    {
        factory->release();
    }

    T* fromJson(Framework::JSON::JSONValue* zJson) const
    {
        return fromJsonFunc(zJson);
    }

    Framework::JSON::JSONValue* toJson(T* zObject) const
    {
        return toJsonFunc(zObject);
    }

    Framework::Validator::DataValidator* getValidator() const
    {
        return getValidatorFunc();
    }

    const char* getTypeToken() const
    {
        return typetokenFunc();
    }
};

template<typename T> class PolymorphTypeFactory : public ObjectTypeFactory<T>
{
private:
    Framework::RCArray<SubTypeFactoryRef<T>> factories;
    Framework::RCArray<Framework::Text> typeNames;
    static thread_local bool insideGetValidator;

public:
    PolymorphTypeFactory()
        : ObjectTypeFactory<T>()
    {}

    T* fromJson(Framework::JSON::JSONObject* zJson) const override
    {
        for (SubTypeFactoryRef<T>* factory : factories)
        {
            if (zJson->zValue("type")->asString()->getString().istGleich(
                    factory->getTypeToken()))
            {
                return factory->fromJson(zJson);
            }
        }
        Framework::Logging::error()
            << "No Sub Type Factory for typetoken "
            << zJson->zValue("type")->asString()->getString() << " of type "
            << typeid(T).name() << " was found.";
        return 0;
    }

    Framework::JSON::JSONObject* toJsonObject(T* zObject) const override
    {
        auto name = typeNames.begin();
        for (SubTypeFactoryRef<T>* factory : factories)
        {
            if (name->istGleich(typeid(*zObject).name()))
            {
                Framework::JSON::JSONObject* result
                    = factory->toJson(zObject)->asObject();
                result->addValue("type",
                    new Framework::JSON::JSONString(factory->getTypeToken()));
                return result;
            }
            name++;
        }
        Framework::Logging::error()
            << "No Sub Type Factory for subtype " << typeid(*zObject).name()
            << " of type " << typeid(T).name() << " was found.";
        return new Framework::JSON::JSONObject();
    }

    JSONObjectValidationBuilder* addToValidator(
        JSONObjectValidationBuilder* builder) const override
    {
        Framework::Logging::error()
            << "Can not add polymorph json validator to an object "
               "validation builder";
        throw "Can not add polymorph json "
              "validator to an object "
              "validation builder";
    }

    Framework::Validator::DataValidator* getValidator() const override
    {
        Framework::Validator::DataValidator* result;
        if (!insideGetValidator)
        {
            insideGetValidator = true;
            auto validator
                = Framework::Validator::DataValidator::buildForOneOf()
                      ->typeSpecifiedByAttribute("type");
            for (SubTypeFactoryRef<T>* factory : factories)
            {
                validator = validator->addAcceptedType(factory->getValidator());
            }
            result = validator->finishOneOf();
            insideGetValidator = false;
        }
        else
        {
            auto validator
                = Framework::Validator::DataValidator::buildForOneOf()
                      ->typeSpecifiedByAttribute("type");
            for (SubTypeFactoryRef<T>* factory : factories)
            {
                validator = validator->addAcceptedType(
                    Framework::Validator::DataValidator::buildForReference(
                        ((Framework::Text("_type_") += typeid(T).name()) += "_")
                        += factory->getTypeToken()));
            }
            result = validator->finishOneOf();
        }
        return result;
    }

    template<typename S,
        typename = std::enable_if<std::is_base_of<T, S>::value>>
    void addFactory(SubTypeFactory<T, S>* factory)
    {
        factories.add(new SubTypeFactoryRef<T>(
            [factory]() { return factory->getTypeToken(); },
            [factory](Framework::JSON::JSONValue* zJson) {
                S* value = factory->fromJson(zJson);
                if (value)
                {
                    return dynamic_cast<T*>(value);
                }
                return (T*)0;
            },
            [factory](T* zObject) {
                return factory->toJson(dynamic_cast<S*>(zObject));
            },
            [factory]() { return factory->getValidator(); },
            dynamic_cast<Framework::ReferenceCounter*>(factory)));
        typeNames.add(new Framework::Text(factory->getTypeName()));
    }
};

template<typename T>
thread_local bool PolymorphTypeFactory<T>::insideGetValidator = false;

class TypeFatoryRef : public Framework::ReferenceCounter
{
private:
    std::function<void*(Framework::JSON::JSONValue*)> fromJsonFunc;
    std::function<Framework::JSON::JSONValue*(void*)> toJsonFunc;
    std::function<Framework::Validator::DataValidator*()> getValidatorFunc;
    Framework::ReferenceCounter* factory;

public:
    TypeFatoryRef(
        std::function<void*(Framework::JSON::JSONValue*)> fromJsonFunc,
        std::function<Framework::JSON::JSONValue*(void*)> toJsonFunc,
        std::function<Framework::Validator::DataValidator*()> getValidatorFunc,
        Framework::ReferenceCounter* factory)
        : ReferenceCounter(),
          fromJsonFunc(fromJsonFunc),
          toJsonFunc(toJsonFunc),
          getValidatorFunc(getValidatorFunc),
          factory(factory)
    {}

    ~TypeFatoryRef()
    {
        factory->release();
    }

    void* fromJson(Framework::JSON::JSONValue* zJson) const
    {
        return fromJsonFunc(zJson);
    }

    Framework::JSON::JSONValue* toJson(void* zObject) const
    {
        return toJsonFunc(zObject);
    }

    Framework::Validator::DataValidator* getValidator() const
    {
        return getValidatorFunc();
    }

    Framework::ReferenceCounter* zFactory() const
    {
        return factory;
    }
};

class TypeRegistry : public Framework::ReferenceCounter
{
private:
    Framework::RCTrie<TypeFatoryRef> parsableTypes;
    Framework::RCArray<Framework::Text> parsableTypeNames;

public:
    TypeRegistry();

    template<typename T,
        typename S,
        typename = std::enable_if<std::is_base_of<T, S>::value>>
    void registerSubType(SubTypeFactory<T, S>* factory)
    {
        Framework::Text typeId = typeid(T).name();
        TypeFatoryRef* typeFactoryRef
            = parsableTypes.z(typeId, typeId.getLength());
        if (!typeFactoryRef)
        {
            PolymorphTypeFactory<T>* polymorphFactory
                = new PolymorphTypeFactory<T>();
            registerType(polymorphFactory);
            typeFactoryRef = parsableTypes.z(typeId, typeId.getLength());
            parsableTypeNames.add(new Framework::Text(typeId));
        }
        PolymorphTypeFactory<T>* polymorphFactory
            = dynamic_cast<PolymorphTypeFactory<T>*>(
                typeFactoryRef->zFactory());
        if (!polymorphFactory)
        {
            Framework::Logging::error()
                << Framework::Text("Type not registered as Polymorphic type: ")
                       + typeId;
            throw Framework::Text("Type not registered as Polymorphic type: ")
                + typeId;
        }
        polymorphFactory->addFactory<S>(factory);
    }

    template<typename T> void registerType(ObjectTypeFactory<T>* factory)
    {
        registerType(typeid(T).name(), factory);
    }

    template<typename T>
    void registerType(Framework::Text typeId, SimpleTypeFactory<T>* factory)
    {
        TypeFatoryRef* typeFactoryRef
            = parsableTypes.z(typeId, typeId.getLength());
        if (typeFactoryRef)
        {
            Framework::Logging::error()
                << Framework::Text("Type already registered: ") + typeId;
            throw Framework::Text("Type already registered: ") + typeId;
        }
        typeFactoryRef = new TypeFatoryRef(
            [factory](Framework::JSON::JSONValue* zJson) {
                return (void*)factory->fromJson(zJson);
            },
            [factory](void* zObject) { return factory->toJson((T)zObject); },
            [factory]() { return factory->getValidator(); },
            factory);
        parsableTypes.set(typeId, typeId.getLength(), typeFactoryRef);
        parsableTypeNames.add(new Framework::Text(typeId));
    }

    template<typename T>
    T fromJson(Framework::Text typeId, Framework::JSON::JSONValue* zJson) const
    {
        TypeFatoryRef* typeFactoryRef
            = parsableTypes.z(typeId, typeId.getLength());
        if (!typeFactoryRef)
        {
            Framework::Logging::error()
                << Framework::Text("Type not registered: ") + typeId;
            throw Framework::Text("Type not registered: ") + typeId;
        }
        return (T)(typeFactoryRef->fromJson(zJson));
    }

    template<typename T> T* fromJson(Framework::JSON::JSONValue* zJson) const
    {
        return fromJson<T*>(typeid(T).name(), zJson);
    }

    template<typename T> Framework::JSON::JSONValue* toJson(T* zObject) const
    {
        return toJson<T*>(typeid(T).name(), zObject);
    }

    template<typename T>
    Framework::JSON::JSONValue* toJson(Framework::Text typeId, T zObject) const
    {
        TypeFatoryRef* typeFactoryRef
            = parsableTypes.z(typeId, typeId.getLength());
        if (!typeFactoryRef)
        {
            Framework::Logging::error()
                << Framework::Text("Type not registered: ") + typeId;
            throw Framework::Text("Type not registered: ") + typeId;
        }
        return typeFactoryRef->toJson(zObject);
    }

    template<typename T>
    Framework::Validator::DataValidator* getValidator() const
    {
        return getValidator<T>(typeid(T).name());
    }

    template<typename T> Framework::Validator::DataValidator* getValidator(
        Framework::Text typeId) const
    {
        TypeFatoryRef* typeFactoryRef
            = parsableTypes.z(typeId, typeId.getLength());
        if (!typeFactoryRef)
        {
            Framework::Logging::error()
                << Framework::Text("Type not registered: ") + typeId;
            throw Framework::Text("Type not registered: ") + typeId;
        }
        return typeFactoryRef->getValidator();
    }

    template<typename T> Framework::JSON::JSONValue* getValidParts(
        Framework::JSON::JSONValue* zJson) const
    {
        Framework::RCArray<Framework::Validator::ValidationResult> invalidParts;
        Framework::Validator::DataValidator* validator = getValidator<T>();
        Framework::JSON::JSONValue* result
            = validator->getValidParts(zJson, &invalidParts);
        for (Framework::Validator::ValidationResult* invalidPart : invalidParts)
        {
            Framework::Logging::error() << invalidPart->getInvalidInfo();
        }
        return result;
    }

    void writeSyntaxInfo(Framework::Text folderPath) const;
};