// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
 *                                                                          *
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>               *
 *   Copyright (c) 2022 Zheng, Lei <realthunder.dev@gmail.com>              *
 *   Copyright (c) 2023 FreeCAD Project Association                         *
 *                                                                          *
 *   This file is part of FreeCAD.                                          *
 *                                                                          *
 *   FreeCAD is free software: you can redistribute it and/or modify it     *
 *   under the terms of the GNU Lesser General Public License as            *
 *   published by the Free Software Foundation, either version 2.1 of the   *
 *   License, or (at your option) any later version.                        *
 *                                                                          *
 *   FreeCAD is distributed in the hope that it will be useful, but         *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of             *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU       *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU Lesser General Public       *
 *   License along with FreeCAD. If not, see                                *
 *   <https://www.gnu.org/licenses/>.                                       *
 *                                                                          *
 ***************************************************************************/


#ifndef APP_COMPLEX_GEO_DATA_H
#define APP_COMPLEX_GEO_DATA_H

#include <algorithm>
#include <optional>

#include <Base/Handle.h>
#include <Base/Matrix.h>
#include <Base/Persistence.h>
#include "MappedName.h"
#include "MappedElement.h"
#include "ElementMap.h"
#include "StringHasher.h"

#ifdef __GNUC__
#include <cstdint>
#endif


namespace Base
{
class Placement;
class Rotation;
template<class _Precision>
class BoundBox3;  // NOLINT
using BoundBox3d = BoundBox3<double>;
}  // namespace Base

namespace Data
{

// struct MappedChildElements;
/// Option for App::GeoFeature::searchElementCache()
enum class SearchOption
{
    /// Whether to compare shape geometry
    CheckGeometry = 1,
    SingleResult = 2,
};
typedef Base::Flags<SearchOption> SearchOptions;

/** Segments
 *  Sub-element type of the ComplexGeoData type
 *  It is used to split an object in further sub-parts.
 */
class AppExport Segment: public Base::BaseClass
{
    TYPESYSTEM_HEADER_WITH_OVERRIDE();  // NOLINT

public:
    ~Segment() override = default;
    virtual std::string getName() const = 0;
};

enum ElementMapResetPolicy
{
    AllowNoMap,
    ForceEmptyMap
};

/** ComplexGeoData Object
 */
class AppExport ComplexGeoData: public Base::Persistence, public Base::Handled
{
    TYPESYSTEM_HEADER_WITH_OVERRIDE();  // NOLINT

public:
    struct Line
    {
        uint32_t I1;
        uint32_t I2;
    };
    struct Facet
    {
        uint32_t I1;
        uint32_t I2;
        uint32_t I3;
    };
    struct Domain
    {
        std::vector<Base::Vector3d> points;
        std::vector<Facet> facets;
    };

    /// Constructor
    ComplexGeoData();
    /// Destructor
    ~ComplexGeoData() override = default;

    /** @name Sub-element management */
    //@{
    /** Sub type list
     *  List of different sub-element types
     *  its NOT a list of the sub-elements itself
     */
    virtual std::vector<const char*> getElementTypes() const = 0;
    virtual unsigned long countSubElements(const char* Type) const = 0;
    /// Returns a generic element type and index. The determined element type isn't
    /// necessarily supported by this geometry.
    static std::pair<std::string, unsigned long> getTypeAndIndex(const char* Name);
    /// get the sub-element by type and number
    virtual Segment* getSubElement(const char* Type, unsigned long) const = 0;
    /// get sub-element by combined name
    virtual Segment* getSubElementByName(const char* Name) const;
    /** Get lines from segment */
    virtual void getLinesFromSubElement(const Segment*,
                                        std::vector<Base::Vector3d>& Points,
                                        std::vector<Line>& lines) const;
    /** Get faces from segment */
    virtual void getFacesFromSubElement(const Segment*,
                                        std::vector<Base::Vector3d>& Points,
                                        std::vector<Base::Vector3d>& PointNormals,
                                        std::vector<Facet>& faces) const;
    //@}

