#pragma once

#include "drape/dynamic_texture.hpp"
#include "drape/glyph_manager.hpp"
#include "drape/pointers.hpp"

#include <map>
#include <mutex>
#include <utility>  // std::tie
#include <vector>

namespace dp
{
class GlyphPacker
{
public:
  explicit GlyphPacker(m2::PointU const & size);

  bool PackGlyph(uint32_t width, uint32_t height, m2::RectU & rect);
  bool CanBePacked(uint32_t glyphsCount, uint32_t width, uint32_t height) const;
  m2::RectF MapTextureCoords(m2::RectU const & pixelRect) const;
  bool IsFull() const;
  m2::PointU const & GetSize() const { return m_size; }

private:
  m2::PointU m_size = m2::PointU(0, 0);
  m2::PointU m_cursor = m2::PointU(0, 0);
  uint32_t m_yStep = 0;
  bool m_isFull = false;
};

class GlyphKey : public GlyphFontAndId, public Texture::Key
{
public:
  Texture::ResourceType GetType() const override { return Texture::ResourceType::Glyph; }
};

// TODO(AB): Make Texture::ResourceInfo non-abstract and use it here directly.
class GlyphInfo : public Texture::ResourceInfo
{
public:
  explicit GlyphInfo(m2::RectF const & texRect)
    : ResourceInfo(texRect)
  {}

  Texture::ResourceType GetType() const override { return Texture::ResourceType::Glyph; }
};

class GlyphIndex
{
public:
  GlyphIndex(m2::PointU const & size, ref_ptr<GlyphManager> mng);
  ~GlyphIndex();

  // This function can return nullptr.
  ref_ptr<Texture::ResourceInfo> MapResource(GlyphFontAndId const & key, bool & newResource);
  std::vector<ref_ptr<Texture::ResourceInfo>> MapResources(TGlyphs const & keys, bool & hasNewResources);
  void UploadResources(ref_ptr<dp::GraphicsContext> context, ref_ptr<Texture> texture);

  bool CanBeGlyphPacked(uint32_t glyphsCount) const;

  // ONLY for unit-tests. DO NOT use this function anywhere else.
  size_t GetPendingNodesCount();

private:
  GlyphPacker m_packer;
  ref_ptr<GlyphManager> m_mng;

  using ResourceMapping = std::map<GlyphFontAndId, GlyphInfo>;
  using PendingNode = std::pair<m2::RectU, Glyph>;
  using PendingNodes = std::vector<PendingNode>;

  ResourceMapping m_index;
  PendingNodes m_pendingNodes;
  std::mutex m_mutex;
};

class FontTexture : public DynamicTexture<GlyphIndex, GlyphKey, Texture::ResourceType::Glyph>
{
public:
  FontTexture(m2::PointU const & size, ref_ptr<GlyphManager> glyphMng, ref_ptr<HWTextureAllocator> allocator)
    : m_index(size, glyphMng)
  {
    DynamicTextureParams const params{size, TextureFormat::Alpha, TextureFilter::Linear, true /* m_usePixelBuffer */};
    Init(allocator, make_ref(&m_index), params);
  }

  ~FontTexture() override { Reset(); }

  ref_ptr<ResourceInfo> MapResource(GlyphFontAndId const & key, bool & hasNewResources) const
  {
    ASSERT(m_indexer != nullptr, ());
    return m_indexer->MapResource(key, hasNewResources);
  }

  bool HasEnoughSpace(uint32_t newKeysCount) const override
  {
    return m_index.CanBeGlyphPacked(newKeysCount);
  }

private:
  GlyphIndex m_index;
};
}  // namespace dp