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

/**
 * @brief a header file for internal Layers structure to describe layers information
 * @file ie_layers.h
 */
#pragma once

#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <iterator>
#include <cctype>
#include "ie_common.h"
#include "ie_data.h"
#include "ie_blob.h"
#include "ie_device.hpp"
#include "ie_layers_property.hpp"

namespace InferenceEngine {
/**
 * @brief This is an internal common Layer parameter parsing arguments
 */
struct LayerParams {
    /// @brief Layer name
    std::string name;
    /// @brief Layer type
    std::string type;
    /// @brief Layer precision
    Precision precision;
};

/**
 * @brief This is a base abstraction Layer - all DNN Layers inherit from this class
 */
class CNNLayer  {
public:
    /**
     * @brief A shared pointer to CNNLayer
     */
    using  Ptr = std::shared_ptr<CNNLayer>;

    /**
     * @brief Layer name
     */
    std::string name;
    /**
     * @brief Layer type
     */
    std::string type;
    /**
     * @brief Layer base operating precision
     */
    Precision precision;
    /**
     * @brief A vector of pointers to the output data elements of this layer in the di-graph (order matters)
     */
    std::vector<DataPtr> outData;
    /**
     * @brief A vector of weak pointers to the input data elements of this layer in the di-graph (order matters)
     */
    std::vector<DataWeakPtr> insData;
    /**
     * @brief If suggested to fuse - a pointer to the layer which needs to be fused with this layer
     */
    Ptr _fusedWith;
    /**
     * @brief Convenience user values to store in this object as extra data
     */
    UserValue userValue;
    /**
     * @brief Layer affinity set by user.
     */
    std::string affinity;

    /**
     * @brief A constructor. Creates a new CNNLayer instance and initializes layer parameters with the given values.
     * @param prms Basic common parsing parameters
     */
    explicit CNNLayer(const LayerParams &prms) : name(prms.name), type(prms.type),
                                                 precision(prms.precision), userValue({0}) {
    }

    /**
     * @brief A virtual destructor
     */
    virtual ~CNNLayer() = default;

    /**
     * @brief Sets a layer to be fused with
     * @param layer Reference to the layer to be fused with
     */
    void fuse(Ptr &layer) {
        _fusedWith = layer;
    }

    /**
     * @brief Returns the first element of the input data for this layer
     * @return A smart pointer to the input data element
     */
    virtual const DataPtr input() const {
        if (insData.empty()) {
            THROW_IE_EXCEPTION << "Internal error: input data is empty";
        }
        auto lockedFirstInsData = insData[0].lock();
        if (!lockedFirstInsData) {
            THROW_IE_EXCEPTION << "Internal error: unable to lock weak_ptr\n";
        }
        return lockedFirstInsData;
    }

    /**
     * @brief Checks if the input data and layer data are legitimate
     */
    INFERENCE_ENGINE_API_CPP(void) validateLayer();

    /**
     * @brief Gets float value for the given parameter
     * @param param - name of the parameter to find
     * @param def - default value of the parameter if not found
     * @return float value
     */
    float GetParamAsFloat(const char* param, float def) const {
        std::string val = GetParamAsString(param, std::to_string(def).c_str());
        try {
            return std::stof(val);
        } catch (...) {
            THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                               << ". Value " << val << " cannot be casted to float.";
        }
    }

    /**
     * @brief Returns a float value for the given layer parameter
     * @param param Name of the layer parameter
     * @return A float value for the specified parameter
     */
    float GetParamAsFloat(const char *param) const {
        std::string val = GetParamAsString(param);
        try {
            return std::stof(val);
        } catch (...) {
            THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                               << ". Value " << val << " cannot be casted to float.";
        }
    }

    /**
     * @brief Returns a vector of float values for the given parameter or returns the default value
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return vector of float values
     */
    std::vector<float> GetParamAsFloats(const char *param, std::vector<float> def) const {
        std::string vals = GetParamAsString(param, "");
        std::vector<float> result;
        std::istringstream stream(vals);
        std::string str;
        if (vals.empty())
            return def;
        while (getline(stream, str, ',')) {
            try {
                result.push_back(std::stof(str));
            } catch (...) {
                THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                   << ". Value " << vals << " cannot be casted to floats.";
            }
        }
        return result;
    }

    /**
     * @brief Returns a vector of float values for the given parameter
     * @param param Name of the layer parameter
     * @return vector of float values
     */
    std::vector<float> GetParamAsFloats(const char *param) const {
        std::string vals = GetParamAsString(param);
        std::vector<float> result;
        std::istringstream stream(vals);
        std::string str;
        while (getline(stream, str, ',')) {
            try {
                result.push_back(std::stof(str));
            } catch (...) {
                THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                   << ". Value " << vals << " cannot be casted to floats.";
            }
        }
        return result;
    }

    /**
     * @brief Returns an integer value for the given parameter or returns the default value
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return An int value for the specified parameter
     */
    int GetParamAsInt(const char *param, int def) const {
        std::string val = GetParamAsString(param, std::to_string(def).c_str());
        try {
            return std::stoi(val);
        } catch (...) {
            THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                               << ". Value " << val << " cannot be casted to int.";
        }
    }

