// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

#include <dwrite.h>
#include <d2d1.h>

#include <wrl.h>
#include <wrl/client.h>
#include <wrl/implements.h>

#include "BoxDrawingEffect.h"
#include "../inc/Cluster.hpp"

namespace Microsoft::Console::Render
{
    class CustomTextLayout : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextAnalysisSource, IDWriteTextAnalysisSink>
    {
    public:
        // Based on the Windows 7 SDK sample at https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomLayout

        CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory,
                         gsl::not_null<IDWriteTextAnalyzer1*> const analyzer,
                         gsl::not_null<IDWriteTextFormat*> const format,
                         gsl::not_null<IDWriteFontFace1*> const font,
                         size_t const width,
                         IBoxDrawingEffect* const boxEffect);

        [[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const gsl::span<const ::Microsoft::Console::Render::Cluster> clusters);

        [[nodiscard]] HRESULT STDMETHODCALLTYPE Reset() noexcept;

        [[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns);

        // IDWriteTextLayout methods (but we don't actually want to implement them all, so just this one matching the existing interface)
        [[nodiscard]] HRESULT STDMETHODCALLTYPE Draw(_In_opt_ void* clientDrawingContext,
                                                     _In_ IDWriteTextRenderer* renderer,
                                                     FLOAT originX,
                                                     FLOAT originY) noexcept;

        // IDWriteTextAnalysisSource methods
        [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition,
                                                                  _Outptr_result_buffer_(*textLength) WCHAR const** textString,
                                                                  _Out_ UINT32* textLength) override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition,
                                                                      _Outptr_result_buffer_(*textLength) WCHAR const** textString,
                                                                      _Out_ UINT32* textLength) noexcept override;
        [[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition,
                                                              _Out_ UINT32* textLength,
                                                              _Outptr_result_z_ WCHAR const** localeName) noexcept override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition,
                                                                      _Out_ UINT32* textLength,
                                                                      _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept override;

        // IDWriteTextAnalysisSink methods
        [[nodiscard]] HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition,
                                                                  UINT32 textLength,
                                                                  _In_ DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition,
                                                                   UINT32 textLength,
                                                                   _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition,
                                                             UINT32 textLength,
                                                             UINT8 explicitLevel,
                                                             UINT8 resolvedLevel) override;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition,
                                                                      UINT32 textLength,
                                                                      _In_ IDWriteNumberSubstitution* numberSubstitution) override;

        [[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept;

    protected:
        // A single contiguous run of characters containing the same analysis results.
        struct Run
        {
            Run() noexcept :
                textStart(),
                textLength(),
                glyphStart(),
                glyphCount(),
                bidiLevel(),
                script(),
                isNumberSubstituted(),
                isSideways(),
                fontFace{ nullptr },
                fontScale{ 1.0 },
                drawingEffect{ nullptr }
            {
            }

            UINT32 textStart; // starting text position of this run
            UINT32 textLength; // number of contiguous code units covered
            UINT32 glyphStart; // starting glyph in the glyphs array
            UINT32 glyphCount; // number of glyphs associated with this run of text
            DWRITE_SCRIPT_ANALYSIS script;
            UINT8 bidiLevel;
            bool isNumberSubstituted;
            bool isSideways;
            ::Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
            FLOAT fontScale;
            ::Microsoft::WRL::ComPtr<IUnknown> drawingEffect;

            inline bool ContainsTextPosition(UINT32 desiredTextPosition) const noexcept
            {
                return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength;
            }

            inline bool operator==(UINT32 desiredTextPosition) const noexcept
            {
                // Search by text position using std::find
                return ContainsTextPosition(desiredTextPosition);
            }
        };

        // Single text analysis run, which points to the next run.
        struct LinkedRun : Run
        {
            LinkedRun() noexcept :
                nextRunIndex(0)
            {
            }

            UINT32 nextRunIndex; // index of next run
        };

        [[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength);
        [[nodiscard]] LinkedRun& _GetCurrentRun();
        void _SetCurrentRun(const UINT32 textPosition);
        void _SplitCurrentRun(const UINT32 splitPosition);
        void _OrderRuns();

        [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength);
        [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFont(UINT32 textPosition, UINT32 textLength, IDWriteFont* const font, FLOAT const scale);

        [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null<IDWriteTextAnalysisSource*> const source, UINT32 textPosition, UINT32 textLength);
        [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength);

        [[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept;
        [[nodiscard]] HRESULT _AnalyzeRuns() noexcept;
        [[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept;
        [[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept;
        [[nodiscard]] HRESULT _CorrectGlyphRuns() noexcept;
        [[nodiscard]] HRESULT _CorrectGlyphRun(const UINT32 runIndex) noexcept;
        [[nodiscard]] HRESULT STDMETHODCALLTYPE _CorrectBoxDrawing() noexcept;
        [[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext,
                                             IDWriteTextRenderer* renderer,
                                             const D2D_POINT_2F origin) noexcept;
        [[nodiscard]] HRESULT _DrawGlyphRun(_In_opt_ void* clientDrawingContext,
                                            gsl::not_null<IDWriteTextRenderer*> renderer,
                                            D2D_POINT_2F& mutableOrigin,
                                            const Run& run) noexcept;

        [[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept;

    private:
        const ::Microsoft::WRL::ComPtr<IDWriteFactory1> _factory;

        // DirectWrite analyzer
        const ::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _analyzer;

        // DirectWrite text format
        const ::Microsoft::WRL::ComPtr<IDWriteTextFormat> _format;

        // DirectWrite font face
        const ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _font;

        // Box drawing effect
        const ::Microsoft::WRL::ComPtr<IBoxDrawingEffect> _boxDrawingEffect;

        // The text we're analyzing and processing into a layout
        std::wstring _text;
        std::vector<UINT16> _textClusterColumns;
        size_t _width;

        // Properties of the text that might be relevant.
        std::wstring _localeName;
        ::Microsoft::WRL::ComPtr<IDWriteNumberSubstitution> _numberSubstitution;
        DWRITE_READING_DIRECTION _readingDirection;

        // Text analysis results
        std::vector<LinkedRun> _runs;
        std::vector<DWRITE_LINE_BREAKPOINT> _breakpoints;

        // Text analysis interim status variable (to assist the Analyzer Sink in operations involving _runs)
        UINT32 _runIndex;

        // Glyph shaping results

        // Whether the entire text is determined to be simple and does not require full script shaping.
        bool _isEntireTextSimple;

        std::vector<DWRITE_GLYPH_OFFSET> _glyphOffsets;

        // Clusters are complicated. They're in respect to each individual run.
        // The offsets listed here are in respect to the _text string, but from the beginning index of
        // each run.
        // That means if we have two runs, we will see 0 1 2 3 4 0 1 2 3 4 5 6 7... in this clusters count.
        std::vector<UINT16> _glyphClusters;

        // This appears to be the index of the glyph inside each font.
        std::vector<UINT16> _glyphIndices;

        // This is for calculating glyph advances when the entire text is simple.
        std::vector<INT32> _glyphDesignUnitAdvances;

        std::vector<float> _glyphAdvances;

        struct ScaleCorrection
        {
            UINT32 textIndex;
            UINT32 textLength;
            float scale;
        };

        // These are used to further break the runs apart and adjust the font size so glyphs fit inside the cells.
        std::vector<ScaleCorrection> _glyphScaleCorrections;

#ifdef UNIT_TESTING
    public:
        CustomTextLayout() = default;

        friend class CustomTextLayoutTests;
#endif
    };
}
