#include <mbgl/layout/symbol_instance.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <utility>

namespace mbgl {

using namespace style;

namespace {

const Shaping& getAnyShaping(const ShapedTextOrientations& shapedTextOrientations) {
    if (shapedTextOrientations.right) return shapedTextOrientations.right;
    if (shapedTextOrientations.center) return shapedTextOrientations.center;
    if (shapedTextOrientations.left) return shapedTextOrientations.left;
    if (shapedTextOrientations.vertical) return shapedTextOrientations.vertical;
    return shapedTextOrientations.horizontal;
}

} // namespace

SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
                                                   const ShapedTextOrientations& shapedTextOrientations,
                                                   const std::optional<PositionedIcon>& shapedIcon,
                                                   const std::optional<PositionedIcon>& verticallyShapedIcon,
                                                   const style::SymbolLayoutProperties::Evaluated& layout,
                                                   const style::SymbolPlacementType textPlacement,
                                                   const std::array<float, 2>& textOffset,
                                                   const ImageMap& imageMap,
                                                   float iconRotation,
                                                   SymbolContent iconType,
                                                   bool hasIconTextFit,
                                                   bool allowVerticalPlacement)
    : line(std::move(line_)) {
    // Create the quads used for rendering the icon and glyphs.
    if (shapedIcon) {
        iconQuads = getIconQuads(*shapedIcon, iconRotation, iconType, hasIconTextFit);
        if (verticallyShapedIcon) {
            verticalIconQuads = getIconQuads(*verticallyShapedIcon, iconRotation, iconType, hasIconTextFit);
        }
    }

    bool singleLineInitialized = false;
    const auto initHorizontalGlyphQuads = [&](SymbolQuads& quads, const Shaping& shaping) {
        if (!shapedTextOrientations.singleLine) {
            quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
            return;
        }
        if (!singleLineInitialized) {
            rightJustifiedGlyphQuads = getGlyphQuads(
                shaping, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
            singleLineInitialized = true;
        }
    };

    if (shapedTextOrientations.right) {
        initHorizontalGlyphQuads(rightJustifiedGlyphQuads, shapedTextOrientations.right);
    }

    if (shapedTextOrientations.center) {
        initHorizontalGlyphQuads(centerJustifiedGlyphQuads, shapedTextOrientations.center);
    }

    if (shapedTextOrientations.left) {
        initHorizontalGlyphQuads(leftJustifiedGlyphQuads, shapedTextOrientations.left);
    }

    if (shapedTextOrientations.vertical) {
        verticalGlyphQuads = getGlyphQuads(
            shapedTextOrientations.vertical, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
    }
}

bool SymbolInstanceSharedData::empty() const {
    return rightJustifiedGlyphQuads.empty() && centerJustifiedGlyphQuads.empty() && leftJustifiedGlyphQuads.empty() &&
           verticalGlyphQuads.empty();
}

SymbolInstance::SymbolInstance(Anchor& anchor_,
                               std::shared_ptr<SymbolInstanceSharedData> sharedData_,
                               const ShapedTextOrientations& shapedTextOrientations,
                               const std::optional<PositionedIcon>& shapedIcon,
                               const std::optional<PositionedIcon>& verticallyShapedIcon,
                               const float textBoxScale_,
                               const float textPadding,
                               const SymbolPlacementType textPlacement,
                               const std::array<float, 2>& textOffset_,
                               const float iconBoxScale,
                               const float iconPadding,
                               const std::array<float, 2>& iconOffset_,
                               const IndexedSubfeature& indexedFeature,
                               const std::size_t layoutFeatureIndex_,
                               const std::size_t dataFeatureIndex_,
                               std::u16string key_,
                               const float overscaling,
                               const float iconRotation,
                               const float textRotation,
                               const std::array<float, 2>& variableTextOffset_,
                               bool allowVerticalPlacement,
                               const SymbolContent iconType)
    : sharedData(std::move(sharedData_)),
      anchor(anchor_),
      symbolContent(iconType),
      // Create the collision features that will be used to check whether this symbol instance can be placed
      // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature
      textCollisionFeature(sharedData->line,
                           anchor,
                           getAnyShaping(shapedTextOrientations),
                           textBoxScale_,
                           textPadding,
                           textPlacement,
                           indexedFeature,
                           overscaling,
                           textRotation),
      iconCollisionFeature(
          sharedData->line, anchor, shapedIcon, iconBoxScale, iconPadding, indexedFeature, iconRotation),
      writingModes(WritingModeType::None),
      layoutFeatureIndex(layoutFeatureIndex_),
      dataFeatureIndex(dataFeatureIndex_),
      textOffset(textOffset_),
      iconOffset(iconOffset_),
      key(std::move(key_)),
      textBoxScale(textBoxScale_),
      variableTextOffset(variableTextOffset_),
      singleLine(shapedTextOrientations.singleLine) {
    // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap
    if (!sharedData->empty()) symbolContent |= SymbolContent::Text;
    if (allowVerticalPlacement && shapedTextOrientations.vertical) {
        const float verticalPointLabelAngle = 90.0f;
        verticalTextCollisionFeature = CollisionFeature(line(),
                                                        anchor,
                                                        shapedTextOrientations.vertical,
                                                        textBoxScale_,
                                                        textPadding,
                                                        textPlacement,
                                                        indexedFeature,
                                                        overscaling,
                                                        textRotation + verticalPointLabelAngle);
        if (verticallyShapedIcon) {
            verticalIconCollisionFeature = CollisionFeature(sharedData->line,
                                                            anchor,
                                                            verticallyShapedIcon,
                                                            iconBoxScale,
                                                            iconPadding,
                                                            indexedFeature,
                                                            iconRotation + verticalPointLabelAngle);
        }
    }

    rightJustifiedGlyphQuadsSize = sharedData->rightJustifiedGlyphQuads.size();
    centerJustifiedGlyphQuadsSize = sharedData->centerJustifiedGlyphQuads.size();
    leftJustifiedGlyphQuadsSize = sharedData->leftJustifiedGlyphQuads.size();
    verticalGlyphQuadsSize = sharedData->verticalGlyphQuads.size();
    iconQuadsSize = sharedData->iconQuads ? sharedData->iconQuads->size() : 0;

    if (rightJustifiedGlyphQuadsSize || centerJustifiedGlyphQuadsSize || leftJustifiedGlyphQuadsSize) {
        writingModes |= WritingModeType::Horizontal;
    }

    if (verticalGlyphQuadsSize) {
        writingModes |= WritingModeType::Vertical;
    }
}

const GeometryCoordinates& SymbolInstance::line() const {
    assert(sharedData);
    return sharedData->line;
}

const SymbolQuads& SymbolInstance::rightJustifiedGlyphQuads() const {
    assert(sharedData);
    return sharedData->rightJustifiedGlyphQuads;
}

const SymbolQuads& SymbolInstance::leftJustifiedGlyphQuads() const {
    assert(sharedData);
    return sharedData->leftJustifiedGlyphQuads;
}

const SymbolQuads& SymbolInstance::centerJustifiedGlyphQuads() const {
    assert(sharedData);
    return sharedData->centerJustifiedGlyphQuads;
}

const SymbolQuads& SymbolInstance::verticalGlyphQuads() const {
    assert(sharedData);
    return sharedData->verticalGlyphQuads;
}

const std::optional<SymbolQuads>& SymbolInstance::iconQuads() const {
    assert(sharedData);
    return sharedData->iconQuads;
}

bool SymbolInstance::hasText() const {
    return static_cast<bool>(symbolContent & SymbolContent::Text);
}

bool SymbolInstance::hasIcon() const {
    return static_cast<bool>(symbolContent & SymbolContent::IconRGBA) || hasSdfIcon();
}

bool SymbolInstance::hasSdfIcon() const {
    return static_cast<bool>(symbolContent & SymbolContent::IconSDF);
}

const std::optional<SymbolQuads>& SymbolInstance::verticalIconQuads() const {
    assert(sharedData);
    return sharedData->verticalIconQuads;
}

void SymbolInstance::releaseSharedData() {
    sharedData.reset();
}

std::optional<size_t> SymbolInstance::getDefaultHorizontalPlacedTextIndex() const {
    if (placedRightTextIndex) return placedRightTextIndex;
    if (placedCenterTextIndex) return placedCenterTextIndex;
    if (placedLeftTextIndex) return placedLeftTextIndex;
    return std::nullopt;
}
} // namespace mbgl
