#ifndef OPENXLSX_XLROWDATA_HPP
#define OPENXLSX_XLROWDATA_HPP

#ifdef _MSC_VER    // conditionally enable MSVC specific pragmas to avoid other compilers warning about unknown pragmas
#    pragma warning(push)
#    pragma warning(disable : 4251)
#    pragma warning(disable : 4275)
#endif    // _MSC_VER

// ===== External Includes ===== //
#include <deque>
#include <iterator>
#include <list>
#include <memory>
#include <vector>

// ===== OpenXLSX Includes ===== //
#include "OpenXLSX-Exports.hpp"
#include "XLCell.hpp"
#include "XLConstants.hpp"
#include "XLException.hpp"
#include "XLIterator.hpp"
#include "XLXmlParser.hpp"

namespace OpenXLSX
{
    class XLRow;
    class XLRowDataRange;

    /**
     * @brief This class encapsulates a (non-const) iterator, for iterating over the cells in a row.
     * @todo Consider implementing a const iterator also
     */
    class OPENXLSX_EXPORT XLRowDataIterator
    {
        friend class XLRowDataRange;

    public:
        using iterator_category = std::forward_iterator_tag;
        using value_type        = XLCell;
        using difference_type   = int64_t;
        using pointer           = XLCell*;
        using reference         = XLCell&;

        /**
         * @brief Destructor.
         */
        ~XLRowDataIterator();

        /**
         * @brief Copy constructor.
         * @param other Object to be copied.
         */
        XLRowDataIterator(const XLRowDataIterator& other);

        /**
         * @brief Move constructor.
         * @param other Object to be moved.
         */
        XLRowDataIterator(XLRowDataIterator&& other) noexcept;

        /**
         * @brief Copy assignment operator.
         * @param other Object to be copied.
         * @return Reference to the copied-to object.
         */
        XLRowDataIterator& operator=(const XLRowDataIterator& other);

        /**
         * @brief Move assignment operator.
         * @param other Object to be moved.
         * @return Reference to the moved-to object.
         */
        XLRowDataIterator& operator=(XLRowDataIterator&& other) noexcept;

        /**
         * @brief Pre-increment of the iterator.
         * @return Reference to the iterator object.
         */
        XLRowDataIterator& operator++();

        /**
         * @brief Post-increment of the iterator.
         * @return Reference to the iterator object.
         */
        XLRowDataIterator operator++(int);    // NOLINT

        /**
         * @brief Dereferencing operator.
         * @return Reference to the object pointed to by the iterator.
         */
        reference operator*();

        /**
         * @brief Arrow operator.
         * @return Pointer to the object pointed to by the iterator.
         */
        pointer operator->();

        /**
         * @brief Equality operator.
         * @param rhs XLRowDataIterator to compare to.
         * @return true if equal, otherwise false.
         */
        bool operator==(const XLRowDataIterator& rhs) const;

        /**
         * @brief Non-equality operator.
         * @param rhs XLRowDataIterator to compare to.
         * @return false if equal, otherwise true.
         */
        bool operator!=(const XLRowDataIterator& rhs) const;

    private:
        /**
         * @brief Constructor.
         * @param rowDataRange The range to iterate over.
         * @param loc The location of the iterator (begin or end).
         */
        XLRowDataIterator(const XLRowDataRange& rowDataRange, XLIteratorLocation loc);

        std::unique_ptr<XLRowDataRange> m_dataRange;   /**< A pointer to the range to iterate over. */
        std::unique_ptr<XMLNode>        m_cellNode;    /**< The XML node representing the cell currently pointed at. */
        XLCell                          m_currentCell; /**< The XLCell currently pointed at. */
    };

    /**
     * @brief This class encapsulates the concept of a contiguous range of cells in a row.
     */
    class OPENXLSX_EXPORT XLRowDataRange
    {
        friend class XLRowDataIterator;
        friend class XLRowDataProxy;
        friend class XLRow;