    /**
     * @brief Returns an integer value for the given parameter
     * @param param Name of the layer parameter
     * @return An int value for the specified parameter
     */
    int GetParamAsInt(const char *param) const {
        std::string val = GetParamAsString(param);
        try {
            return std::stoi(val);
        } catch (...) {
            THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                               << ". Value " << val << " cannot be casted to int.";
        }
    }


    /**
     * @brief Returns a vector of int values for the given parameter or returns the default value
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return vector of int values
     */
    std::vector<int> GetParamAsInts(const char *param, std::vector<int> def) const {
        std::string vals = GetParamAsString(param, "");
        std::vector<int> result;
        std::istringstream stream(vals);
        std::string str;
        if (vals.empty())
            return def;
        while (getline(stream, str, ',')) {
            try {
                result.push_back(std::stoi(str));
            } catch (...) {
                THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                   << ". Value " << vals << " cannot be casted to int.";
            }
        }
        return result;
    }

    /**
     * @brief Returns a vector of int values for the given parameter
     * @param param Name of the layer parameter
     * @return vector of int values
     */
    std::vector<int> GetParamAsInts(const char *param) const {
        std::string vals = GetParamAsString(param);
        std::vector<int> result;
        std::istringstream stream(vals);
        std::string str;
        while (getline(stream, str, ',')) {
            try {
                result.push_back(std::stoi(str));
            } catch (...) {
                THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                        << ". Value " << vals <<  " cannot be casted to int.";
            }
        }
        return result;
    }
    /**
     * @brief Returns an unsigned integer value for the given parameter or returns the default value
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return An unsigned integer value for the specified parameter
     */
    unsigned int GetParamAsUInt(const char *param, unsigned int def) const {
        std::string val = GetParamAsString(param, std::to_string(def).c_str());
        std::string message = "Cannot parse parameter " + std::string(param) + " from IR for layer " + name
                              + ". Value " + val + " cannot be casted to int.";
        try {
            int value = std::stoi(val);
            if (value < 0) {
                THROW_IE_EXCEPTION << message;
            }
            return static_cast<unsigned int>(value);
        } catch (...) {
            THROW_IE_EXCEPTION << message;
        }
    }

    /**
     * @brief Returns an unsigned integer value for the given parameter
     * @param param Name of the layer parameter
     * @return An unsigned integer value for the specified parameter
     */
    unsigned int GetParamAsUInt(const char *param) const {
        std::string val = GetParamAsString(param);
        std::string message = "Cannot parse parameter " + std::string(param) + " from IR for layer " + name
                                                 + ". Value " + val + " cannot be casted to int.";
        try {
            int value = std::stoi(val);
            if (value < 0) {
                THROW_IE_EXCEPTION << message;
            }
            return static_cast<unsigned int>(value);
        } catch (...) {
            THROW_IE_EXCEPTION << message;
        }
    }


    /**
     * @brief Returns a vector of unsigned int values for the given parameter or returns the default value
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return vector of unsigned int values
     */
    std::vector<unsigned int> GetParamAsUInts(const char *param, std::vector<unsigned int> def) const {
        std::string vals = GetParamAsString(param, "");
        std::vector<unsigned int> result;
        std::istringstream stream(vals);
        std::string str;
        std::string message = "Cannot parse parameter " + std::string(param) + " " + str + " from IR for layer " + name
                              + ". Value " + vals +  " cannot be casted to int.";
        if (vals.empty())
            return def;
        while (getline(stream, str, ',')) {
            try {
                int value = std::stoi(str);
                if (value < 0) {
                    THROW_IE_EXCEPTION << message;
                }
                result.push_back(static_cast<unsigned int>(value));
            } catch (...) {
                THROW_IE_EXCEPTION << message;
            }
        }
        return result;
    }

    /**
     * @brief Returns a vector of unsigned int values for the given parameter
     * @param param Name of the layer parameter
     * @return vector of unsigned int values
     */
    std::vector<unsigned int> GetParamAsUInts(const char *param) const {
        std::string vals = GetParamAsString(param);
        std::vector<unsigned int> result;
        std::istringstream stream(vals);
        std::string str;
        std::string message = "Cannot parse parameter " + std::string(param) + " " + str + " from IR for layer " + name
                                                        + ". Value " + vals +  " cannot be casted to int.";
        while (getline(stream, str, ',')) {
            try {
                int value = std::stoi(str);
                if (value < 0) {
                    THROW_IE_EXCEPTION << message;
                }
                result.push_back(static_cast<unsigned int>(value));
            } catch (...) {
                THROW_IE_EXCEPTION << message;
            }
        }
        return result;
    }
    /**
     * @brief Returns an boolean value for the given parameter.
     * The valid values are (true, false, 1, 0).
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return An bool value for the specified parameter
     */
    bool GetParamAsBool(const char *param, bool def) const {
        std::string val = GetParamAsString(param, std::to_string(def).c_str());
        std::string loweredCaseValue;
        std::transform(val.begin(), val.end(), std::back_inserter(loweredCaseValue), [](char value) {
            return std::tolower(value);
        });

        bool result = false;

        if (!(std::istringstream(loweredCaseValue) >> std::boolalpha >> result)) {
            // attempting parse using non alpha bool
            return (GetParamAsInt(param, def) != 0);
        }

        return result;
    }
    /**
     * @deprecated Use GetParamAsBool function for that functionality
     */
    bool GetParamsAsBool(const char *param, bool def) const {
        return GetParamAsBool(param, def);
    }

