// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <memory>
#include <string>
#include <vector>

#include "openvino/core/any.hpp"
#include "openvino/core/extension.hpp"
#include "openvino/core/model.hpp"
#include "openvino/core/op_extension.hpp"
#include "openvino/frontend/input_model.hpp"
#include "openvino/frontend/visibility.hpp"

namespace ov {
namespace frontend {
/// \brief An interface for identifying a frontend for a particular framework.
/// Provides an ability to load and convert of input model
class FRONTEND_API FrontEnd {
    friend class FrontEndManager;

    std::shared_ptr<void> m_shared_object = {};  // Library handle
    std::shared_ptr<FrontEnd> m_actual = {};

public:
    using Ptr = std::shared_ptr<FrontEnd>;

    /// \brief Default constructor
    FrontEnd();

    FrontEnd(const FrontEnd&) = delete;

    FrontEnd(FrontEnd&&) = delete;

    FrontEnd& operator=(const FrontEnd&) = delete;

    FrontEnd& operator=(FrontEnd&&) = delete;

    virtual ~FrontEnd();

    /// \brief Validates if FrontEnd can recognize model with parameters specified.
    /// Same parameters should be used to load model.
    /// \param vars Any number of parameters of any type. What kind of parameters
    /// are accepted is determined by each FrontEnd individually, typically it is
    /// std::string containing path to the model file. For more information please
    /// refer to specific FrontEnd documentation.
    /// \return true if model recognized, false - otherwise.
    template <typename... Types>
    inline bool supported(const Types&... vars) const {
        return supported_impl({ov::Any(vars)...});
    }

    /// \brief Loads an input model by any specified arguments. Each FrontEnd separately
    /// defines what arguments it can accept.
    /// \param vars Any number of parameters of any type. What kind of parameters
    /// are accepted is determined by each FrontEnd individually, typically it is
    /// std::string containing path to the model file. For more information please
    /// refer to specific FrontEnd documentation.
    /// \return Loaded input model.
    template <typename... Types>
    inline InputModel::Ptr load(const Types&... vars) const {
        return load_impl({ov::Any{vars}...});
    }

    inline InputModel::Ptr load(const ov::AnyVector& vars) const {
        return load_impl(vars);
    }

    /// \brief Completely convert and normalize entire Model, throws if it is not
    /// possible
    /// \param model Input model
    /// \return fully converted OV Model
    virtual std::shared_ptr<ov::Model> convert(const InputModel::Ptr& model) const;

    /// \brief Completely convert the remaining, not converted part of a Model.
    /// \param partiallyConverted partially converted OV Model
    virtual void convert(const std::shared_ptr<ov::Model>& partially_converted) const;

    /// \brief Convert only those parts of the model that can be converted leaving others
    /// as-is. Converted parts are not normalized by additional transformations; normalize
    /// Model or another form of convert Model should be called to finalize the
    /// conversion process.
    /// \param model Input model
    /// \return partially converted OV Model
    virtual std::shared_ptr<ov::Model> convert_partially(const InputModel::Ptr& model) const;

    /// \brief Convert operations with one-to-one mapping with decoding nodes.
    /// Each decoding node is an OV node representing a single FW operation node with
    /// all attributes represented in FW-independent way.
    /// \param model Input model
    /// \return OV Model after decoding
    virtual std::shared_ptr<ov::Model> decode(const InputModel::Ptr& model) const;

    /// \brief Runs normalization passes on Model that was loaded with partial conversion
    /// \param Model partially converted OV Model
    virtual void normalize(const std::shared_ptr<ov::Model>& model) const;

    /// \brief Gets name of this FrontEnd. Can be used by clients
    /// if frontend is selected automatically by FrontEndManager::load_by_model
    ///
    /// \return Current frontend name. Empty string if not implemented
    virtual std::string get_name() const;

    /// \brief Register base extension in the FrontEnd
    /// \param extension base extension
    virtual void add_extension(const std::shared_ptr<ov::Extension>& extension);

    /// \brief Register base extensions in the FrontEnd
    /// \param extensions vector of extensions
    void add_extension(const std::vector<std::shared_ptr<ov::Extension>>& extensions);

    /// \brief Registers extension
    /// \param library_path path to library with ov::Extension
    void add_extension(const std::string& library_path);

#ifdef OPENVINO_ENABLE_UNICODE_PATH_SUPPORT

    /// \brief Registers extension
    /// \param library_path path to library with ov::Extension
    void add_extension(const std::wstring& library_path);

#endif

    /// @brief Registers extension
    /// @param extension Extension class which is inherited from ov::BaseOpExtension class
    template <class T, typename std::enable_if<std::is_base_of<ov::Extension, T>::value, bool>::type = true>
    void add_extension(const T& extension) {
        std::shared_ptr<ov::Extension> ext = std::make_shared<T>(extension);
        add_extension(ext);
    }

    /// @brief Registers extensions
    /// @param extension Extension class which is inherited from ov::Extension class
    template <class T,
              class... Targs,
              typename std::enable_if<std::is_base_of<ov::Extension, T>::value, bool>::type = true>
    void add_extension(const T& extension, Targs... args) {
        std::shared_ptr<ov::Extension> ext = std::make_shared<T>(extension);
        add_extension(ext);
        add_extension(args...);
    }

protected:
    virtual bool supported_impl(const std::vector<ov::Any>& variants) const;

    virtual InputModel::Ptr load_impl(const std::vector<ov::Any>& variants) const;

private:
    static std::shared_ptr<ov::Model> create_copy(const std::shared_ptr<ov::Model>& ov_model,
                                                  const std::shared_ptr<void>& shared_object);
};

template <>
inline bool FrontEnd::supported(const std::vector<ov::Any>& variants) const {
    return supported_impl(variants);
}

}  // namespace frontend

}  // namespace ov
