#ifndef RAYLIB_CPP_INCLUDE_FONTUNMANAGED_HPP_
#define RAYLIB_CPP_INCLUDE_FONTUNMANAGED_HPP_

#include <string>

#include "./RaylibException.hpp"
#include "./TextureUnmanaged.hpp"
#include "./raylib-cpp-utils.hpp"
#include "./raylib.hpp"

namespace raylib {
/**
 * A Font that is not managed by C++ RAII.
 *
 * Make sure to Unload() this if needed, otherwise use raylib::Font.
 *
 * @see raylib::Font
 */
class FontUnmanaged : public ::Font {
public:
    FontUnmanaged(
        int baseSize,
        int glyphCount,
        int glyphPadding,
        ::Texture2D texture,
        ::Rectangle* recs = nullptr,
        ::GlyphInfo* glyphs = nullptr)
        : ::Font{baseSize, glyphCount, glyphPadding, texture, recs, glyphs} {
        // Nothing.
    }

    /**
     * Retrieves the default Font.
     */
    FontUnmanaged() : ::Font(::GetFontDefault()) {}

    /**
     * Creates a FontUnmanaged from an existing Font struct.
     */
    FontUnmanaged(const ::Font& font) : ::Font(font) {}

    /**
     * Loads a Font from the given file.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    FontUnmanaged(const std::string& fileName) { Load(fileName); }

    /**
     * Loads a Font from the given file, with generation parameters.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    FontUnmanaged(const std::string& fileName, int fontSize, const int* codepoints = nullptr, int codepointCount = 0) {
        Load(fileName, fontSize, codepoints, codepointCount);
    }

    /**
     * Loads a Font from the given image with a color key.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    FontUnmanaged(const ::Image& image, ::Color key, int firstChar) { Load(image, key, firstChar); }

    /**
     * Loads a font from memory, based on the given file type and file data.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    FontUnmanaged(
        const std::string& fileType,
        const unsigned char* fileData,
        int dataSize,
        int fontSize,
        const int* codepoints,
        int codepointCount) {
        Load(fileType, fileData, dataSize, fontSize, codepoints, codepointCount);
    }

    GETTER(int, BaseSize, baseSize)
    GETTER(int, GlyphCount, glyphCount)
    GETTER(int, GlyphPadding, glyphPadding)
    GETTER(::Rectangle*, Recs, recs)
    GETTER(::GlyphInfo*, Glyphs, glyphs)

    /**
     * Get the texture atlas containing the glyphs.
     */
    TextureUnmanaged GetTexture() { return texture; }
    RLCPP_NODISCARD TextureUnmanaged GetTexture() const { return texture; }

    /**
     * Set the texture atlas containing the glyphs.
     */
    void SetTexture(const ::Texture& newTexture) { texture = newTexture; }

    FontUnmanaged& operator=(const ::Font& font) {
        set(font);
        return *this;
    }

    /**
     * Loads a font from a given file.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    void Load(const std::string& fileName) {
        set(::LoadFont(fileName.c_str()));
        if (!IsValid()) {
            throw RaylibException("Failed to load Font with from file: " + fileName);
        }
    }

    /**
     * Loads a font from a given file with generation parameters.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    void Load(const std::string& fileName, int fontSize, const int* codepoints = nullptr, int codepointCount = 0) {
        set(::LoadFontEx(fileName.c_str(), fontSize, codepoints, codepointCount));
        if (!IsValid()) {
            throw RaylibException("Failed to load Font with from file with font size: " + fileName);
        }
    }

    /**
     * Loads a font from an image with a color key.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    void Load(const ::Image& image, ::Color key, int firstChar) {
        set(::LoadFontFromImage(image, key, firstChar));
        if (!IsValid()) {
            throw RaylibException("Failed to load Font with from image");
        }
    }

    /**
     * Loads a font from memory.
     *
     * @throws raylib::RaylibException Throws if the given font failed to initialize.
     */
    void Load(
        const std::string& fileType,
        const unsigned char* fileData,
        int dataSize,
        int fontSize,
        const int* codepoints,
        int codepointCount) {
        set(::LoadFontFromMemory(fileType.c_str(), fileData, dataSize, fontSize, codepoints, codepointCount));
        if (!IsValid()) {
            throw RaylibException("Failed to load Font " + fileType + " with from file data");
        }
    }

    /**
     * Unload font from memory.
     */
    void Unload() {
        // Protect against calling UnloadFont() twice.
        if (baseSize != 0) {
            ::UnloadFont(*this);
            baseSize = 0;
        }
    }