    /**
     * @brief Returns a string value for the given parameter or returns the default one
     * @param param Name of the layer parameter
     * @param def Default value of the parameter if not found
     * @return A string value
     */
    std::string GetParamAsString(const char *param, const char *def) const {
        auto it = params.find(param);
        if (it == params.end() || it->second.empty()) {
            return def;
        }
        return (*it).second;
    }

    /**
    * @brief Checks the param presence in the layer
    * @param param Name of the layer parameter
    * @return a bool depending param presence
    */
    bool CheckParamPresence(const char *param) const {
        auto it = params.find(param);
        if (it == params.end()) {
            return false;
        }
        return true;
    }

    /**
     * @brief Returns a string value for the given parameter.
     * Throws exception if parameter was not found.
     * @param param Name of the layer parameter
     * @return A string value
     */
    std::string GetParamAsString(const char *param) const {
        auto it = params.find(param);
        if (it == params.end()) {
            THROW_IE_EXCEPTION << "No such parameter name '" << param << "' for layer " << name;
        }
        return (*it).second;
    }

    std::vector<std::string> GetParamAsStrings(const char *param, std::vector<std::string> def) const {
        std::string vals = GetParamAsString(param, "");
        std::vector<std::string> result;
        std::istringstream stream(vals);
        std::string str;
        if (vals.empty())
            return def;
        while (getline(stream, str, ',')) {
            try {
                result.push_back(str);
            } catch (...) {
                THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name << ".";
            }
        }
        return result;
    }

    /**
     * @brief Map of pairs: (parameter name, parameter value)
     */
    std::map<std::string, std::string> params;

    /**
     * @brief Map of pairs: (name, weights/biases blob)
     */
    std::map<std::string, Blob::Ptr> blobs;
};

/**
 * @brief Alias for CNNLayer object
 */
using GenericLayer = class CNNLayer;

/**
 * @brief This class represents a layer with Weights and/or Biases (e.g. Convolution/Fully Connected, etc.)
 */
class WeightableLayer : public CNNLayer {
public:
    /**
     * @brief A default constructor. Constructs a WeightableLayer instance and initiates layer parameters with the given values
     * @param prms Initial layer parameters
     */
    explicit WeightableLayer(const LayerParams &prms) : CNNLayer(prms) {}

    /**
     * @brief A pointer to a weights blob
     */
    Blob::Ptr _weights;
    /**
     * @brief A pointer to a biases blob
     */
    Blob::Ptr _biases;

    /**
     * @brief Constructs a WeightableLayer instance and initiates layer parameters with the given values
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief convinenent way to declare property with backward compatibility to 2D members
 */
#define DEFINE_PROP(prop_name) \
PropertyVector<unsigned int> prop_name;\
unsigned int &prop_name##_x = prop_name.at(X_AXIS);\
unsigned int &prop_name##_y = prop_name.at(Y_AXIS);\

/**
 * @brief This class represents a standard 3D Convolution Layer
 */
class ConvolutionLayer : public WeightableLayer {
public:
    /**
     * @brief A convolution kernel array [X, Y, Z, ...]
     */
    DEFINE_PROP(_kernel);
    /**
     * @brief A convolution paddings begin array [X, Y, Z, ...]
     */
    DEFINE_PROP(_padding);
    /**
     * @brief A convolution paddings end array [X, Y, Z, ...]
     */
    PropertyVector<unsigned int> _pads_end;
    /**
     * @brief A convolution strides array [X, Y, Z, ...]
     */
    DEFINE_PROP(_stride);
    /**
     * @brief A convolution dilations array [X, Y, Z, ...]
     */
    DEFINE_PROP(_dilation);
    /**
     * @brief A number of output feature maps (size) generating the 3'rd output dimension
     */
    unsigned int _out_depth = 0u;
    /**
     * @brief Number of groups
     */
    unsigned int _group = 1u;
    /**
     * @brief Auto padding type
     */
    std::string _auto_pad;

    /**
     * @brief Creates a new ConvolutionLayer instance.
     */
    explicit ConvolutionLayer(const LayerParams &p) : WeightableLayer(p),
            _kernel(2, 0u), _padding(2, 0u), _stride(2, 1u), _dilation(2, 1u) {}
    /**
     * @brief assignment operator
     */
    ConvolutionLayer & operator = (const ConvolutionLayer & that) {
        if (&that != this) {
            WeightableLayer::operator=(that);
            _kernel = that._kernel;
            _padding = that._padding;
            _pads_end = that._pads_end;
            _stride = that._stride;
            _dilation = that._dilation;
            _out_depth = that._out_depth;
            _group = that._group;
        }
        return *this;
    }
    /**
     * @brief move assignment operator
     */
    ConvolutionLayer& operator = (ConvolutionLayer &&) = default;
    /**
     * @brief copy constructor
     */
    ConvolutionLayer(const ConvolutionLayer & that) : WeightableLayer(that) {
        operator = (that);
    }
    /**
     * @brief move constructor
     */
    ConvolutionLayer(ConvolutionLayer &&) = default;
};

/**
 * @brief This class represents a standard deconvolution layer
 */
class DeconvolutionLayer : public ConvolutionLayer {
 public:
    using ConvolutionLayer::ConvolutionLayer;
    using ConvolutionLayer::operator=;
};

/**
 * @brief This class represents a standard pooling layer
 */
class PoolingLayer : public CNNLayer {
public:
    /**
     * @brief Pooling kernel array [X, Y, Z, ...]
     */
    DEFINE_PROP(_kernel);
    /**
     * @brief Pooling paddings begin array [X, Y, Z, ...]
     */
    DEFINE_PROP(_padding);
    /**
     * @brief Pooling paddings end array [X, Y, Z, ...]
     */
    PropertyVector<unsigned int> _pads_end;
    /**
     * @brief Pooling strides array [X, Y, Z, ...]
     */
    DEFINE_PROP(_stride);

