/*
 * Copyright (c) 2020-2025 Samuel Ugochukwu <sammycageagle@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*/

#ifndef LUNASVG_H
#define LUNASVG_H

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

#if defined(LUNASVG_BUILD_STATIC)
#define LUNASVG_EXPORT
#define LUNASVG_IMPORT
#elif (defined(_WIN32) || defined(__CYGWIN__))
#define LUNASVG_EXPORT __declspec(dllexport)
#define LUNASVG_IMPORT __declspec(dllimport)
#elif defined(__GNUC__) && (__GNUC__ >= 4)
#define LUNASVG_EXPORT __attribute__((__visibility__("default")))
#define LUNASVG_IMPORT
#else
#define LUNASVG_EXPORT
#define LUNASVG_IMPORT
#endif

#ifdef LUNASVG_BUILD
#define LUNASVG_API LUNASVG_EXPORT
#else
#define LUNASVG_API LUNASVG_IMPORT
#endif

#define LUNASVG_VERSION_MAJOR 3
#define LUNASVG_VERSION_MINOR 5
#define LUNASVG_VERSION_MICRO 0

#define LUNASVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1))
#define LUNASVG_VERSION LUNASVG_VERSION_ENCODE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO)

#define LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro
#define LUNASVG_VERSION_STRINGIZE(major, minor, micro) LUNASVG_VERSION_XSTRINGIZE(major, minor, micro)
#define LUNASVG_VERSION_STRING LUNASVG_VERSION_STRINGIZE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO)

#ifdef __cplusplus
extern "C" {
#endif

typedef struct juce_plutovg_surface juce_plutovg_surface_t;
typedef struct juce_plutovg_matrix juce_plutovg_matrix_t;

/**
 * @brief Callback for cleaning up resources.
 *
 * This function is called to release resources associated with a specific operation.
 *
 * @param closure A user-defined pointer to the resource or context to be freed.
 */
typedef void (*lunasvg_destroy_func_t)(void* closure);

/**
 * @brief A function pointer type for a write callback.
 * @param closure A pointer to user-defined data or context.
 * @param data A pointer to the data to be written.
 * @param size The size of the data in bytes.
 */
typedef void (*lunasvg_write_func_t)(void* closure, void* data, int size);

/**
 * @brief Returns the version of the lunasvg library encoded in a single integer.
 *
 * Encodes the version of the lunasvg library into a single integer for easier comparison.
 * The version is typically represented by combining major, minor, and patch numbers into one integer.
 *
 * @return The lunasvg library version as a single integer.
 */
LUNASVG_API int lunasvg_version(void);

/**
 * @brief Returns the lunasvg library version as a human-readable string in "X.Y.Z" format.
 *
 * Provides the version of the lunasvg library as a human-readable string in the format "X.Y.Z",
 * where X represents the major version, Y the minor version, and Z the patch version.
 *
 * @return A pointer to a string containing the version in "X.Y.Z" format.
 */
LUNASVG_API const char* lunasvg_version_string(void);

/**
* @brief Add a font face from a file to the cache.
* @param family The name of the font family. If an empty string is provided, the font will act as a fallback.
* @param bold Use `true` for bold, `false` otherwise.
* @param italic Use `true` for italic, `false` otherwise.
* @param filename The path to the font file.
* @return `true` if the font face was successfully added to the cache, `false` otherwise.
*/
LUNASVG_API bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename);

/**
* @brief Add a font face from memory to the cache.
* @param family The name of the font family. If an empty string is provided, the font will act as a fallback.
* @param bold Use `true` for bold, `false` otherwise.
* @param italic Use `true` for italic, `false` otherwise.
* @param data A pointer to the memory buffer containing the font data.
* @param length The size of the memory buffer in bytes.
* @param destroy_func Callback function to free the memory buffer when it is no longer needed.
* @param closure User-defined pointer passed to the `destroy_func` callback.
* @return `true` if the font face was successfully added to the cache, `false` otherwise.
*/
LUNASVG_API bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure);

#ifdef __cplusplus
}
#endif

namespace {
namespace lunasvg {

/**
* @note Bitmap pixel format is ARGB32_Premultiplied.
*/
class LUNASVG_API Bitmap {
public:
    /**
     * @brief Constructs a null bitmap.
     */
    Bitmap() = default;

    /**
     * @brief Constructs a bitmap with the specified width and height.
     * @note A null bitmap will be returned if memory cannot be allocated.
     * @param width The width of the bitmap in pixels.
     * @param height The height of the bitmap in pixels.
     */
    Bitmap(int width, int height);