    public:
        /**
         * @brief Copy constructor.
         * @param other Object to be copied.
         */
        XLRowDataRange(const XLRowDataRange& other);

        /**
         * @brief Move constructor.
         * @param other Object to be moved.
         */
        XLRowDataRange(XLRowDataRange&& other) noexcept;

        /**
         * @brief Destructor.
         */
        ~XLRowDataRange();

        /**
         * @brief Copy assignment operator.
         * @param other Object to be copied.
         * @return A reference to the copied-to object.
         */
        XLRowDataRange& operator=(const XLRowDataRange& other);

        /**
         * @brief Move assignment operator.
         * @param other Object to be moved.
         * @return A reference to the moved-to object.
         */
        XLRowDataRange& operator=(XLRowDataRange&& other) noexcept;

        /**
         * @brief Get the size (length) of the range.
         * @return The size of the range.
         */
        uint16_t size() const;

        /**
         * @brief Get an iterator to the first element.
         * @return An XLRowDataIterator pointing to the first element.
         */
        XLRowDataIterator begin();

        /**
         * @brief Get an iterator to (one-past) the last element.
         * @return An XLRowDataIterator pointing to (one past) the last element.
         */
        XLRowDataIterator end();

        /**
         * @brief Templated assignment operator - assign value to all existing cells in the row
         * @note CAUTION: non-existing cells will not be assigned
         * @tparam T The type of the value argument.
         * @param value The value.
         * @return A reference to the assigned-to object.
         */
        template<typename T,
                 typename = std::enable_if_t<
                     std::is_integral_v<T> or std::is_floating_point_v<T> or std::is_same_v<std::decay_t<T>, std::string> ||
                     std::is_same_v<std::decay_t<T>, std::string_view> or std::is_same_v<std::decay_t<T>, const char*> ||
                     std::is_same_v<std::decay_t<T>, char*> or std::is_same_v<T, XLDateTime>>>
        XLRowDataRange& operator=(T value)
        {
            // forward implementation to templated XLCellValue& XLCellValue::operator=(T value)
            for (auto it = begin(); it != end(); ++it) it->value() = value;
            return *this;
        }

    private:
        /**
         * @brief Constructor.
         * @param rowNode XMLNode representing the row of the range.
         * @param firstColumn The index of the first column.
         * @param lastColumn The index of the last column.
         * @param sharedStrings A pointer to the shared strings repository.
         */
        explicit XLRowDataRange(const XMLNode& rowNode, uint16_t firstColumn, uint16_t lastColumn, const XLSharedStrings& sharedStrings);

        /**
         * @brief Constructor for an empty range.
         */
        explicit XLRowDataRange();

        std::unique_ptr<XMLNode> m_rowNode;       /**< */
        uint16_t                 m_firstCol{1};   /**< The cell reference of the first cell in the range */
        uint16_t                 m_lastCol{1};    /**< The cell reference of the last cell in the range */
        XLSharedStringsRef       m_sharedStrings; /**< */
    };

    /**
     * @brief The XLRowDataProxy is used as a proxy object when getting or setting row data. The class facilitates easy conversion
     * to/from containers.
     */
    class OPENXLSX_EXPORT XLRowDataProxy
    {
        friend class XLRow;

    public:
        /**
         * @brief Destructor
         */
        ~XLRowDataProxy();

        /**
         * @brief Copy assignment operator.
         * @param other Object to be copied.
         * @return A reference to the copied-to object.
         */
        XLRowDataProxy& operator=(const XLRowDataProxy& other);

        /**
         * @brief Assignment operator taking a std::vector of XLCellValues as an argument.
         * @param values A std::vector of XLCellValues representing the values to be assigned.
         * @return A reference to the copied-to object.
         */
        XLRowDataProxy& operator=(const std::vector<XLCellValue>& values);

        /**
         * @brief Assignment operator taking a std::vector of bool values as an argument.
         * @param values A std::vector of bool values representing the values to be assigned.
         * @return A reference to the copied-to object.
         */
        XLRowDataProxy& operator=(const std::vector<bool>& values);