    /**
     * Returns if the font is ready to be used.
     */
    RLCPP_NODISCARD bool IsValid() const { return ::IsFontValid(*this); }

    /**
     * Draw text using font and additional parameters.
     */
    void DrawText(const char* text, ::Vector2 position, float fontSize, float spacing, ::Color tint = WHITE) const {
        ::DrawTextEx(*this, text, position, fontSize, spacing, tint);
    }

    /**
     * Draw text using font and additional parameters.
     */
    void DrawText(
        const std::string& text,
        ::Vector2 position,
        float fontSize,
        float spacing,
        ::Color tint = WHITE) const {
        ::DrawTextEx(*this, text.c_str(), position, fontSize, spacing, tint);
    }

    /**
     * Draw text using font and additional parameters.
     */
    void DrawText(const char* text, int posX, int posY, float fontSize, float spacing, ::Color tint = WHITE) const {
        ::DrawTextEx(*this, text, {static_cast<float>(posX), static_cast<float>(posY)}, fontSize, spacing, tint);
    }

    /**
     * Draw text using font and additional parameters.
     */
    void DrawText(
        const std::string& text,
        int posX,
        int posY,
        float fontSize,
        float spacing,
        ::Color tint = WHITE) const {
        ::DrawTextEx(
            *this,
            text.c_str(),
            {static_cast<float>(posX), static_cast<float>(posY)},
            fontSize,
            spacing,
            tint);
    }

    /**
     * Draw text using font with pro parameters (rotation).
     */
    void DrawText(
        const char* text,
        ::Vector2 position,
        ::Vector2 origin,
        float rotation,
        float fontSize,
        float spacing,
        ::Color tint = WHITE) const {
        ::DrawTextPro(*this, text, position, origin, rotation, fontSize, spacing, tint);
    }

    /**
     * Draw text using font with pro parameters (rotation).
     */
    void DrawText(
        const std::string& text,
        ::Vector2 position,
        ::Vector2 origin,
        float rotation,
        float fontSize,
        float spacing,
        ::Color tint = WHITE) const {
        ::DrawTextPro(*this, text.c_str(), position, origin, rotation, fontSize, spacing, tint);
    }

    /**
     * Draw one character (codepoint).
     */
    void DrawText(int codepoint, ::Vector2 position, float fontSize, ::Color tint = {255, 255, 255, 255}) const {
        ::DrawTextCodepoint(*this, codepoint, position, fontSize, tint);
    }

    /**
     * Draw multiple characters (codepoints).
     */
    void DrawText(
        const int* codepoints,
        int count,
        ::Vector2 position,
        float fontSize,
        float spacing,
        ::Color tint = {255, 255, 255, 255}) const {
        ::DrawTextCodepoints(*this, codepoints, count, position, fontSize, spacing, tint);
    }

    /**
     * Measure string size for Font.
     */
    RLCPP_NODISCARD Vector2 MeasureText(const char* text, float fontSize, float spacing) const {
        return ::MeasureTextEx(*this, text, fontSize, spacing);
    }

    /**
     * Measure string size for Font.
     */
    RLCPP_NODISCARD Vector2 MeasureText(const std::string& text, float fontSize, float spacing) const {
        return ::MeasureTextEx(*this, text.c_str(), fontSize, spacing);
    }

    /**
     * Get index position for a unicode character on font.
     */
    RLCPP_NODISCARD int GetGlyphIndex(int character) const { return ::GetGlyphIndex(*this, character); }

    /**
     * Create an image from text (custom sprite font).
     */
    RLCPP_NODISCARD ::Image ImageText(const char* text, float fontSize, float spacing, ::Color tint) const {
        return ::ImageTextEx(*this, text, fontSize, spacing, tint);
    }

    /**
     * Create an image from text (custom sprite font).
     */
    RLCPP_NODISCARD ::Image ImageText(const std::string& text, float fontSize, float spacing, ::Color tint) const {
        return ::ImageTextEx(*this, text.c_str(), fontSize, spacing, tint);
    }

protected:
    void set(const ::Font& font) {
        baseSize = font.baseSize;
        glyphCount = font.glyphCount;
        glyphPadding = font.glyphPadding;
        texture = font.texture;
        recs = font.recs;
        glyphs = font.glyphs;
    }
};
} // namespace raylib

using RFontUnmanaged = raylib::FontUnmanaged;

#endif // RAYLIB_CPP_INCLUDE_FONTUNMANAGED_HPP_