    /**
     * @enum PoolType
     * @brief Defines available pooling types
     */
    enum PoolType {
        MAX = 1,
        AVG = 2,
        STOCH = 3,
        ROI = 4,
        SPACIAL_PYRAMID = 5
    };

    /**
     * @brief A pooling type
     */
    PoolType _type = MAX;

    /**
     * @brief A flag that indicates if padding is excluded or not
     */
    bool _exclude_pad = false;
    /**
     * @brief Auto padding type
     */
    std::string _auto_pad;

    /**
    * @brief Creates a new PoolingLayer instance.
    */
    explicit PoolingLayer(const LayerParams &p) : CNNLayer(p),
            _kernel(2, 0u), _padding(2, 0u), _stride(2, 0u) {}

    /**
     * @brief assignment operator
     */
    PoolingLayer & operator = (const PoolingLayer & that) {
        if (&that != this) {
            CNNLayer::operator=(that);
            _kernel = that._kernel;
            _padding = that._padding;
            _pads_end = that._pads_end;
            _stride = that._stride;
            _type = that._type;
            _exclude_pad = that._exclude_pad;
        }
        return *this;
    }
    /**
     * @brief move assignment operator
     */
    PoolingLayer& operator = (PoolingLayer &&) = default;

    /**
     * @brief copy constructor
     */
    PoolingLayer(const PoolingLayer & that) : CNNLayer(that) {
        operator=(that);
    }

    /**
     * @brief move constructor
     */
    PoolingLayer(PoolingLayer &&) = default;
};

/**
 * @brief This class represents a standard binary convolution layer
 */
class BinaryConvolutionLayer : public WeightableLayer {
public:
    /**
    * @enum eBinaryConvolutionMode
    * @brief Defines possible modes of binary convolution operation
    */
    enum eBinaryConvolutionMode {
        xnor_popcount = 0
    };

    /**
    * @brief Mode of binary convolution operation
    */
    eBinaryConvolutionMode _mode = xnor_popcount;

    /**
     * @brief A number of input feature maps (size) generating the 3'rd input dimension
     */
    unsigned int _in_depth = 0u;

    /**
    * @brief A pad value which is used to fill pad area
    */
    float _pad_value = 0.0f;

    /**
     * @brief A convolution kernel array [X, Y, Z, ...]
     */
    DEFINE_PROP(_kernel);
    /**
     * @brief A convolution paddings begin array [X, Y, Z, ...]
     */
    DEFINE_PROP(_padding);
    /**
     * @brief A convolution paddings end array [X, Y, Z, ...]
     */
    PropertyVector<unsigned int> _pads_end;
    /**
     * @brief A convolution strides array [X, Y, Z, ...]
     */
    DEFINE_PROP(_stride);
    /**
     * @brief A convolution dilations array [X, Y, Z, ...]
     */
    DEFINE_PROP(_dilation);
    /**
     * @brief A number of output feature maps (size) generating the 3'rd output dimension
     */
    unsigned int _out_depth = 0u;
    /**
     * @brief Number of groups
     */
    unsigned int _group = 1u;
    /**
     * @brief Auto padding type
     */
    std::string _auto_pad;

    /**
     * @brief Creates a new BinaryConvolutionLayer instance.
     */
    explicit BinaryConvolutionLayer(const LayerParams &p) : WeightableLayer(p),
            _kernel(2, 0u), _padding(2, 0u), _stride(2, 1u), _dilation(2, 1u) {}
    /**
     * @brief assignment operator
     */
    BinaryConvolutionLayer & operator = (const BinaryConvolutionLayer & that) {
        if (&that != this) {
            WeightableLayer::operator=(that);
            _kernel = that._kernel;
            _padding = that._padding;
            _pads_end = that._pads_end;
            _stride = that._stride;
            _dilation = that._dilation;
            _out_depth = that._out_depth;
            _group = that._group;
            _mode = that._mode;
            _in_depth = that._in_depth;
            _pad_value = that._pad_value;
        }
        return *this;
    }
    /**
     * @brief move assignment operator
     */
    BinaryConvolutionLayer& operator = (BinaryConvolutionLayer &&) = default;
    /**
     * @brief copy constructor
     */
    BinaryConvolutionLayer(const BinaryConvolutionLayer & that) : WeightableLayer(that) {
        operator = (that);
    }
    /**
     * @brief move constructor
     */
    BinaryConvolutionLayer(BinaryConvolutionLayer &&) = default;
};

#undef DEFINE_PROP

/**
 * @brief This class represents a fully connected layer
 */
class FullyConnectedLayer : public WeightableLayer {
public:
    /**
     * @brief A size of output
     */
    unsigned int _out_num = 0;