    /**
     * @brief Constructs a bitmap with the provided pixel data, width, height, and stride.
     *
     * @param data A pointer to the raw pixel data in ARGB32 Premultiplied format.
     * @param width The width of the bitmap in pixels.
     * @param height The height of the bitmap in pixels.
     * @param stride The number of bytes per row of pixel data (stride).
    */
    Bitmap(uint8_t* data, int width, int height, int stride);

    /**
     * @brief Copy constructor.
     * @param bitmap The bitmap to copy.
     */
    Bitmap(const Bitmap& bitmap);

    /**
     * @brief Move constructor.
     * @param bitmap The bitmap to move.
     */
    Bitmap(Bitmap&& bitmap);

    /**
     * @internal
     */
    Bitmap(juce_plutovg_surface_t* surface) : m_surface(surface) {}

    /**
     * @brief Cleans up any resources associated with the bitmap.
     */
    ~Bitmap();

    /**
     * @brief Copy assignment operator.
     * @param bitmap The bitmap to copy.
     * @return A reference to this bitmap.
     */
    Bitmap& operator=(const Bitmap& bitmap);

    /**
     * @brief Move assignment operator.
     * @param bitmap The bitmap to move.
     * @return A reference to this bitmap.
     */
    Bitmap& operator=(Bitmap&& bitmap);

    /**
     * @brief Swaps the content of this bitmap with another.
     * @param bitmap The bitmap to swap with.
     */
    void swap(Bitmap& bitmap);

    /**
     * @brief Gets the pointer to the raw pixel data.
     * @return A pointer to the raw pixel data.
     */
    uint8_t* data() const;

    /**
     * @brief Gets the width of the bitmap.
     * @return The width of the bitmap in pixels.
     */
    int width() const;

    /**
     * @brief Gets the height of the bitmap.
     * @return The height of the bitmap in pixels.
     */
    int height() const;

    /**
     * @brief Gets the stride of the bitmap.
     * @return The number of bytes per row of pixel data (stride).
     */
    int stride() const;

    /**
     * @brief Clears the bitmap with the specified color.
     * @param The color value in 0xRRGGBBAA format.
     */
    void clear(uint32_t value);

    /**
     * @brief Converts the bitmap pixel data from ARGB32 Premultiplied to RGBA Plain format in place.
     */
    void convertToRGBA();

    /**
     * @brief Checks if the bitmap is null.
     * @return True if the bitmap is null, false otherwise.
     */
    bool isNull() const { return m_surface == nullptr; }

    /**
     * @brief Checks if the bitmap is valid.
     * @deprecated This function has been deprecated. Use `isNull()` instead to check whether the bitmap is null.
     * @return True if the bitmap is valid, false otherwise.
     */
    bool valid() const { return !isNull(); }

    /**
     * @brief Writes the bitmap to a PNG file.
     * @param filename The name of the file to write.
     * @return True if the file was written successfully, false otherwise.
     */
    bool writeToPng(const std::string& filename) const;

    /**
     * @brief Writes the bitmap to a PNG stream.
     * @param callback Callback function for writing data.
     * @param closure User-defined data passed to the callback.
     * @return True if successful, false otherwise.
     */
    bool writeToPng(lunasvg_write_func_t callback, void* closure) const;

    /**
     * @internal
     */
    juce_plutovg_surface_t* surface() const { return m_surface; }

private:
    juce_plutovg_surface_t* release();
    juce_plutovg_surface_t* m_surface{nullptr};
};

class Rect;
class Matrix;

/**
 * @brief Represents a 2D axis-aligned bounding box.
 */
class LUNASVG_API Box {
public:
    /**
     * @brief Constructs a box with zero dimensions.
     */
    Box() = default;

    /**
     * @brief Constructs a box with the specified position and size.
     * @param x The x-coordinate of the box's origin.
     * @param y The y-coordinate of the box's origin.
     * @param w The width of the box.
     * @param h The height of the box.
     */
    Box(float x, float y, float w, float h);

    /**
     * @internal
     */
    Box(const Rect& rect);

    /**
     * @brief Transforms the box using the specified matrix.
     * @param matrix The transformation matrix.
     * @return A reference to this box, modified by the transformation.
     */
    Box& transform(const Matrix& matrix);

    /**
     * @brief Returns a new box transformed by the specified matrix.
     * @param matrix The transformation matrix.
     * @return A new box, transformed by the matrix.
     */
    Box transformed(const Matrix& matrix) const;

