/*
 * Copyright (C) 2021 magicxqq <xqq@xqq.im>. All rights reserved.
 *
 * This file is part of libaribcaption.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef ARIBCAPTION_RENDERER_HPP
#define ARIBCAPTION_RENDERER_HPP

#include <memory>
#include <optional>
#include "aribcc_config.h"
#include "aribcc_export.h"
#include "context.hpp"
#include "caption.hpp"
#include "image.hpp"

namespace aribcaption {

/**
 * Enums for FontProvider indication
 */
enum class FontProviderType {
    /**
     * Detect and select FontProvider automatically. Should be used in most cases.
     */
    kAuto = 0,

#if defined(ARIBCC_USE_CORETEXT)
    /**
     * FontProvider relies on Apple CoreText API. Available on macOS and iOS.
     */
    kCoreText = 1,
#endif

#if defined(ARIBCC_USE_DIRECTWRITE)
    /**
     * FontProvider relies on DirectWrite API. Available on Windows 7+.
     */
    kDirectWrite = 2,
#endif

#if defined(ARIBCC_USE_FONTCONFIG)
    /**
     * FontProvider using libfontconfig. Usually been used on Linux platforms.
     */
    kFontconfig = 3,
#endif

#if defined(ARIBCC_IS_ANDROID)
    /**
     * FontProvider for Android. Available on Android 2.x+.
     */
    kAndroid = 4,
#endif

#if defined(ARIBCC_USE_GDI_FONT)
    /**
     * FontProvder based on Win32 GDI API. Available on Windows 2000+.
     */
    kGDI = 5,
#endif
};

/**
 * Enums for TextRenderer indication
 */
enum class TextRendererType {
    /**
     * Detect and select TextRenderer automatically. Should be used in most cases.
     */
    kAuto = 0,

#if defined(ARIBCC_USE_CORETEXT)
    /**
     * Apple CoreText API based TextRenderer. Available on macOS and iOS.
     */
    kCoreText = 1,
#endif

#if defined(ARIBCC_USE_DIRECTWRITE)
    /**
     * DirectWrite API based TextRenderer. Available on Windows 7+.
     */
    kDirectWrite = 2,
#endif

#if defined(ARIBCC_USE_FREETYPE)
    /**
     * Freetype based TextRenderer. Available on all platforms.
     */
    kFreetype = 3,
#endif
};

namespace internal { class RendererImpl; }

/**
 * Enums for Renderer's caption storage policy indication
 */
enum class CaptionStoragePolicy {
    /**
     * The renderer will only keep minimal amount of captions for subsequent rendering if necessary.
     * This is the default behavior.
     */
    kMinimum = 0,

    /**
     * The renderer will never evict appended captions unless you call the @Renderer::Flush().
     * This may result in memory waste. Use at your own risk.
     */
    kUnlimited = 1,

    /**
     * The renderer will keep appended captions at an upper limit of count.
     */
    kUpperLimitCount = 2,

    /**
     * The renderer will keep appended captions at an upper limit of duration, in milliseconds.
     */
    kUpperLimitDuration = 3,
};

/**
 * Enums for reporting rendering status
 *
 * See @Renderer::Render()
 */
enum class RenderStatus {
    kError = 0,
    kNoImage = 1,
    kGotImage = 2,
    kGotImageUnchanged = 3,
};

/**
 * Structure for holding rendered caption images
 */
struct RenderResult {
    int64_t pts = 0;             ///< PTS of rendered caption
    int64_t duration = 0;        ///< duration of rendered caption, may be DURATION_INDEFINITE
    std::vector<Image> images;
};

/**
 * ARIB STD-B24 caption renderer
 */
class Renderer {
public:
    /**
     * A context is needed for constructing the Renderer.
     *
     * The context shouldn't be destructed before any other object constructed from the context has been destructed.
     */
    ARIBCC_API explicit Renderer(Context& context);
    ARIBCC_API ~Renderer();
    ARIBCC_API Renderer(Renderer&&) noexcept;
    ARIBCC_API Renderer& operator=(Renderer&&) noexcept;
public:
    /**
     * Initialize function must be called before calling any other member functions.
     *
     * @param caption_type        Indicate caption type (kCaption / kSuperimpose)
     * @param font_provider_type  Indicate @FontProviderType. Use kAuto in most cases.
     * @param text_renderer_type  Indicate @TextRendererType. Use kAuto in most cases.
     * @return true on success
     */
    ARIBCC_API bool Initialize(CaptionType caption_type = CaptionType::kCaption,
                               FontProviderType font_provider_type = FontProviderType::kAuto,
                               TextRendererType text_renderer_type = TextRendererType::kAuto);

    /**
     * Indicate stroke width for stroke text, in dots (relative)
     * @param dots must >= 0.0f
     */
    ARIBCC_API void SetStrokeWidth(float dots);

    /**
     * Indicate whether render replaced DRCS characters as Unicode characters
     * @param replace default as true
     */
    ARIBCC_API void SetReplaceDRCS(bool replace);

    /**
     * Indicate whether always render stroke text for all characters regardless of the indication by CharStyle
     * @param force_stroke default as false
     */
    ARIBCC_API void SetForceStrokeText(bool force_stroke);

    /**
     * Indicate whether ignore rendering for ruby-like (furigana) characters
     * @param force_no_ruby default as false
     */
    ARIBCC_API void SetForceNoRuby(bool force_no_ruby);

    /**
     * Indicate whether ignore background color rendering
     * @param force_no_background default as false
     */
    ARIBCC_API void SetForceNoBackground(bool force_no_background);