    /**
    * @brief Creates a new FullyConnectedLayer instance and initializes layer parameters with the given values.
    */
    using WeightableLayer::WeightableLayer;
};

/**
 * @brief This class represents concatenation layer
 * Takes as input several data elements and merges them to one using the supplied axis
 */
class ConcatLayer : public CNNLayer {
public:
    /**
     * @brief An axis on which concatenation operation is performed
     */
    unsigned int _axis = 1;

    /**
    * @brief Creates a new ConcatLayer instance and initializes layer parameters with the given values.
    * If batch is used, then batch needs to be specified as an input dimension also
    * In current implementation 1 means channels, 0 - batch
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a layer that evenly splits the input into the supplied outputs
 */
class SplitLayer : public CNNLayer {
public:
    /**
     * @brief An axis on which split operation is performed
     */
    unsigned int _axis = 1;

    /**
    * @brief Creates a new SplitLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a Linear Response Normalization (LRN) Layer
 */
class NormLayer : public CNNLayer {
public:
    /**
     * @brief Response size
     */
    unsigned int _size = 0;
    /**
     * @deprecated
     */
    unsigned int _k = 1;
    /**
     * @brief Alpha coefficient
     */
    float _alpha = 0;
    /**
     * @brief Beta coefficient
     */
    float _beta = 0;
    /**
     * @brief Flag to specify normalization across feature maps (true) or across channels
     */
    bool _isAcrossMaps = false;

    /**
     * @brief Creates a new NormLayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents standard softmax Layer
 */
class SoftMaxLayer : public CNNLayer {
public:
    /**
     * @brief Axis number for a softmax operation
     */
    int axis = 1;
    /**
     * @brief Creates a new SoftMaxLayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @class GRNLayer
 * @brief This class represents standard GRN Layer
 */
class GRNLayer : public CNNLayer {
public:
    /**
    * @brief A default constructor. Creates a new GRNLayer instance and initializes layer parameters with the given values.
    * @param prms Initial layer parameters
    */
    explicit GRNLayer(const LayerParams &prms) : CNNLayer(prms), bias(0.f) {}

    /**
     * @brief Bias for squares sum
     */
    float bias = 0.f;
};

/**
 * @class MVNLayer
 * @brief This class represents standard MVN Layer
 */
class MVNLayer : public CNNLayer {
public:
    /**
    * @brief A default constructor. Creates a new MVNLayer instance and initializes layer parameters with the given values.
    * @param prms Initial layer parameters
    */
    explicit MVNLayer(const LayerParams &prms) : CNNLayer(prms), across_channels(0), normalize(1) {}

    /**
     * @brief Indicate that mean value is calculated across channels
     */
    int across_channels;

    /**
    * @brief Indicate that the result needs to be normalized
    */
    int normalize = 1;
};

/**
 * @brief This class represents a Rectified Linear activation layer
 */
class ReLULayer : public CNNLayer {
public:
    /**
     * @brief Negative slope is used to takle negative inputs instead of setting them to 0
     */
    float negative_slope = 0.0f;

    /**
     * @brief Creates a new ReLULayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a Clamp activation layer
 * Clamps all tensor elements into the range [min_value, max_value]
 */
class ClampLayer : public CNNLayer {
public:
    /**
     * @brief A minimum value
     */
    float min_value = 0.0f;

    /**
     * @brief A maximum value
     */
    float max_value = 1.0f;
    /**
     * @brief Creates a new ClampLayer instance.
     */
    using CNNLayer::CNNLayer;
};


/**
 * @brief This class represents a ReLU6 activation layer
 * Clamps all tensor elements into the range [0, 6.0]
 */
class ReLU6Layer : public ClampLayer {
public:
    explicit ReLU6Layer(const LayerParams &prms) : ClampLayer(prms) {
        max_value = 6.0f;
    }

    using ClampLayer::ClampLayer;
};


/**
 * @brief This class represents an element wise operation layer
 */
class EltwiseLayer : public CNNLayer {
public:
    /**
     * @enum eOperation
     * @brief Defines possible operations that can be used
     */
    enum eOperation {
        Sum = 0, Prod, Max, Sub, Min, Div, Squared_diff, Floor_mod, Pow,
        Equal, Not_equal, Less, Less_equal, Greater, Greater_equal,
        Logical_AND, Logical_OR, Logical_XOR
    };

    /**
     * @brief A type of the operation to use
     */
    eOperation _operation = Sum;

    /**
     * @brief A vector of coefficients to scale the operands
     */
    std::vector<float> coeff;

    /**
    * @brief Creates a new EltwiseLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard crop layer
 */
class CropLayer : public CNNLayer {
public:
    /**
     * @brief A vector of dimensions for cropping
     */
    std::vector<int> axis;
    /**
     * @brief A vector of dimensions to be preserved
     */
    std::vector<int> dim;
    /**
     * @brief A vector of offsets for each dimension
     */
    std::vector<int> offset;

