#pragma once

#include <mbgl/gfx/index_buffer.hpp>
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/layout/symbol_feature.hpp>
#include <mbgl/layout/symbol_instance.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/programs/collision_box_program.hpp>
#include <mbgl/programs/segment.hpp>
#include <mbgl/programs/symbol_program.hpp>
#include <mbgl/renderer/bucket.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <mbgl/text/glyph_range.hpp>
#include <mbgl/text/placement.hpp>

#include <vector>

namespace mbgl {

class CrossTileSymbolLayerIndex;

class PlacedSymbol {
public:
    PlacedSymbol(Point<float> anchorPoint_,
                 std::size_t segment_,
                 float lowerSize_,
                 float upperSize_,
                 std::array<float, 2> lineOffset_,
                 WritingModeType writingModes_,
                 GeometryCoordinates line_,
                 std::vector<float> tileDistances_,
                 std::optional<size_t> placedIconIndex_ = std::nullopt)
        : anchorPoint(anchorPoint_),
          segment(segment_),
          lowerSize(lowerSize_),
          upperSize(upperSize_),
          lineOffset(lineOffset_),
          writingModes(writingModes_),
          line(std::move(line_)),
          tileDistances(std::move(tileDistances_)),
          hidden(false),
          vertexStartIndex(0),
          placedIconIndex(std::move(placedIconIndex_)) {}
    Point<float> anchorPoint;
    std::size_t segment;
    float lowerSize;
    float upperSize;
    std::array<float, 2> lineOffset;
    WritingModeType writingModes;
    GeometryCoordinates line;
    std::vector<float> tileDistances;
    std::vector<float> glyphOffsets;
    bool hidden;
    size_t vertexStartIndex;
    // The crossTileID is only filled/used on the foreground for variable text anchors
    uint32_t crossTileID = 0u;
    // The placedOrientation is only used when symbol layer's property is set to support
    // placement for orientation variants.
    std::optional<style::TextWritingModeType> placedOrientation;
    float angle = 0;

    // Reference to placed icon, only applicable for text symbols.
    std::optional<size_t> placedIconIndex;
};

class SymbolBucket final : public Bucket {
public:
    SymbolBucket(Immutable<style::SymbolLayoutProperties::PossiblyEvaluated>,
                 const std::map<std::string, Immutable<style::LayerProperties>>&,
                 const style::PropertyValue<float>& textSize,
                 const style::PropertyValue<float>& iconSize,
                 float zoom,
                 bool iconsNeedLinear,
                 bool sortFeaturesByY,
                 std::string bucketName_,
                 const std::vector<SymbolInstance>&&,
                 const std::vector<SortKeyRange>&&,
                 float tilePixelRatio,
                 bool allowVerticalPlacement,
                 std::vector<style::TextWritingModeType> placementModes,
                 bool iconsInText);
    ~SymbolBucket() override;

    void upload(gfx::UploadPass&) override;
    bool hasData() const override;
    std::pair<uint32_t, bool> registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const RenderTile&) override;
    void place(Placement&, const BucketPlacementData&, std::set<uint32_t>&) override;
    void updateVertices(
        const Placement&, bool updateOpacities, const TransformState&, const RenderTile&, std::set<uint32_t>&) override;
    bool hasTextData() const;
    bool hasIconData() const;
    bool hasSdfIconData() const;
    bool hasIconCollisionBoxData() const;
    bool hasIconCollisionCircleData() const;
    bool hasTextCollisionBoxData() const;
    bool hasTextCollisionCircleData() const;
    bool hasFormatSectionOverrides() const;

    void sortFeatures(float angle);
    // Returns references to the `symbolInstances` items, sorted by viewport Y.
    SymbolInstanceReferences getSortedSymbols(float angle) const;
    // Returns references to the `symbolInstances` items, which belong to the `sortKeyRange` range;
    // returns references to all the symbols if |sortKeyRange| is `std::nullopt`.
    SymbolInstanceReferences getSymbols(const std::optional<SortKeyRange>& sortKeyRange = std::nullopt) const;

    Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout;
    const std::string bucketLeaderID;
    float sortedAngle = std::numeric_limits<float>::max();