    /** @name Placement control */
    //@{
    /** Applies an additional transformation to the current transformation. */
    void applyTransform(const Base::Matrix4D& rclTrf);
    /** Applies an additional translation to the current transformation. */
    void applyTranslation(const Base::Vector3d&);
    /** Applies an additional rotation to the current transformation. */
    void applyRotation(const Base::Rotation&);
    /** Override the current transformation with a placement
     * using the setTransform() method.
     */
    void setPlacement(const Base::Placement& rclPlacement);
    /** Return the current transformation as placement using
     * getTransform().
     */
    Base::Placement getPlacement() const;
    /** Override the current transformation with the new one.
     * This method has to be handled by the child classes.
     * the actual placement and matrix is not part of this class.
     */
    virtual void setTransform(const Base::Matrix4D& rclTrf) = 0;
    /** Return the current matrix
     * This method has to be handled by the child classes.
     * the actual placement and matrix is not part of this class.
     */
    virtual Base::Matrix4D getTransform() const = 0;
    //@}

    /** @name Modification */
    //@{
    /// Applies a transformation on the real geometric data type
    virtual void transformGeometry(const Base::Matrix4D& rclMat) = 0;
    //@}

    /** @name Getting basic geometric entities */
    //@{
    /// Get the standard accuracy to be used with getPoints, getLines or getFaces
    virtual double getAccuracy() const;
    /// Get the bound box
    virtual Base::BoundBox3d getBoundBox() const = 0;
    /** Get point from line object intersection  */
    virtual Base::Vector3d getPointFromLineIntersection(const Base::Vector3f& base,
                                                        const Base::Vector3f& dir) const;
    /** Get points from object with given accuracy */
    virtual void getPoints(std::vector<Base::Vector3d>& Points,
                           std::vector<Base::Vector3d>& Normals,
                           double Accuracy,
                           uint16_t flags = 0) const;
    /** Get lines from object with given accuracy */
    virtual void getLines(std::vector<Base::Vector3d>& Points,
                          std::vector<Line>& lines,
                          double Accuracy,
                          uint16_t flags = 0) const;
    /** Get faces from object with given accuracy */
    virtual void getFaces(std::vector<Base::Vector3d>& Points,
                          std::vector<Facet>& faces,
                          double Accuracy,
                          uint16_t flags = 0) const;
    /** Get the center of gravity
     * If this method is implemented then true is returned and the center of gravity.
     * The default implementation only returns false.
     */
    virtual bool getCenterOfGravity(Base::Vector3d& center) const;
    virtual std::optional<Base::Vector3d> centerOfGravity() const;
    //@}

    static const std::string& elementMapPrefix();

    /** @name Element name mapping */
    //@{

    /** Get element indexed name
     *
     * @param name: the input name
     * @param sid: optional output of and App::StringID involved forming this mapped name
     *
     * @return Returns an indexed name.
     */
    IndexedName getIndexedName(const MappedName& name, ElementIDRefs* sid = nullptr) const;

    /** Get element mapped name
     *
     * @param name: the input name
     * @param allowUnmapped: If the queried element is not mapped, then return
     *                       an empty name if \c allowUnmapped is false, or
     *                       else, return the indexed name.
     * @param sid: optional output of and App::StringID involved forming this mapped name
     * @return Returns the mapped name.
     */
    MappedName getMappedName(const IndexedName& element,
                             bool allowUnmapped = false,
                             ElementIDRefs* sid = nullptr) const;

    /** Return a pair of indexed name and mapped name
     *
     * @param name: the input name.
     * @param sid: optional output of any App::StringID involved in forming
     *             this mapped name
     * @param copy: if true, copy the name string, or else use it as constant
     *              string, and caller must make sure the memory is not freed.
     *
     * @return Returns the MappedElement which contains both the indexed and
     * mapped name.
     *
     * This function guesses whether the input name is an indexed name or
     * mapped, and perform a lookup and return the names found. If the input
     * name contains only alphabets and underscore followed by optional digits,
     * it will be treated as indexed name. Or else, it will be treated as
     * mapped name.
     */
    MappedElement
    getElementName(const char* name, ElementIDRefs* sid = nullptr, bool copy = false) const;

