/*

MIT License

Copyright (c) 2022 PCSX-Redux authors

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.

*/

#pragma once

#include <EASTL/array.h>
#include <EASTL/functional.h>
#include <EASTL/string_view.h>
#include <stdarg.h>
#include <stdint.h>

#include "psyqo/fragments.hh"
#include "psyqo/gpu.hh"
#include "psyqo/primitives/common.hh"
#include "psyqo/primitives/sprites.hh"

namespace psyqo {

template <size_t Fragments = 16>
class Font;

/**
 * @brief The Font drawing class.
 *
 * @details This class is used to draw text to the screen. It is a template class that takes a number of fragments as
 * a template parameter. The number of fragments determines how many times a chainprint can be called. If not using
 * chainprint, then the number of fragments is irrelevant, and should be set to 1. The font is expected to be uploaded
 * to VRAM before use. It is expected to be a 4bpp texture, 256 pixels wide. While the location and size are
 * user-defined, it is the user's responsibility to ensure that the font properly fits in a texture page. It should be
 * in ASCII order, with the first character being ASCII 0x20 (space), which has to be completely transparent. Even
 * though the font is 4bpp, it really should be monochrome, with the color being set by the color parameter of the
 * various print methods.
 */
class FontBase {
  public:
    virtual ~FontBase() {}

    /**
     * @brief Uploads the system font to VRAM, and initializes the object.
     *
     * @details This method uploads the built-in system font to VRAM, and initializes the object.
     * There is no need to call this method if you are using your own font. Also, when using this
     * method, you should not call initialize() afterwards. The footprint for this font is 877 bytes
     * of read-only data, and a 256x48x4bpp texture. It is a 8x16 clean and simple ASCII font called
     * mig68000, available for free with attribution, made by Zingot Games. See
     * https://www.zingot.com/ and https://zingot.itch.io/fontpack
     */
    void uploadSystemFont(GPU& gpu, Vertex location = {{.x = 960, .y = 464}});

    /**
     * @brief Uploads the Kernel rom font to VRAM, and initializes the object.
     *
     * @details This method uploads the built-in Kernel rom font to VRAM, and initializes the object.
     * There is no need to call this method if you are using your own font. Also, when using this
     * method, you should not call initialize() afterwards. The Kernel rom font is a 16x15 font created
     * by Sony, and built into the PSX rom chip. Its appearance is variable, depending on the version
     * of the PSX bios. It may not be available on all PSX models. The footprint for this font is 192
     * bytes of read-only data, and a 256x90x4bpp texture. This font isn't going to work if psyqo
     * took over the kernel. See the `Kernel` namespace for more information.
     */
    void uploadKromFont(GPU& gpu, Vertex location = {{.x = 960, .y = 422}});

    /**
     * @brief Unpacks and uploads a font to VRAM.
     *
     * @details This method unpacks and uploads a font to VRAM. The compressed font data is expected to be in the
     * format generated by the `font-compress.lua` script. See this script for more details.
     */
    static void unpackFont(GPU& gpu, const uint8_t* data, Vertex location, Vertex textureSize);

    /**
     * @brief Initializes the object for use.
     *
     * @details When using your own font, you should call this method to initialize the Font object. This includes
     * when using the `unpackFont` method. This method should not be called if you are using the `uploadSystemFont`
     * method.
     * @param location The location of the font in VRAM.
     * @param glyphSize The size of each glyph in the font.
     */
    void initialize(GPU& gpu, Vertex location, Vertex glyphSize);

