/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#pragma once

#include <memory>
#include <unordered_map>

#include "cif/common/cif.h"
#include "cif/common/id.h"
#include "cif/common/compatibility.h"
#include "cif/export/interface_creator.h"

namespace CIF {

namespace Helpers {
  struct ForwardGetSupportedVersions {
    template <template <Version_t> class Interface>
    static bool Call(Version_t &verMin, Version_t &verMax) {
      verMax = Interface<CIF::TraitsSpecialVersion>::GetLatestSupportedVersion();
      verMin = Interface<CIF::TraitsSpecialVersion>::GetOldestSupportedVersion();
      return true;
    }
  };
}

struct EntryPointInterfaceBase{
    EntryPointInterfaceBase(){
    }
    virtual ~EntryPointInterfaceBase() = default;

    virtual ICIF * Create(Version_t version, ICIF * parent) const = 0;
    virtual InterfaceId_t GetFirstIncompatible(CIF::CompatibilityDataHandle handle) const = 0;
    virtual void GetSupportedVersions(Version_t &verMin, Version_t &verMax) const = 0;
    virtual bool FindSupportedVersions(CIF::InterfaceId_t interfaceToFind, Version_t &verMin, Version_t &verMax) const = 0;
};

template<template <Version_t> class Interface>
struct EntryPointInterface : EntryPointInterfaceBase {
    EntryPointInterface(){
    }

    ICIF * Create(Version_t version, ICIF * parent) const override{
        return CIF::InterfaceCreator<Interface>::template CreateInterfaceVer(version, version, parent);
    }

    InterfaceId_t GetFirstIncompatible(CIF::CompatibilityDataHandle handle) const override{
        CIF::CompatibilityEncoder::IncompatibilityData ret;
        auto incompatible = CompatibilityEncoder::GetFirstIncompatible<Interface>(handle, ret);
        if(incompatible == false){
            return CIF::InvalidInterface;
        }

        return ret.Interface;
    }

    void GetSupportedVersions(Version_t &verMin, Version_t &verMax) const override{
      verMax = Interface<CIF::TraitsSpecialVersion>::GetLatestSupportedVersion();
      verMin = Interface<CIF::TraitsSpecialVersion>::GetOldestSupportedVersion();
    }

    bool FindSupportedVersions(CIF::InterfaceId_t interfaceToFind, Version_t &verMin, Version_t &verMax) const override{
        return Interface<CIF::TraitsSpecialVersion>::AllUsedInterfaces::template forwardToOne<Helpers::ForwardGetSupportedVersions, bool>(interfaceToFind, false, verMin, verMax);
    }
};

struct EntryPointRegistry {
protected:
    void Clear(){
        registeredEntryPoints.clear();
    }

public:
    static EntryPointRegistry & Get () {
        static EntryPointRegistry r;
        return r;
    }

    template<template <Version_t> class Interface>
    void Register(){
        assert(registeredEntryPoints.find(Interface<BaseVersion>::GetInterfaceId()) == registeredEntryPoints.end());
        registeredEntryPoints[Interface<BaseVersion>::GetInterfaceId()]
            = std::unique_ptr<EntryPointInterfaceBase>(new EntryPointInterface<Interface>{});
    }

    EntryPointInterfaceBase * GetEntryPointInterface(InterfaceId_t interface) const {
        auto it = registeredEntryPoints.find(interface);
        if(it == registeredEntryPoints.end()){
            return nullptr;
        }

        return &*it->second;
    }

    template<typename Sentinel = void>
    void RegisterAll(){
    }

    template<template <Version_t> class Interface, template <Version_t> class ... RestInterfaces>
    void RegisterAll(){
        Register<Interface>();
        RegisterAll<RestInterfaces...>();
    }

#include "cif/macros/enable.h"
    CIF_ULT_EXPOSE(Clear)
#include "cif/macros/disable.h"

protected:
    using RegistryContainerT = std::unordered_map<InterfaceId_t, std::unique_ptr<EntryPointInterfaceBase>>;
    RegistryContainerT registeredEntryPoints;
};

template<template <Version_t> class Interface>
struct RegisterEntryPoint{
    RegisterEntryPoint(){
        EntryPointRegistry::Get().Register<Interface>();
    }
};

}