    /** Add a sub-element name mapping.
     *
     * @param element: the original \c Type + \c Index element name
     * @param name: the mapped sub-element name. May or may not start with
     * elementMapPrefix().
     * @param sid: in case you use a hasher to hash the element name, pass in
     * the string id reference using this parameter. You can have more than one
     * string id associated with the same name.
     * @param overwrite: if true, it will overwrite existing names
     *
     * @return Returns the stored mapped element name.
     *
     * An element can have multiple mapped names. However, a name can only be
     * mapped to one element
     *
     * Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access,
     *   now you must pass in `long masterTag` explicitly.
     */
    MappedName setElementName(const IndexedName& element,
                              const MappedName& name,
                              long masterTag,
                              const ElementIDRefs* sid = nullptr,
                              bool overwrite = false)
    {
        return _elementMap->setElementName(element, name, masterTag, sid, overwrite);
    }

    bool hasElementMap() const
    {
        return _elementMap != nullptr;
    }

    /** Get mapped element names
     *
     * @param element: original element name with \c Type + \c Index
     * @param needUnmapped: if true, return the original element name if no
     * mapping is found
     *
     * @return a list of mapped names of the give element along with their
     * associated string ID references
     */
    std::vector<std::pair<MappedName, ElementIDRefs>>
    getElementMappedNames(const IndexedName& element, bool needUnmapped = false) const;

    /// Hash the child element map postfixes to shorten element name from hierarchical maps
    void hashChildMaps();

    /// Check if there is child element map
    bool hasChildElementMap() const;

    /// Append the Tag (if and only if it is non zero) into the element map
    virtual void
    reTagElementMap(long tag, App::StringHasherRef hasher, const char* postfix = nullptr)
    {
        (void)tag;
        (void)hasher;
        (void)postfix;
    }

    // NOTE: getElementHistory is now in ElementMap
    long getElementHistory(const MappedName& name,
                           MappedName* original = nullptr,
                           std::vector<MappedName>* history = nullptr) const
    {
        if (_elementMap != nullptr) {
            return _elementMap->getElementHistory(name, Tag, original, history);
        }
        return 0;
    };

    void setMappedChildElements(const std::vector<Data::ElementMap::MappedChildElements>& children);
    std::vector<Data::ElementMap::MappedChildElements> getMappedChildElements() const;

    char elementType(const Data::MappedName&) const;
    char elementType(const Data::IndexedName&) const;
    char elementType(const char* name) const;

    /** Reset/swap the element map
     *
     * @param elementMap: optional new element map
     *
     * @return Returns the existing element map.
     */
    virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap = ElementMapPtr());

    /// Get the entire element map
    std::vector<MappedElement> getElementMap() const;

    /// Set the entire element map
    void setElementMap(const std::vector<MappedElement>& elements);

    /// Get the current element map size
    size_t getElementMapSize(bool flush = true) const;

    /// Return the higher level element names of the given element
    virtual std::vector<IndexedName> getHigherElements(const char* name, bool silent = false) const;

    /// Return the current element map version
    virtual std::string getElementMapVersion() const;

    /// Return true to signal element map version change
    virtual bool checkElementMapVersion(const char* ver) const;

    /// Check if the given sub-name only contains an element name
    static bool isElementName(const char* subName)
    {
        return (subName != nullptr) && (*subName != 0) && findElementName(subName) == subName;
    }

    /** Iterate through the history of the give element name with a given callback
     *
     * @param name: the input element name
     * @param cb: trace callback with call signature.
     * @sa TraceCallback
     */
    void traceElement(const MappedName& name, TraceCallback cb) const
    {
        _elementMap->traceElement(name, Tag, std::move(cb));
    }

    /** Flush an internal buffering for element mapping */
    virtual void flushElementMap() const;
    //@}

    /** @name Save/restore */
    //@{
    void Save(Base::Writer& writer) const override;
    void Restore(Base::XMLReader& reader) override;
    void SaveDocFile(Base::Writer& writer) const override;
    void RestoreDocFile(Base::Reader& reader) override;
    unsigned int getMemSize() const override;
    void setPersistenceFileName(const char* name) const;
    virtual void beforeSave() const;
    bool isRestoreFailed() const
    {
        return _restoreFailed;
    }
    void resetRestoreFailure() const
    {
        _restoreFailed = true;
    }
    //@}