    /**
     * @brief Creates a new CropLayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard reshape layer
 */
class ReshapeLayer : public CNNLayer {
public:
    /**
     * @brief A vector of sizes of the shape
     */
    std::vector<int> shape;
    /**
     * @brief A number of axis to be taken for a reshape
     */
    int axis = 0;
    /**
     * @brief A number of first axises to be taken for a reshape
     */
    int num_axes = -1;

    /**
     * @brief Creates a new ReshapeLayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard Tile Layer
 */
class TileLayer : public CNNLayer {
public:
    /**
     * @brief An index of the axis to tile
     */
    int axis = -1;
    /**
     * @brief A number of copies to be made
     */
    int tiles = -1;

    /**
     * @brief Creates a new TileLayer instance.
     */
    using CNNLayer::CNNLayer;
};


/**
 * @brief This class represents a Layer which performs Scale and Shift
 */
class ScaleShiftLayer : public WeightableLayer {
public:
    /**
     * @brief A flag that indicates if the same value is used for all the features. If false, the value is used pixel wise
     */
    unsigned int _broadcast = 0;

    /**
     * @brief Creates a new ScaleShiftLayer instance.
     */
    using WeightableLayer::WeightableLayer;
};

/**
 * @brief This class represents TensorIterator layer
 */
class TensorIterator : public CNNLayer {
public:
    struct PortMap {
        // Data map rule
        int from;      /**< Index of exteral data from ins/outs fields of CNNLayer */
        int to;        /**< Index of internal data in iterator body */

        // Iteration rule
        int axis;      /**< Axis to iterate throught */
        int stride;    /**< Stride to iterate throught */
        int start;     /**< Start index of iteration range */
        int end;       /**< Last index of iteration range  */
        int part_size; /**< Part size which will be transfered to body subnetwork */
    };

    struct Body {
        std::vector<DataPtr> inputs;
        std::vector<DataPtr> outputs;
    };

    std::vector<PortMap> input_port_map;
    std::vector<PortMap> output_port_map;
    std::vector<PortMap> back_edges;

    Body body;

    using CNNLayer::CNNLayer;
};

/**
 * @brief Base class for recurrent cell layers
 */
class RNNCellBase : public WeightableLayer {
public:
    using WeightableLayer::WeightableLayer;

    /**
     * @brief Direct type of recurrent cell (including subtypes)
     * Description of particular cell semantics is in LSTMCell, GRUCell, RNNCell.
     */
    enum CellType {
        LSTM,      /**< Original LSTM cell */
        GRU,       /**< Original GRU cell */
        RNN,       /**< Original RNN cell */
        GRU_LBR,   /**< GRU cell modification. "Linear before reset" */
    };

    /** @copybrief CellType */
    CellType cellType = LSTM;

    /**
     * @brief Size of hidden state data
     *
     * In case of batch output state tensor will have shape [N, hidden_size]
     */
    int hidden_size = 0;

    /**
     * @brief Clip data into range [-clip, clip] on input of activations
     *
     * clip==0.0f means no clipping
     */
    float clip = 0.0f;
    /**
     * @brief Activations used inside recurrent cell
     *
     * Valid values: sigmoid, tanh, relu
     */
    std::vector<std::string> activations;

    /**
     * @brief Alpha parameters of activations
     *
     * Respective to activation list.
     */
    std::vector<float> activation_alpha;