    /**
     * @brief These method immediately print text to the screen.
     *
     * @details These methods immediately print text to the screen. They are meant to be used when not using
     * DMA chaining. When using DMA chaining, you should use the `chainprint` method family instead. When
     * a callback is provided, it will be called when the text has been printed, while the method will return
     * immediately. See the `GPU` class for more details on DMA callbacks.
     */
    void print(GPU& gpu, eastl::string_view text, Vertex pos, Color color);
    void print(GPU& gpu, eastl::string_view text, Vertex pos, Color color, eastl::function<void()>&& callback,
               DMA::DmaCallback dmaCallback);
    void print(GPU& gpu, const char* text, Vertex pos, Color color);
    void print(GPU& gpu, const char* text, Vertex pos, Color color, eastl::function<void()>&& callback,
               DMA::DmaCallback dmaCallback);
    void printf(GPU& gpu, Vertex pos, Color color, const char* format, ...) {
        va_list args;
        va_start(args, format);
        vprintf(gpu, pos, color, format, args);
        va_end(args);
    }
    void printf(GPU& gpu, Vertex pos, Color color, eastl::function<void()>&& callback, DMA::DmaCallback dmaCallback,
                const char* format, ...) {
        va_list args;
        va_start(args, format);
        vprintf(gpu, pos, color, eastl::move(callback), dmaCallback, format, args);
        va_end(args);
    }
    void vprintf(GPU& gpu, Vertex pos, Color color, const char* format, va_list ap);
    void vprintf(GPU& gpu, Vertex pos, Color color, eastl::function<void()>&& callback, DMA::DmaCallback dmaCallback,
                 const char* format, va_list ap);

    /**
     * @brief These methods use the DMA chaining system to print text to the screen.
     *
     * @details These methods use the DMA chaining system to print text to the screen. They are meant to be used when
     * constructing a frame using DMA chaining. When not using DMA chaining, you should use the `print` method family
     * instead. See the `GPU` class for more details on DMA chaining.
     */
    void chainprint(GPU& gpu, eastl::string_view text, Vertex pos, Color color);
    void chainprint(GPU& gpu, const char* text, Vertex pos, Color color);
    void chainprintf(GPU& gpu, Vertex pos, Color color, const char* format, ...) {
        va_list args;
        va_start(args, format);
        chainvprintf(gpu, pos, color, format, args);
        va_end(args);
    }
    void chainvprintf(GPU& gpu, Vertex pos, Color color, const char* format, va_list ap);

  protected:
    struct GlyphsFragmentPrologue {
        Prim::VRAMUpload upload;
        uint32_t pixel;
        Prim::FlushCache flushCache;
        Prim::TPage tpage;
    };
    typedef Fragments::FixedFragmentWithPrologue<GlyphsFragmentPrologue, Prim::Sprite, 48> GlyphsFragment;
    virtual GlyphsFragment* getGlyphFragment(bool increment) = 0;
    virtual void forEach(eastl::function<void(GlyphsFragment&)>&& cb) = 0;

    void innerprint(GlyphsFragment* fragment, GPU& gpu, eastl::string_view text, Vertex pos, Color color);
    void innerprint(GlyphsFragment* fragment, GPU& gpu, const char* text, Vertex pos, Color color);
    void innervprintf(GlyphsFragment* fragment, GPU& gpu, Vertex pos, Color color, const char* format, va_list ap);

  private:
    struct XPrintfInfo;
    GlyphsFragment& printToFragment(GPU& gpu, const char* text, Vertex pos, Color color);
    eastl::array<PrimPieces::TexInfo, 224> m_lut;
    Vertex m_glyphSize;

    friend struct XPrintfInfo;
};

template <size_t N>
class Font : public FontBase {
  public:
    virtual ~Font() { static_assert(N > 0, "Needs to have at least one fragment"); }

  private:
    virtual GlyphsFragment* getGlyphFragment(bool increment) override {
        auto fragment = &m_fragments[m_index];
        if (increment) {
            if (++m_index == N) {
                m_index = 0;
            }
        }
        return fragment;
    }
    virtual void forEach(eastl::function<void(GlyphsFragment&)>&& cb) override {
        for (auto& fragment : m_fragments) {
            cb(fragment);
        }
    }
    eastl::array<GlyphsFragment, N> m_fragments;
    unsigned m_index = 0;
};

}  // namespace psyqo