        /**
         * @brief Templated assignment operator taking any container supporting bidirectional iterators.
         * @tparam T The container and value type (will be auto deducted by the compiler).
         * @param values The container of the values to be assigned.
         * @return A reference to the copied-to object.
         * @throws XLOverflowError if size of container exceeds maximum number of columns.
         */
        template<typename T,
                 typename = std::enable_if_t<!std::is_same_v<T, XLRowDataProxy> &&
                                                 std::is_base_of_v<std::bidirectional_iterator_tag,
                                                                   typename std::iterator_traits<typename T::iterator>::iterator_category>,
                                             T>>
        XLRowDataProxy& operator=(const T& values)    // 2024-04-30: whitespace support
        {
            if (values.size() > MAX_COLS) throw XLOverflowError("Container size exceeds maximum number of columns.");
            if (values.size() == 0) return *this;

            // ===== If the container value_type is XLCellValue, the values can be copied directly.
            if constexpr (std::is_same_v<typename T::value_type, XLCellValue>) {
                // ===== First, delete the values in the first N columns.
                deleteCellValues(static_cast<uint16_t>(values.size()));    // 2024-04-30: whitespace support

                // ===== Then, prepend new cell nodes to current row node
                auto colNo = values.size();
                for (auto value = values.rbegin(); value != values.rend(); ++value) {    // NOLINT
                    prependCellValue(*value, static_cast<uint16_t>(colNo));    // 2024-04-30: whitespace support: this is safe because only prependCellValue (with
                                                        // whitespace support) touches the row data
                    --colNo;
                }
            }

            // ===== If the container value_type is a POD type, use the overloaded operator= on each cell.
            else {
                auto range = XLRowDataRange(*m_rowNode, 1, static_cast<uint16_t>(values.size()), getSharedStrings());
                auto dst   = range.begin();    // 2024-04-30: whitespace support: safe because XLRowDataRange::begin invokes whitespace-safe
                                               // getCellNode for column 1
                auto src = values.begin();

                while (true) {
                    dst->value() = *src;
                    ++src;
                    if (src == values.end()) break;
                    ++dst;    // 2024-04-30: whitespace support: XLRowDataIterator::operator++ is whitespace-safe
                }
            }

            return *this;
        }

        // // BEGIN working template header
        //         template<
        //             typename T,
        //             typename std::enable_if<
        //                 !std::is_same_v< T, XLRowDataProxy >
        //                  and std::is_base_of_v< XMLNode, T >,
        //                 T
        //             >::type* = nullptr
        //         >
        // // END working template header
        //         XLRowDataProxy& operator=(const T& values)
        //         {
        // using namespace std::literals::string_literals;
        // throw XLInternalError( "templated XLRowDataProxy& operator=(const T& values) instantiated for an XMLNode ("s+ typeid(T).name() +
        // "), this function must be implemented then"s );
        //
        //         }

        /**
         * @brief Implicit conversion to std::vector of XLCellValues.
         * @return A std::vector of XLCellValues.
         */
        operator std::vector<XLCellValue>() const;    // NOLINT

        /**
         * @brief Implicit conversion to std::deque of XLCellValues.
         * @return A std::deque of XLCellValues.
         */
        operator std::deque<XLCellValue>() const;    // NOLINT

        /**
         * @brief Implicit conversion to std::list of XLCellValues.
         * @return A std::list of XLCellValues.
         */
        operator std::list<XLCellValue>() const;    // NOLINT

        /**
         * @brief Explicit conversion operator.
         * @details This function calls the convertContainer template function to convert the row data to the container
         * stipulated by the client. The reason that this function is marked explicit is that the implicit conversion operators
         * above will be ambiguous.
         * @tparam Container The container (and value) type to convert the row data to.
         * @return The required container with the row data.
         */
        template<typename Container,
                 typename =
                     std::enable_if_t<!std::is_same_v<Container, XLRowDataProxy> &&
                                          std::is_base_of_v<std::bidirectional_iterator_tag,
                                                            typename std::iterator_traits<typename Container::iterator>::iterator_category>,
                                      Container>>
        explicit operator Container() const
        { return convertContainer<Container>(); }