    /**
     * @brief Beta parameters of activations
     *
     * Respective to activation list.
     */
    std::vector<float> activation_beta;
};

/**
 * @brief LSTM Cell layer
 *
 * G - number of gates (=4)
 * N - batch size
 * S - state size (=hidden_size)
 *
 * Inputs:
 *   [N,D] Xt - input data
 *   [N,S] Ht-1 - initial hidden state
 *   [N,S] Ct-1 - initial cell state
 *
 * Outputs:
 *   [N,S] Ht - out hidden state
 *   [N,S] Ct - out cell state
 *
 * Weights:
 *   - weights [G,S,D+S]
 *   - biases [G,S]
 * NB!  gates order is FICO {forget, input, candidate, output}
 *
 * activations is {_f, _g, _h}
 * default: {_f=sigm, _g=tanh, _h=tanh}
 *
 * Equations:
 *
 *   *  - matrix mult
 *  (.) - eltwise mult
 *  [,] - concatenation
 *
 * - ft = _f(Wf*[Ht-1, Xt] + Bf)
 * - it = _f(Wi*[Ht-1, Xt] + Bi)
 * - ct = _g(Wc*[Ht-1, Xt] + Bc)
 * - ot = _f(Wo*[Ht-1, Xt] + Bo)
 * - Ct = ft (.) Ct-1 + it (.) ct
 * - Ht = ot (.) _h(Ct)
 */
using LSTMCell = RNNCellBase;

/**
 * @brief GRU Cell layer
 *
 * G - number of gates (=3)
 * N - batch size
 * S - state size (=hidden_size)
 *
 * Inputs:
 *   [N,D] Xt - input data
 *   [N,S] Ht-1 - initial hidden state
 *
 * Outputs:
 *   [N,S] Ht - out hidden state
 *
 * Weights:
 *   - weights [G,S,D+S]
 *   - biases [G,S]
 * NB!  gates order is ZRH {update, reset, output}
 *
 * activations is {_f, _g}
 * default: {_f=sigm, _g=tanh}
 *
 * Equations:
 *
 *   *  - matrix mult
 *  (.) - eltwise mult
 *  [,] - concatenation
 *
 * - zt = _f(Wz*[Ht-1, Xt] + Bz)
 * - rt = _f(Wr*[Ht-1, Xt] + Br)
 * - ht = _g(Wh*[rt (.) Ht-1, Xt] + Bh)
 * - Ht = (1 - zt) (.) ht + zt (.) Ht-1
 */
using GRUCell  = RNNCellBase;

/**
 * @brief RNN Cell layer
 *
 * G - number of gates (=1)
 * N - batch size
 * S - state size (=hidden_size)
 *
 * Inputs:
 *   [N,D] Xt - input data
 *   [N,S] Ht-1 - initial hidden state
 *
 * Outputs:
 *   [N,S] Ht - out hidden state
 *
 * Weights:
 *   - weights [G,S,D+S]
 *   - biases [G,S]
 *
 * activations is {_f}
 * default: {_f=tanh}
 *
 * Equations:
 *
 *   *  - matrix mult
 *  [,] - concatenation
 *
 * - Ht = _f(Wi*[Ht-1, Xt] + Bi)
 */
using RNNCell  = RNNCellBase;

/**
 * @brief Sequence of recurrent cells
 *
 * N  - batch size
 * T  - sequence size
 * S  - state size (=hidden_size)
 * NS - num of state tensors (LSTM=2, GRU/RNN=1)
 * ND - num of direction (BDR=2, WFD/BWD=1)
 *
 * Inputs:
 *   [N,T,D]  Xt - input data
 *   [ND,N,S] Ht-1 - initial hidden state
 *   [ND,N,S] Ct-1 - initial cell state  // if NS==2
 *
 * Outputs:
 *   [ND,N,T,S] Xt - input data
 *   [ND,N,S] Ht-1 - initial hidden state
 *   [ND,N,S] Ct-1 - initial cell state  // if NS==2
 *
 * NB! if axis==0 batch and sequense dimensions are swapped (N <-> T) for input and output tensors
 *
 * Weights:
 *   - weights [ND,G,S,D+S]
 *   - biases [ND,G,S]
 * NB! if ND==2 weights are concatenated cell weights [forward_cell_weights, backward_cell_weights]
 *
 */
class RNNSequenceLayer : public RNNCellBase {
public:
    using RNNCellBase::RNNCellBase;

    /**
     * @brief An axis by which iteration is performed
     * axis=0 means first input/output data blob dimension is sequence
     * axis=1 means first input/output data blob dimension is batch
     */
    unsigned int axis = 1;

    /**
     * @brief Direction of iteration through sequence dimension
     */
    enum Direction {
        FWD,  /**< Forward mode. Iterate starts from index 0 with step 1.         */
        BWD,  /**< Backward mode. Iterate starts from last index with step -1.    */
        BDR   /**< Bidirectional mode. First is forward pass, second is backward. */
    };

    /** @copybrief Direction */
    Direction direction = FWD;
};

/**
 * @brief This class represents a Layer which performs Scale and Shift
 */
class PReLULayer : public WeightableLayer {
public:
    /**
     * @brief A flag that indicates if the same negative_slope value is used for all the features. If false, the value is used pixel wise
     */
    bool _channel_shared;

public:
    /**
     * @brief A default constructor. Creates a new PReLULayer instance and initializes layer parameters with the given values.
     * @param prms Initial layer parameters
     */
    explicit PReLULayer(const LayerParams &prms) : WeightableLayer(prms), _channel_shared(false) {}
};

/**
 * @brief This class represents a standard Power Layer
 * Formula is: output = (offset + scale * input) ^ power
 */
class PowerLayer : public CNNLayer {
public:
    /**
     * @brief An exponent value
     */
    float power = 1.f;
    /**
     * @brief A scale factor
     */
    float scale = 1.f;
    /**
     * @brief An offset value
     */
    float offset = 0.f;

    /**
     * @brief Creates a new PowerLayer instance.
     */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a Batch Normalization Layer
 */
class BatchNormalizationLayer : public WeightableLayer {
public:
    /**
     * @brief A small value to add to the variance estimate to avoid division by zero
     */
    float epsilon = 1e-3f;

    /**
     * @brief Creates a new BatchNormalizationLayer instance.
     */
    using WeightableLayer::WeightableLayer;
};

/**
 * @brief This class represents a general matrix multiplication operation layer
 * Formula is: dst := alpha*src1*src2 + beta*src3
 */
class GemmLayer : public CNNLayer {
public:
    /**
    * @brief A scale factor of src1 matrix
    */
    float alpha = 1.f;
    /**
    * @brief A scale factor of src3 matrix
    */
    float beta = 1.f;
    /**
    * @brief A flag that indicates if the src1 matrix is to be transposed
    */
    bool transpose_a = false;
    /**
    * @brief A flag that indicates if the src2 matrix is to be transposed
    */
    bool transpose_b = false;
    /**
    * @brief Creates a new GemmLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard Pad layer
 * Adds paddings to input tensor
 */
class PadLayer : public CNNLayer {
public:
    /**
     * @enum ePadMode
     * @brief Defines possible modes of pad operation
     */
    enum ePadMode {
        Constant = 0, Edge, Reflect, Symmetric
    };