    // Flags
    const bool iconsNeedLinear : 1;
    const bool sortFeaturesByY : 1;
    bool staticUploaded : 1;
    bool placementChangesUploaded : 1;
    bool dynamicUploaded : 1;
    bool sortUploaded : 1;
    bool iconsInText : 1;
    // Set and used by placement.
    mutable bool justReloaded : 1;
    bool hasVariablePlacement : 1;
    bool hasUninitializedSymbols : 1;

    std::vector<SymbolInstance> symbolInstances;
    const std::vector<SortKeyRange> sortKeyRanges;

    struct PaintProperties {
        SymbolIconProgram::Binders iconBinders;
        SymbolSDFTextProgram::Binders textBinders;
    };
    std::map<std::string, PaintProperties> paintProperties;

    std::unique_ptr<SymbolSizeBinder> textSizeBinder;

    struct Buffer {
        gfx::VertexVector<SymbolLayoutVertex> vertices;
        gfx::VertexVector<gfx::Vertex<SymbolDynamicLayoutAttributes>> dynamicVertices;
        gfx::VertexVector<gfx::Vertex<SymbolOpacityAttributes>> opacityVertices;
        gfx::IndexVector<gfx::Triangles> triangles;
        SegmentVector<SymbolTextAttributes> segments;
        std::vector<PlacedSymbol> placedSymbols;

        std::optional<gfx::VertexBuffer<SymbolLayoutVertex>> vertexBuffer;
        std::optional<gfx::VertexBuffer<gfx::Vertex<SymbolDynamicLayoutAttributes>>> dynamicVertexBuffer;
        std::optional<gfx::VertexBuffer<gfx::Vertex<SymbolOpacityAttributes>>> opacityVertexBuffer;
        std::optional<gfx::IndexBuffer> indexBuffer;
    } text;

    std::unique_ptr<SymbolSizeBinder> iconSizeBinder;

    Buffer icon;
    Buffer sdfIcon;

    struct CollisionBuffer {
        gfx::VertexVector<gfx::Vertex<CollisionBoxLayoutAttributes>> vertices;
        gfx::VertexVector<gfx::Vertex<CollisionBoxDynamicAttributes>> dynamicVertices;
        SegmentVector<CollisionBoxProgram::AttributeList> segments;

        std::optional<gfx::VertexBuffer<gfx::Vertex<CollisionBoxLayoutAttributes>>> vertexBuffer;
        std::optional<gfx::VertexBuffer<gfx::Vertex<CollisionBoxDynamicAttributes>>> dynamicVertexBuffer;
    };

    struct CollisionBoxBuffer : public CollisionBuffer {
        gfx::IndexVector<gfx::Lines> lines;
        std::optional<gfx::IndexBuffer> indexBuffer;
    };
    std::unique_ptr<CollisionBoxBuffer> iconCollisionBox;
    std::unique_ptr<CollisionBoxBuffer> textCollisionBox;

    CollisionBoxBuffer& getOrCreateIconCollisionBox() {
        if (!iconCollisionBox) iconCollisionBox = std::make_unique<CollisionBoxBuffer>();
        return *iconCollisionBox;
    }

    CollisionBoxBuffer& getOrCreateTextCollisionBox() {
        if (!textCollisionBox) textCollisionBox = std::make_unique<CollisionBoxBuffer>();
        return *textCollisionBox;
    }

    struct CollisionCircleBuffer : public CollisionBuffer {
        gfx::IndexVector<gfx::Triangles> triangles;
        std::optional<gfx::IndexBuffer> indexBuffer;
    };
    std::unique_ptr<CollisionCircleBuffer> iconCollisionCircle;
    std::unique_ptr<CollisionCircleBuffer> textCollisionCircle;

    CollisionCircleBuffer& getOrCreateIconCollisionCircleBuffer() {
        if (!iconCollisionCircle) iconCollisionCircle = std::make_unique<CollisionCircleBuffer>();
        return *iconCollisionCircle;
    }

    CollisionCircleBuffer& getOrCreateTextCollisionCircleBuffer() {
        if (!textCollisionCircle) textCollisionCircle = std::make_unique<CollisionCircleBuffer>();
        return *textCollisionCircle;
    }

    const float tilePixelRatio;
    uint32_t bucketInstanceId;
    const bool allowVerticalPlacement;
    const std::vector<style::TextWritingModeType> placementModes;
    mutable std::optional<bool> hasFormatSectionOverrides_;

    FeatureSortOrder featureSortOrder;
};

} // namespace mbgl