    float x{0}; ///< The x-coordinate of the box's origin.
    float y{0}; ///< The y-coordinate of the box's origin.
    float w{0}; ///< The width of the box.
    float h{0}; ///< The height of the box.
};

class Transform;

/**
 * @brief Represents a 2D transformation matrix.
 */
class LUNASVG_API Matrix {
public:
    /**
     * @brief Initializes the matrix to the identity matrix.
     */
    Matrix() = default;

    /**
     * @brief Constructs a matrix with the specified values.
     * @param a The horizontal scaling factor.
     * @param b The vertical shearing factor.
     * @param c The horizontal shearing factor.
     * @param d The vertical scaling factor.
     * @param e The horizontal translation offset.
     * @param f The vertical translation offset.
     */
    Matrix(float a, float b, float c, float d, float e, float f);

    /**
     * @internal
     */
    Matrix(const juce_plutovg_matrix_t& matrix);

    /**
     * @internal
     */
    Matrix(const Transform& transform);

    /**
     * @brief Multiplies this matrix with another matrix.
     * @param matrix The matrix to multiply with.
     * @return A new matrix that is the result of the multiplication.
     */
    Matrix operator*(const Matrix& matrix) const;

    /**
     * @brief Multiplies this matrix with another matrix in place.
     * @param matrix The matrix to multiply with.
     * @return A reference to this matrix after multiplication.
     */
    Matrix& operator*=(const Matrix& matrix);

    /**
     * @brief Multiplies this matrix with another matrix.
     * @param matrix The matrix to multiply with.
     * @return A reference to this matrix after multiplication.
     */
    Matrix& multiply(const Matrix& matrix);

    /**
     * @brief Translates this matrix by the specified offsets.
     * @param tx The horizontal translation offset.
     * @param ty The vertical translation offset.
     * @return A reference to this matrix after translation.
     */
    Matrix& translate(float tx, float ty);

    /**
     * @brief Scales this matrix by the specified factors.
     * @param sx The horizontal scaling factor.
     * @param sy The vertical scaling factor.
     * @return A reference to this matrix after scaling.
     */
    Matrix& scale(float sx, float sy);

    /**
     * @brief Rotates this matrix by the specified angle around a point.
     * @param angle The rotation angle in degrees.
     * @param cx The x-coordinate of the center of rotation.
     * @param cy The y-coordinate of the center of rotation.
     * @return A reference to this matrix after rotation.
     */
    Matrix& rotate(float angle, float cx = 0.f, float cy = 0.f);

    /**
     * @brief Shears this matrix by the specified factors.
     * @param shx The horizontal shearing factor.
     * @param shy The vertical shearing factor.
     * @return A reference to this matrix after shearing.
     */
    Matrix& shear(float shx, float shy);

    /**
     * @brief Inverts this matrix.
     * @return A reference to this matrix after inversion.
     */
    Matrix& invert();

    /**
     * @brief Returns the inverse of this matrix.
     * @return A new matrix that is the inverse of this matrix.
     */
    Matrix inverse() const;

    /**
     * @brief Resets this matrix to the identity matrix.
     */
    void reset();

    /**
     * @brief Creates a translation matrix with the specified offsets.
     * @param tx The horizontal translation offset.
     * @param ty The vertical translation offset.
     * @return A new translation matrix.
     */
    static Matrix translated(float tx, float ty);

    /**
     * @brief Creates a scaling matrix with the specified factors.
     * @param sx The horizontal scaling factor.
     * @param sy The vertical scaling factor.
     * @return A new scaling matrix.
     */
    static Matrix scaled(float sx, float sy);

    /**
     * @brief Creates a rotation matrix with the specified angle around a point.
     * @param angle The rotation angle in degrees.
     * @param cx The x-coordinate of the center of rotation.
     * @param cy The y-coordinate of the center of rotation.
     * @return A new rotation matrix.
     */
    static Matrix rotated(float angle, float cx = 0.f, float cy = 0.f);

    /**
     * @brief Creates a shearing matrix with the specified factors.
     * @param shx The horizontal shearing factor.
     * @param shy The vertical shearing factor.
     * @return A new shearing matrix.
     */
    static Matrix sheared(float shx, float shy);