    /**
    * @brief Size of padding in the beginning of each axis
    */
    PropertyVector<unsigned int> pads_begin;
    /**
    * @brief Size of padding in the end of each axis
    */
    PropertyVector<unsigned int> pads_end;
    /**
    * @brief Mode of pad operation
    */
    ePadMode pad_mode = Constant;
    /**
    * @brief A pad value which is used for filling in Constant mode
    */
    float pad_value = 0.0f;
    /**
    * @brief Creates a new PadLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard Gather layer
 * Gather slices from Dictionary according to Indexes
 */
class GatherLayer : public CNNLayer {
public:
    /**
    * @brief The axis in Dictionary to gather Indexes from
    */
    int axis = 0;
    /**
    * @brief Creates a new GatherLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a standard Strided Slice layer
 * Strided Slice picks from input tensor according parameters
 */
class StridedSliceLayer : public CNNLayer {
public:
    /**
    * @brief The begin_mask is a bitmask where bit i being 0 means
    * to ignore the begin value and instead use the default value
    */
    std::string begin_mask;
    /**
    * @brief Analogous to begin_mask
    */
    std::string end_mask;
    /**
    * @brief The ellipsis_mask is a bitmask where bit i being 1 means
    * the i-th is actually an ellipsis
    */
    std::string ellipsis_mask;
    /**
    * @brief The new_axis_mask_ is a bitmask where bit i being 1 means
    * the i-th position creates a new 1 dimension shape
    */
    std::string new_axis_mask;
    /**
    * @brief The shrink_axis_mask is a bitmask where bit i being 1 means
    * the i-th position shrinks the dimensionality
    */
    std::string shrink_axis_mask;

    /**
    * @brief Creates a new StridedSliceLayer instance.
    */
    using CNNLayer::CNNLayer;
};

/**
* @brief This class represents a standard Shuffle Channels layer
* Shuffle Channels picks from input tensor according parameters
*/
class ShuffleChannelsLayer : public CNNLayer {
public:
    /**
    * @brief The axis in tensor to shuffle channels
    */
    int axis = 1;

    /**
    * @brief The group of output shuffled channels
    */
    unsigned int group = 1;

    /**
    * @brief Creates a new ShuffleChannelsLayer instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Depth To Space layer
* Depth To Space picks from input tensor according parameters
*/
class DepthToSpaceLayer : public CNNLayer {
public:
    /**
    * @brief The group of output shuffled channels
    */
    unsigned int block_size = 1;

    /**
    * @brief Creates a new DepthToSpaceLayer instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Space To Depth layer
* Depth To Space picks from input tensor according parameters
*/
class SpaceToDepthLayer : public CNNLayer {
public:
    /**
    * @brief The group of output Space To Depth
    */
    unsigned int block_size = 1;

    /**
    * @brief Creates a new SpaceToDepthLayer instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Reverse Sequence layer
* Reverse Sequence modifies input tensor according parameters
*/
class ReverseSequenceLayer : public CNNLayer {
public:
    /**
    * @brief The seq_axis dimension in tensor which is partially reversed
    */
    int seq_axis = 1;

    /**
    * @brief The batch_axis dimension in tensor along which reversal is performed
    */
    int batch_axis = 0;

    /**
    * @brief Creates a new ReverseSequence instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Squeeze layer
* Squeeze modifies input tensor dimensions according parameters
*/
class SqueezeLayer : public CNNLayer {
public:
    /**
    * @brief Creates a new Squeeze instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Unsqueeze layer
* Unsqueeze modifies input tensor dimensions according parameters
*/
class UnsqueezeLayer : public CNNLayer {
public:
    /**
    * @brief Creates a new Unsqueeze instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard RangeLayer layer
* RangeLayer modifies input tensor dimensions according parameters
*/
class RangeLayer : public CNNLayer {
public:
    /**
    * @brief Creates a new RangeLayer instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Fill layer
* RFill modifies input tensor according parameters
*/
class FillLayer : public CNNLayer {
public:
    /**
    * @brief Creates a new Fill instance.
    */
    using CNNLayer::CNNLayer;
};


/**
* @brief This class represents a standard Expand layer
* Expand modifies input tensor dimensions according parameters
*/
class ExpandLayer : public CNNLayer {
public:
    /**
    * @brief Creates a new Expand instance.
    */
    using CNNLayer::CNNLayer;
};

/**
 * @brief This class represents a quantization operation layer
 * Element-wise linear quantization of floating point input values into a descrete set of floating point values
 */
class QuantizeLayer : public CNNLayer {
public:
    /**
    * @brief The number of quantization levels
    */
    int levels = 1;

    /**
    * @brief Creates a new QuantizeLayer instance.
    */
    using CNNLayer::CNNLayer;
};

}  // namespace InferenceEngine