    /**
     * Merge rendered region images into one big image on Render() call.
     * @param merge default as false
     */
    ARIBCC_API void SetMergeRegionImages(bool merge);

    /**
     * Indicate font families (an array of font family names) for default usage
     *
     * Default font family will be used only if captions' language is unknown (iso6392_language_code == 0).
     * Indicate force_default = true to force use these fonts always.
     *
     * The renderer contains an auto-fallback mechanism among indicated font families.
     * The renderer also contains predefined font indications for Windows / macOS / Linux / Android.
     *
     * @param font_family    Array of font family names
     * @param force_default  Whether force use these font families for all languages
     * @return true on success
     */
    ARIBCC_API bool SetDefaultFontFamily(const std::vector<std::string>& font_family, bool force_default);

    /**
     * Indicate font families (an array of font family names) for specific language
     *
     * The renderer contains an auto-fallback mechanism among indicated font families.
     * The renderer also contains predefined font indications for Windows / macOS / Linux / Android.
     *
     * @param language_code ISO639-2 Language Code, e.g. ThreeCC("jpn")
     * @param font_family   Array of font family names
     * @return true on success
     */
    ARIBCC_API bool SetLanguageSpecificFontFamily(uint32_t language_code, const std::vector<std::string>& font_family);

    /**
     * Set whether to replace MSZ (Middle Size, halfwidth) characters with halfwidth glyphs if the font supports it
     *
     * The font should be Adobe-Japan1 compliant.
     * If the font has Std, Pr5, Pr6, or Pro in the name, it is likely to be compliant.
     * Fonts such as IBM Plex Sans JP, Morisawa BIZ UDGothic, Morisawa BIZ UDMincho,
     * Yu Gothic, Yu Mincho, and Meiryo are also supported.
     * It is only effective when using the FreeType or DirectWrite renderer,
     * and is enabled by default for these renderers.
     *
     * @param replace If true is specified, Captions that use halfwidth kana, halfwidth symbol and
     *                halfwidth alphanumeric characters will look better.
     */
    ARIBCC_API void SetReplaceMSZHalfWidthGlyph(bool replace);

    /**
     * Set the renderer frame size in pixels, include margins. This function must be called before any @Render() call.
     *
     * Usually rendered images will be inside this frame area, unless negative margin values are specified.
     *
     * @param frame_width   must be >= 0
     * @param frame_height  must be >= 0
     * @return true on success
     */
    ARIBCC_API bool SetFrameSize(int frame_width, int frame_height);

    /**
     * Set the frame margins in pixels. This function must be called after calling to @SetFrameSize().
     * Call to this function is optional.
     *
     * Each value specifics the distance from the video rectangle to the renderer frame.
     * Positive margin value means there will be free space between renderer frame and video area,
     * while negative margin value lets renderer frame (visible area) inside the video, i.e. the video is cropped.
     *
     * If negative margin value has been indicated, rendered images may outside the renderer frame.
     *
     * @return true on success
     */
    ARIBCC_API bool SetMargins(int top, int bottom, int left, int right);

    /**
     * Set storage policy for renderer's internal caption storage
     *
     * @param policy       See @CaptionStoragePolicy
     * @param upper_limit  Optional parameter, but must has a value for kUpperLimitCount & kUpperLimitDuration
     */
    ARIBCC_API void SetStoragePolicy(CaptionStoragePolicy policy, std::optional<size_t> upper_limit = std::nullopt);

    /**
     * Append a caption into renderer's internal storage for subsequent rendering
     *
     * If a caption with same PTS already exists in the storage, it will be replaced by the new one.
     *
     * @param caption Caption's const reference
     * @return true on success
     */
    ARIBCC_API bool AppendCaption(const Caption& caption);

    /**
     * Append a caption into renderer's internal storage for subsequent rendering
     *
     * If a caption with same PTS already exists in the storage, it will be replaced by the new one.
     *
     * @param caption Caption's Rvalue reference, use std::move()
     * @return true on success
     */
    ARIBCC_API bool AppendCaption(Caption&& caption);

    /**
     * Retrieve expected RenderStatus at specific PTS, rather than actually do rendering.
     *
     * Useful for detecting whether will got an identical image that is unchanged from the previous rendering.
     *
     * @param pts    Presentation timestamp, in milliseconds
     * @return       kError / kNoImage / kGotImage / kGotImageUnchanged
     *               kGotImageUnchanged means a Render() call at this PTS will return an image identical to the previous
     */
    ARIBCC_API RenderStatus TryRender(int64_t pts);

    /**
     * Render caption at specific PTS
     *
     * This function queries a caption from the specified PTS and render it into images if exists.
     *
     * @param pts         Presentation timestamp, in milliseconds
     * @param out_result  Write back parameter for passing rendered images, will be empty if status is kError / kNoImage
     *
     * @return            kGotImage / kGotImageUnchanged if rendered images provided
     *                    kGotImageUnchanged means this batch of images is completely identical to the previous call
     */
    ARIBCC_API RenderStatus Render(int64_t pts, RenderResult& out_result);

    /**
     * Clear caption storage inside the renderer. Will evict all the appended captions.
     *
     * Call this function if the stream has been seeked, or met non-monotonic PTS.
     */
    ARIBCC_API void Flush();
public:
    Renderer(const Renderer&) = delete;
    Renderer& operator=(const Renderer&) = delete;
private:
    std::unique_ptr<internal::RendererImpl> pimpl_;
};

}  // namespace aribcaption

#endif  // ARIBCAPTION_RENDERER_HPP