        /**
         * @brief Clears all values for the current row.
         */
        void clear();

    private:
        //---------- Private Member Functions ---------- //

        /**
         * @brief Constructor.
         * @param row Pointer to the parent XLRow object.
         * @param rowNode Pointer to the underlying XML node representing the row.
         */
        XLRowDataProxy(XLRow* row, XMLNode* rowNode);

        /**
         * @brief Copy constructor.
         * @param other Object to be copied.
         * @note The copy constructor is private in order to prevent use of the auto keyword in client code.
         */
        XLRowDataProxy(const XLRowDataProxy& other);

        /**
         * @brief Move constructor.
         * @param other Object to be moved.
         * @note Made private, as move construction should only be allowed when the parent object is moved. Disallowed for client code.
         */
        XLRowDataProxy(XLRowDataProxy&& other) noexcept;

        /**
         * @brief Move assignment operator.
         * @param other Object to be moved.
         * @return Reference to the moved-to object.
         * @note Made private, as move assignment is disallowed for client code.
         */
        XLRowDataProxy& operator=(XLRowDataProxy&& other) noexcept;

        /**
         * @brief Get the cell values for the row.
         * @return A std::vector of XLCellValues.
         */
        std::vector<XLCellValue> getValues() const;

        /**
         * @brief Helper function for getting a reference to the shared strings repository.
         * @return A reference to the XLSharedStrings object.
         * @note needed for templated XLRowDataProxy& operator=
         */
        const XLSharedStrings& getSharedStrings() const;

        /**
         * @brief Convenience function for erasing the first 'count' numbers of values in the row.
         * @param count The number of values to erase.
         */
        void deleteCellValues(uint16_t count);

        /**
         * @brief Convenience function for prepending a row value with a given column number.
         * @param value The XLCellValue object.
         * @param col The column of the value.
         */
        void prependCellValue(const XLCellValue& value, uint16_t col);

        /**
         * @brief Convenience function for converting the row data to a user-supplied container.
         * @details This function can convert row data to any user-supplied container that adheres to the design
         * of STL containers and supports bidirectional iterators. This could be std::vector, std::deque, or
         * std::list, but any container with the same interface should work.
         * @tparam Container The container (and value) type to be returned.
         * @return The row data in the required format.
         * @throws bad_variant_access if Container::value type is not XLCellValue and does not match the type contained.
         */
        template<typename Container,
                 typename =
                     std::enable_if_t<!std::is_same_v<Container, XLRowDataProxy> &&
                                          std::is_base_of_v<std::bidirectional_iterator_tag,
                                                            typename std::iterator_traits<typename Container::iterator>::iterator_category>,
                                      Container>>
        Container convertContainer() const    // 2024-04-30: whitespace support
        {
            Container c;
            auto      it = std::inserter(c, c.end());
            for (const auto& v : getValues()) {
                // ===== If the value_type of the container is XLCellValue, the value can be assigned directly.
                if constexpr (std::is_same_v<typename Container::value_type, XLCellValue>) *it++ = v;

                // ===== If the value_type is something else, the underlying value has to be extracted from the XLCellValue object.
                // ===== Note that if the type contained in the XLCellValue object does not match the value_type, a bad_variant_access
                // ===== exception will be thrown.
                else
                    *it++ = v.get<typename Container::value_type>();
            }
            return c;
        }

        //---------- Private Member Variables ---------- //

        XLRow*   m_row{nullptr};     /**< Pointer to the parent XLRow object. */
        XMLNode* m_rowNode{nullptr}; /**< Pointer the the XML node representing the row. */
    };

}    // namespace OpenXLSX

#ifdef _MSC_VER    // conditionally enable MSVC specific pragmas to avoid other compilers warning about unknown pragmas
#    pragma warning(pop)
#endif    // _MSC_VER

#endif    // OPENXLSX_XLROWDATA_HPP
