#pragma once

#include "drape/texture.hpp"

#include <atomic>

namespace dp
{
template <typename TIndexer, typename TResourceKey, Texture::ResourceType TResourceType>
class DynamicTexture : public Texture
{
  using Base = Texture;

public:
  ~DynamicTexture() override { ASSERT(m_indexer == nullptr, ()); }

  ref_ptr<ResourceInfo> FindResource(Key const & key, bool & newResource) override
  {
    ASSERT(m_indexer != nullptr, ());
    if (key.GetType() != TResourceType)
      return nullptr;

    return m_indexer->MapResource(static_cast<TResourceKey const &>(key), newResource);
  }

  void Create(ref_ptr<GraphicsContext> context, Params const & params) override
  {
    ASSERT(Base::IsPowerOfTwo(params.m_width, params.m_height), (params.m_width, params.m_height));
    Base::Create(context, params);
  }

  void Create(ref_ptr<GraphicsContext> context, Params const & params, ref_ptr<void> data) override
  {
    ASSERT(Base::IsPowerOfTwo(params.m_width, params.m_height), (params.m_width, params.m_height));
    Base::Create(context, params, data);
  }

  void UpdateState(ref_ptr<GraphicsContext> context) override
  {
    // Create texture before first uploading.
    if (!m_isInitialized)
    {
      // Passing nullptr allocates GPU memory without uploading data (no large zero-fill buffer needed).
      Create(context, m_params, nullptr);
      m_isInitialized = true;
    }

    ASSERT(m_indexer != nullptr, ());
    Bind(context);
    m_indexer->UploadResources(context, make_ref(this));
  }

  TextureFormat GetFormat() const override { return m_params.m_format; }

  uint32_t GetWidth() const override { return m_params.m_width; }

  uint32_t GetHeight() const override { return m_params.m_height; }

  float GetS(uint32_t x) const override { return static_cast<float>(x) / m_params.m_width; }

  float GetT(uint32_t y) const override { return static_cast<float>(y) / m_params.m_height; }

  uint32_t GetID() const override { return m_isInitialized ? Texture::GetID() : 0; }

  void Bind(ref_ptr<dp::GraphicsContext> context) const override
  {
    if (m_isInitialized)
      Texture::Bind(context);
  }

  void SetFilter(TextureFilter filter) override
  {
    if (m_isInitialized)
      Texture::SetFilter(filter);
  }

protected:
  DynamicTexture() : m_isInitialized(false) {}

  struct DynamicTextureParams
  {
    m2::PointU m_size;
    dp::TextureFormat m_format = dp::TextureFormat::Unspecified;
    TextureFilter m_filter = dp::TextureFilter::Nearest;
    bool m_usePixelBuffer = false;
  };

  void Init(ref_ptr<HWTextureAllocator> allocator, ref_ptr<TIndexer> indexer, DynamicTextureParams const & params)
  {
    m_indexer = indexer;
    m_params.m_allocator = allocator;
    m_params.m_width = params.m_size.x;
    m_params.m_height = params.m_size.y;
    m_params.m_format = params.m_format;
    m_params.m_filter = params.m_filter;
    m_params.m_usePixelBuffer = params.m_usePixelBuffer;
    m_params.m_isMutable = true;
  }

  void Reset() { m_indexer = nullptr; }

  ref_ptr<TIndexer> m_indexer;
  Texture::Params m_params;
  std::atomic<bool> m_isInitialized;
};
}  // namespace dp