    float a{1}; ///< The horizontal scaling factor.
    float b{0}; ///< The vertical shearing factor.
    float c{0}; ///< The horizontal shearing factor.
    float d{1}; ///< The vertical scaling factor.
    float e{0}; ///< The horizontal translation offset.
    float f{0}; ///< The vertical translation offset.
};

class SVGNode;
class SVGTextNode;
class SVGElement;

class Element;
class TextNode;

class LUNASVG_API Node {
public:
    /**
     * @brief Constructs a null node.
     */
    Node() = default;

    /**
     * @brief Checks if the node is a text node.
     * @return True if the node is a text node, false otherwise.
     */
    bool isTextNode() const;

    /**
     * @brief Checks if the node is an element node.
     * @return True if the node is an element node, false otherwise.
     */
    bool isElement() const;

    /**
     * @brief Converts the node to a TextNode.
     * @return A TextNode or a null node if conversion is not possible.
     */
    TextNode toTextNode() const;

    /**
     * @brief Converts the node to an Element.
     * @return An Element or a null node if conversion is not possible.
     */
    Element toElement() const;

    /**
     * @brief Returns the parent element.
     * @return The parent element of this node. If this node has no parent, a null `Element` is returned.
     */
    Element parentElement() const;

    /**
     * @brief Checks if the node is null.
     * @return True if the node is null, false otherwise.
     */
    bool isNull() const { return m_node == nullptr; }

    /**
     * @brief Checks if the node is not null.
     * @return True if the node is not null, false otherwise.
     */
    operator bool() const { return !isNull(); }

    /**
     * @brief Checks if two nodes are equal.
     * @param element The node to compare.
     * @return True if equal, otherwise false.
     */
    bool operator==(const Node& node) const { return m_node == node.m_node; }

    /**
     * @brief Checks if two nodes are not equal.
     * @param element The node to compare.
     * @return True if not equal, otherwise false.
     */
    bool operator!=(const Node& node) const { return m_node != node.m_node; }

protected:
    Node(SVGNode* node);
    SVGNode* node() const { return m_node; }
    SVGNode* m_node{nullptr};
    friend class Element;
};

using NodeList = std::vector<Node>;

class LUNASVG_API TextNode : public Node {
public:
    /**
     * @brief Constructs a null text node.
     */
    TextNode() = default;

    /**
     * @brief Returns the text content of the node.
     * @return A string representing the text content.
     */
    const std::string& data() const;

    /**
     * @brief Sets the text content of the node.
     * @param data The new text content to set.
     */
    void setData(const std::string& data);

private:
    TextNode(SVGTextNode* text);
    SVGTextNode* text() const;
    friend class Node;
};

class LUNASVG_API Element : public Node {
public:
    /**
     * @brief Constructs a null element.
     */
    Element() = default;

    /**
     * @brief Checks if the element has a specific attribute.
     * @param name The name of the attribute to check.
     * @return True if the element has the specified attribute, false otherwise.
     */
    bool hasAttribute(const std::string& name) const;

    /**
     * @brief Retrieves the value of an attribute.
     * @param name The name of the attribute to retrieve.
     * @return The value of the attribute as a string.
     */
    const std::string& getAttribute(const std::string& name) const;

    /**
     * @brief Sets the value of an attribute.
     * @param name The name of the attribute to set.
     * @param value The value to assign to the attribute.
     */
    void setAttribute(const std::string& name, const std::string& value);

    /**
     * @brief Renders the element onto a bitmap using a transformation matrix.
     * @param bitmap The bitmap to render onto.
     * @param The root transformation matrix.
     */
    void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const;

    /**
     * @brief Renders the element to a bitmap with specified dimensions.
     * @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size.
     * @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size.
     * @param backgroundColor The background color in 0xRRGGBBAA format.
     * @return A Bitmap containing the raster representation of the element.
     */
    Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const;

    /**
     * @brief Retrieves the local transformation matrix of the element.
     * @return The matrix that applies only to the element, relative to its parent.
     */
    Matrix getLocalMatrix() const;

    /**
     * @brief Retrieves the global transformation matrix of the element.
     * @return The matrix combining the element's local and all parent transformations.
     */
    Matrix getGlobalMatrix() const;

    /**
     * @brief Retrieves the local bounding box of the element.
     * @return A Box representing the bounding box after applying local transformations.
     */
    Box getLocalBoundingBox() const;

    /**
     * @brief Retrieves the global bounding box of the element.
     * @return A Box representing the bounding box after applying global transformations.
     */
    Box getGlobalBoundingBox() const;

    /**
     * @brief Retrieves the bounding box of the element without any transformations.
     * @return A Box representing the bounding box of the element without any transformations applied.
     */
    Box getBoundingBox() const;