    /**
     * Debugging method to dump an entire element map in human readable form to a stream
     * @param stream
     */
    void dumpElementMap(std::ostream& stream) const;
    /**
     * Debugging method to dump an entire element map in human readable form into a string
     * @return The string
     */
    const std::string dumpElementMap() const;

protected:
    /// from local to outside
    inline Base::Vector3d transformPointToOutside(const Base::Vector3f& vec) const
    {
        // clang-format off
        return getTransform() * Base::Vector3d(static_cast<double>(vec.x),
                                               static_cast<double>(vec.y),
                                               static_cast<double>(vec.z));
        // clang-format on
    }
    /// from local to outside
    template<typename Vec>
    inline std::vector<Base::Vector3d> transformPointsToOutside(const std::vector<Vec>& input) const
    {
        // clang-format off
        std::vector<Base::Vector3d> output;
        output.reserve(input.size());
        Base::Matrix4D mat(getTransform());
        std::transform(input.cbegin(), input.cend(), std::back_inserter(output),
                       [&mat](const Vec& vec) {
                           return mat * Base::Vector3d(static_cast<double>(vec.x),
                                                       static_cast<double>(vec.y),
                                                       static_cast<double>(vec.z));
                       });

        return output;
        // clang-format on
    }
    inline Base::Vector3d transformVectorToOutside(const Base::Vector3f& vec) const
    {
        // clang-format off
        Base::Matrix4D mat(getTransform());
        mat.setCol(3, Base::Vector3d());
        return mat * Base::Vector3d(static_cast<double>(vec.x),
                                   static_cast<double>(vec.y),
                                   static_cast<double>(vec.z));
        // clang-format on
    }
    template<typename Vec>
    std::vector<Base::Vector3d> transformVectorsToOutside(const std::vector<Vec>& input) const
    {
        // clang-format off
        std::vector<Base::Vector3d> output;
        output.reserve(input.size());
        Base::Matrix4D mat(getTransform());
        mat.setCol(3, Base::Vector3d());
        std::transform(input.cbegin(), input.cend(), std::back_inserter(output),
                       [&mat](const Vec& vec) {
                           return mat * Base::Vector3d(static_cast<double>(vec.x),
                                                       static_cast<double>(vec.y),
                                                       static_cast<double>(vec.z));
                       });

        return output;
        // clang-format on
    }
    /// from local to inside
    inline Base::Vector3f transformPointToInside(const Base::Vector3d& vec) const
    {
        Base::Matrix4D tmpM(getTransform());
        tmpM.inverse();
        Base::Vector3d tmp = tmpM * vec;
        return Base::Vector3f(static_cast<float>(tmp.x),
                              static_cast<float>(tmp.y),
                              static_cast<float>(tmp.z));
    }

public:
    mutable long Tag {0};

    /// String hasher for element name shortening
    mutable App::StringHasherRef Hasher;

protected:
    void restoreStream(std::istream& stream, std::size_t count);
    void readElements(Base::XMLReader& reader, size_t count);

    /// from local to outside
    inline Base::Vector3d transformToOutside(const Base::Vector3f& vec) const
    {
        // clang-format off
        return getTransform() * Base::Vector3d(static_cast<double>(vec.x),
                                               static_cast<double>(vec.y),
                                               static_cast<double>(vec.z));
        // clang-format on
    }
    /// from local to inside
    inline Base::Vector3f transformToInside(const Base::Vector3d& vec) const
    {
        Base::Matrix4D tmpM(getTransform());
        tmpM.inverse();
        Base::Vector3d tmp = tmpM * vec;
        return Base::Vector3f(static_cast<float>(tmp.x),
                              static_cast<float>(tmp.y),
                              static_cast<float>(tmp.z));
    }

protected:
    ElementMapPtr elementMap(bool flush = true) const;
    ElementMapPtr ensureElementMap(bool flush = true);

private:
    ElementMapPtr _elementMap;

protected:
    mutable std::string _persistenceName;
    mutable bool _restoreFailed = false;
};

}  // namespace Data

ENABLE_BITMASK_OPERATORS(Data::SearchOption)
#endif