    /**
     * @brief Returns the child nodes of this node.
     * @return A NodeList containing the child nodes.
     */
    NodeList children() const;

private:
    Element(SVGElement* element);
    SVGElement* element(bool layoutIfNeeded = false) const;
    friend class Node;
    friend class Document;
};

using ElementList = std::vector<Element>;

class SVGRootElement;
class Canvas;

class LUNASVG_API Document {
public:
    /**
     * @brief Load an SVG document from a file.
     * @param filename The path to the SVG file.
     * @return A pointer to the loaded `Document`, or `nullptr` on failure.
     */
    static std::unique_ptr<Document> loadFromFile(const std::string& filename);

    /**
     * @brief Load an SVG document from a string.
     * @param string The SVG data as a string.
     * @return A pointer to the loaded `Document`, or `nullptr` on failure.
     */
    static std::unique_ptr<Document> loadFromData(const std::string& string);

    /**
     * @brief Load an SVG document from a null-terminated string.
     * @param data The string containing the SVG data.
     * @return A pointer to the loaded `Document`, or `nullptr` on failure.
     */
    static std::unique_ptr<Document> loadFromData(const char* data);

    /**
     * @brief Load an SVG document from a string with a specified length.
     * @param data The string containing the SVG data.
     * @param length The length of the string in bytes.
     * @return A pointer to the loaded `Document`, or `nullptr` on failure.
     */
    static std::unique_ptr<Document> loadFromData(const char* data, size_t length);

    /**
     * @brief Applies a CSS stylesheet to the document.
     * @param content A string containing the CSS rules to apply, with comments removed.
     */
    void applyStyleSheet(const std::string& content);

    /**
     * @brief Selects all elements that match the given CSS selector(s).
     * @param content A string containing the CSS selector(s) to match elements.
     * @return A list of elements matching the selector(s).
     */
    ElementList querySelectorAll(const std::string& content) const;

    /**
     * @brief Returns the intrinsic width of the document in pixels.
     * @return The width of the document.
     */
    float width() const;

    /**
     * @brief Returns the intrinsic height of the document in pixels.
     * @return The height of the document.
     */
    float height() const;

    /**
     * @brief Returns the smallest rectangle that encloses the document content.
     * @return A Box representing the bounding box of the document.
     */
    Box boundingBox() const;

    /**
     * @brief Updates the layout of the document if needed.
     */
    void updateLayout();

    /**
     * @brief Forces an immediate layout update.
     */
    void forceLayout();

    void renderToCanvas(std::shared_ptr<Canvas> canvas, const Matrix& matrix) const;

    /**
     * @brief Renders the document onto a bitmap using a transformation matrix.
     * @param bitmap The bitmap to render onto.
     * @param The root transformation matrix.
     */
    void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const;

    /**
     * @brief Renders the document to a bitmap with specified dimensions.
     * @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size.
     * @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size.
     * @param backgroundColor The background color in 0xRRGGBBAA format.
     * @return A Bitmap containing the raster representation of the document.
     */
    Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const;

    /**
     * @brief Returns the topmost element under the specified point.
     * @param x The x-coordinate in viewport space.
     * @param y The y-coordinate in viewport space.
     * @return The topmost Element at the given point, or a null `Element` if no match is found.
     */
    Element elementFromPoint(float x, float y) const;

    /**
     * @brief Retrieves an element by its ID.
     * @param id The ID of the element to retrieve.
     * @return The Element with the specified ID, or a null `Element` if not found.
     */
    Element getElementById(const std::string& id) const;

    /**
     * @brief Retrieves the document element.
     * @return The root Element of the document.
     */
    Element documentElement() const;

    const std::string& getElementIdBeingRendered() const
    {
        return elementIdBeingRendered;
    }

    std::string setElementIdBeingRendered (const std::string& eId)
    {
        return std::exchange (elementIdBeingRendered, eId);
    }

    Document(Document&&);
    Document& operator=(Document&&);
    ~Document();

private:
    Document();
    Document(const Document&) = delete;
    Document& operator=(const Document&) = delete;
    SVGRootElement* rootElement(bool layoutIfNeeded = false) const;
    bool parse(const char* data, size_t length);
    std::unique_ptr<SVGRootElement> m_rootElement;
    friend class SVGURIReference;
    friend class SVGNode;

    std::string elementIdBeingRendered;
};

} // namespace lunasvg
}

#endif // LUNASVG_H
