text: #include "drape/batcher_helpers.hpp"
  #include "drape/attribute_provider.hpp"
  #include "drape/cpu_buffer.hpp"
  #include "drape/index_storage.hpp"
  
  #include "base/assert.hpp"
  #include "base/math.hpp"
  
  #include <algorithm>
  
  namespace dp
  {
  namespace
  {
  template <typename T>
  T CyclicClamp(T const x, T const xmin, T const xmax)
  {
    if (x > xmax)
      return xmin;
    if (x < xmin)
      return xmax;
    return x;
  }
  
  bool IsEnoughMemory(uint32_t avVertex, uint32_t existVertex, uint32_t avIndex, uint32_t existIndex)
  {
    return avVertex >= existVertex && avIndex >= existIndex;
  }
  
  template<typename TGenerator>
  void GenerateIndices(void * indexStorage, uint32_t count, uint32_t startIndex)
  {
    GenerateIndices<TGenerator>(indexStorage, count, TGenerator(startIndex));
  }
  
  template<typename TGenerator>
  void GenerateIndices(void * indexStorage, uint32_t count, TGenerator const & generator)
  {
    if (dp::IndexStorage::IsSupported32bit())
    {
      auto pIndexStorage = static_cast<uint32_t *>(indexStorage);
      std::generate(pIndexStorage, pIndexStorage + count, generator);
    }
    else
    {
      auto pIndexStorage = static_cast<uint16_t *>(indexStorage);
      std::generate(pIndexStorage, pIndexStorage + count, generator);
    }
  }
  
  class IndexGenerator
  {
  public:
    explicit IndexGenerator(uint32_t startIndex)
      : m_startIndex(startIndex)
      , m_counter(0)
      , m_minStripCounter(0) {}
  
  protected:
    uint32_t GetCounter() { return m_counter++; }
  
    void ResetCounter()
    {
      m_counter = 0;
      m_minStripCounter = 0;
    }
  
    uint32_t const m_startIndex;
  
    int16_t GetCWNormalizer()
    {
      int16_t tmp = m_minStripCounter;
      m_minStripCounter = static_cast<uint8_t>(CyclicClamp(m_minStripCounter + 1, 0, 5));
      switch (tmp)
      {
        case 4: return 1;
        case 5: return -1;
        default: return 0;
      }
    }
  
  private:
    uint32_t m_counter;
    uint8_t m_minStripCounter;
  };
  
  class ListIndexGenerator : public IndexGenerator
  {
  public:
    explicit ListIndexGenerator(uint32_t startIndex)
      : IndexGenerator(startIndex)
    {}
    uint32_t operator()() { return m_startIndex + GetCounter(); }
  };
  
  class StripIndexGenerator : public IndexGenerator
  {
  public:
    explicit StripIndexGenerator(uint32_t startIndex)
      : IndexGenerator(startIndex)
    {}
  
    uint32_t operator()()
    {
      uint32_t const counter = GetCounter();
      return m_startIndex + counter - 2 * (counter / 3) + GetCWNormalizer();
    }
  };
  
  class LineStripIndexGenerator : public IndexGenerator
  {
  public:
    explicit LineStripIndexGenerator(uint32_t startIndex)
      : IndexGenerator(startIndex)
    {}
  
    uint32_t operator()()
    {
      uint32_t const counter = GetCounter();
      uint32_t const result = m_startIndex + m_counter;
      if (counter % 2 == 0)
        m_counter++;
  
      return result;
    }
  
  private:
    uint32_t m_counter = 0;
  };
  
  class LineRawIndexGenerator : public IndexGenerator
  {
  public:
    LineRawIndexGenerator(uint32_t startIndex, std::vector<int> const & indices)
      : IndexGenerator(startIndex)
      , m_indices(indices)
    {}
  
    uint32_t operator()()
    {
      uint32_t const counter = GetCounter();
      ASSERT_LESS(counter, m_indices.size(), ());
      return static_cast<uint32_t>(m_startIndex + m_indices[counter]);
    }
  
  private:
    std::vector<int> const & m_indices;
  };
  
  class FanIndexGenerator : public IndexGenerator
  {
  public:
    explicit FanIndexGenerator(uint32_t startIndex)
      : IndexGenerator(startIndex)
    {}
  
    uint32_t operator()()
    {
      uint32_t const counter = GetCounter();
      if ((counter % 3) == 0)
        return m_startIndex;
      return m_startIndex + counter - 2 * (counter / 3);
    }
  };
  
  class ListOfStripGenerator : public IndexGenerator
  {
  public:
    ListOfStripGenerator(uint32_t startIndex, uint32_t vertexStride, uint32_t indexPerStrip)
      : IndexGenerator(startIndex)
      , m_vertexStride(vertexStride)
      , m_indexPerStrip(indexPerStrip)
      , m_base(0)
    {}
  
    uint32_t operator()()
    {
      uint32_t const counter = GetCounter();
      uint32_t const result = m_startIndex + m_base + counter - 2 * (counter / 3) + GetCWNormalizer();
      if (counter + 1 == m_indexPerStrip)
      {
        m_base += m_vertexStride;
        ResetCounter();
      }
  
      return result;
    }
  
  private:
    uint32_t m_vertexStride;
    uint32_t m_indexPerStrip;
    uint32_t m_base;
  };
  }  // namespace
  
  UniversalBatch::UniversalBatch(BatchCallbacks & callbacks, uint8_t minVerticesCount,
                                 uint8_t minIndicesCount)
    : m_callbacks(callbacks)
    , m_canDivideStreams(true)
    , m_minVerticesCount(minVerticesCount)
    , m_minIndicesCount(minIndicesCount)
  {}
  
  void UniversalBatch::SetCanDivideStreams(bool canDivide)
  {
    m_canDivideStreams = canDivide;
  }
  
  bool UniversalBatch::CanDivideStreams() const
  {
    return m_canDivideStreams;
  }
  
  void UniversalBatch::SetVertexStride(uint8_t vertexStride)
  {
    m_vertexStride = vertexStride;
  }
  
  void UniversalBatch::FlushData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams,
                                 uint32_t vertexCount) const
  {
    for (uint8_t i = 0; i < streams->GetStreamCount(); ++i)
      FlushData(context, streams->GetBindingInfo(i), streams->GetRawPointer(i), vertexCount);
  }
  
  void UniversalBatch::FlushData(ref_ptr<GraphicsContext> context, BindingInfo const & info,
                                 void const * data, uint32_t elementCount) const
  {
    m_callbacks.FlushData(context, info, data, elementCount);
  }
  
  void * UniversalBatch::GetIndexStorage(uint32_t indexCount, uint32_t & startIndex)
  {
    return m_callbacks.GetIndexStorage(indexCount, startIndex);
  }
  
  void UniversalBatch::SubmitIndices(ref_ptr<GraphicsContext> context)
  {
    m_callbacks.SubmitIndices(context);
  }
  
  uint32_t UniversalBatch::GetAvailableVertexCount() const
  {
    return m_callbacks.GetAvailableVertexCount();
  }
  
  uint32_t UniversalBatch::GetAvailableIndexCount() const
  {
    return m_callbacks.GetAvailableIndexCount();
  }
  
  void UniversalBatch::ChangeBuffer(ref_ptr<GraphicsContext> context) const
  {
    return m_callbacks.ChangeBuffer(context);
  }
  
  uint8_t UniversalBatch::GetVertexStride() const
  {
    return m_vertexStride;
  }
  
  bool UniversalBatch::IsBufferFilled(uint32_t availableVerticesCount,
                                      uint32_t availableIndicesCount) const
  {
    return availableVerticesCount < m_minVerticesCount ||
           availableIndicesCount < m_minIndicesCount;
  }
  
  
  TriangleListBatch::TriangleListBatch(BatchCallbacks & callbacks)
    : TBase(callbacks, 3 /* minVerticesCount */, 3 /* minIndicesCount */)
  {}
  
  void TriangleListBatch::BatchData(ref_ptr<GraphicsContext> context,
                                    ref_ptr<AttributeProvider> streams)
  {
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t avVertex = GetAvailableVertexCount();
      uint32_t avIndex  = GetAvailableIndexCount();
      uint32_t vertexCount = streams->GetVertexCount();
  
      if (CanDivideStreams())
      {
        vertexCount = std::min(vertexCount, avVertex);
        vertexCount = std::min(vertexCount, avIndex);
        ASSERT(vertexCount >= 3, ());
        vertexCount -= vertexCount % 3;
      }
      else if (!IsEnoughMemory(avVertex, vertexCount, avIndex, vertexCount))
      {
        ChangeBuffer(context);
        avVertex = GetAvailableVertexCount();
        avIndex  = GetAvailableIndexCount();
        ASSERT(IsEnoughMemory(avVertex, vertexCount, avIndex, vertexCount), ());
        ASSERT(vertexCount % 3 == 0, ());
      }
  
      uint32_t startIndex = 0;
      void * indicesStorage = GetIndexStorage(vertexCount, startIndex);
      GenerateIndices<ListIndexGenerator>(indicesStorage, vertexCount, startIndex);
      SubmitIndices(context);
  
      FlushData(context, streams, vertexCount);
      streams->Advance(vertexCount);
    }
  }
  
  LineStripBatch::LineStripBatch(BatchCallbacks & callbacks)
    : TBase(callbacks, 2 /* minVerticesCount */, 2 /* minIndicesCount */)
  {}
  
  void LineStripBatch::BatchData(ref_ptr<GraphicsContext> context,
                                 ref_ptr<AttributeProvider> streams)
  {
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t avVertex = GetAvailableVertexCount();
      uint32_t avIndex = GetAvailableIndexCount();
      uint32_t vertexCount = streams->GetVertexCount();
      ASSERT_GREATER_OR_EQUAL(vertexCount, 2, ());
      uint32_t indexCount = (vertexCount - 1) * 2;
  
      if (!IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount))
      {
        ChangeBuffer(context);
        avVertex = GetAvailableVertexCount();
        avIndex = GetAvailableIndexCount();
        ASSERT(IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount), ());
      }
  
      uint32_t startIndex = 0;
      void * indicesStorage = GetIndexStorage(indexCount, startIndex);
      GenerateIndices<LineStripIndexGenerator>(indicesStorage, indexCount, startIndex);
      SubmitIndices(context);
  
      FlushData(context, streams, vertexCount);
      streams->Advance(vertexCount);
    }
  }
  
  LineRawBatch::LineRawBatch(BatchCallbacks & callbacks, std::vector<int> const & indices)
    : TBase(callbacks, 2 /* minVerticesCount */, 2 /* minIndicesCount */)
    , m_indices(indices)
  {}
  
  void LineRawBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
  {
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t avVertex = GetAvailableVertexCount();
      uint32_t avIndex = GetAvailableIndexCount();
      uint32_t vertexCount = streams->GetVertexCount();
      CHECK_GREATER_OR_EQUAL(vertexCount, 2, ());
      auto const indexCount = static_cast<uint32_t>(m_indices.size());
  
      if (!IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount))
      {
        ChangeBuffer(context);
        avVertex = GetAvailableVertexCount();
        avIndex = GetAvailableIndexCount();
        CHECK(IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount), ());
      }
  
      uint32_t startIndex = 0;
      void * indicesStorage = GetIndexStorage(indexCount, startIndex);
      LineRawIndexGenerator generator(startIndex, m_indices);
      GenerateIndices(indicesStorage, indexCount, generator);
      SubmitIndices(context);
  
      FlushData(context, streams, vertexCount);
      streams->Advance(vertexCount);
    }
  }
  
  FanStripHelper::FanStripHelper(BatchCallbacks & callbacks)
    : TBase(callbacks, 3 /* minVerticesCount */, 3 /* minIndicesCount */)
    , m_isFullUploaded(false)
  {}
  
  uint32_t FanStripHelper::BatchIndexes(ref_ptr<GraphicsContext> context, uint32_t vertexCount)
  {
    uint32_t avVertex = GetAvailableVertexCount();
    uint32_t avIndex  = GetAvailableIndexCount();
  
    uint32_t batchVertexCount = 0;
    uint32_t batchIndexCount = 0;
    CalcBatchPortion(vertexCount, avVertex, avIndex, batchVertexCount, batchIndexCount);
  
    if (!IsFullUploaded() && !CanDivideStreams())
    {
      ChangeBuffer(context);
      avVertex = GetAvailableVertexCount();
      avIndex  = GetAvailableIndexCount();
      CalcBatchPortion(vertexCount, avVertex, avIndex, batchVertexCount, batchIndexCount);
      ASSERT(IsFullUploaded(), ());
    }
  
    uint32_t startIndex = 0;
    void * pIndexStorage = GetIndexStorage(batchIndexCount, startIndex);
    GenerateIndexes(pIndexStorage, batchIndexCount, startIndex);
    SubmitIndices(context);
  
    return batchVertexCount;
  }
  
  void FanStripHelper::CalcBatchPortion(uint32_t vertexCount, uint32_t avVertex, uint32_t avIndex,
                                        uint32_t & batchVertexCount, uint32_t & batchIndexCount)
  {
    uint32_t const indexCount = VtoICount(vertexCount);
    batchVertexCount = vertexCount;
    batchIndexCount = indexCount;
    m_isFullUploaded = true;
  
    if (vertexCount > avVertex || indexCount > avIndex)
    {
      uint32_t alignedAvVertex = AlignVCount(avVertex);
      uint32_t alignedAvIndex = AlignICount(avIndex);
      uint32_t indexCountForAvailableVertexCount = VtoICount(alignedAvVertex);
      if (indexCountForAvailableVertexCount <= alignedAvIndex)
      {
        batchVertexCount = alignedAvVertex;
        batchIndexCount = indexCountForAvailableVertexCount;
      }
      else
      {
        batchIndexCount = alignedAvIndex;
        batchVertexCount = ItoVCount(batchIndexCount);
      }
      m_isFullUploaded = false;
    }
  }
  
  bool FanStripHelper::IsFullUploaded() const
  {
    return m_isFullUploaded;
  }
  
  uint32_t FanStripHelper::VtoICount(uint32_t vCount) const
  {
    return 3 * (vCount - 2);
  }
  
  uint32_t FanStripHelper::ItoVCount(uint32_t iCount) const
  {
    return iCount / 3 + 2;
  }
  
  uint32_t FanStripHelper::AlignVCount(uint32_t vCount) const
  {
    return vCount;
  }
  
  uint32_t FanStripHelper::AlignICount(uint32_t iCount) const
  {
    return iCount - iCount % 3;
  }
  
  TriangleStripBatch::TriangleStripBatch(BatchCallbacks & callbacks)
   : TBase(callbacks)
  {}
  
  void TriangleStripBatch::BatchData(ref_ptr<GraphicsContext> context,
                                     ref_ptr<AttributeProvider> streams)
  {
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t const batchVertexCount = BatchIndexes(context, streams->GetVertexCount());
      FlushData(context, streams, batchVertexCount);
  
      uint32_t const advanceCount = IsFullUploaded() ? batchVertexCount : (batchVertexCount - 2);
      streams->Advance(advanceCount);
  
    }
  }
  
  void TriangleStripBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
  {
    GenerateIndices<StripIndexGenerator>(indexStorage, count, startIndex);
  }
  
  TriangleFanBatch::TriangleFanBatch(BatchCallbacks & callbacks) : TBase(callbacks) {}
  
  /*
   * What happens here
   *
   * We try to pack TriangleFan on GPU indexed like triangle list.
   * If we have enough memory in VertexArrayBuffer to store all data from params, we just copy it
   *
   * If we have not enough memory we broke data on parts.
   * On first iteration we create CPUBuffer for each separate atribute
   * in params and copy to it first vertex of fan. This vertex will be need
   * when we will upload second part of data.
   *
   * Than we copy vertex data on GPU as much as we can and move params cursor on
   * "uploaded vertex count" - 1. This last vertex will be used for uploading next part of data
   *
   * On second iteration we need upload first vertex of fan that stored in cpuBuffers and than upload
   * second part of data. But to avoid 2 separate call of glBufferSubData we at first do a copy of
   * data from params to cpuBuffer and than copy continuous block of memory from cpuBuffer
   */
  void TriangleFanBatch::BatchData(ref_ptr<GraphicsContext> context,
                                   ref_ptr<AttributeProvider> streams)
  {
    std::vector<CPUBuffer> cpuBuffers;
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t vertexCount = streams->GetVertexCount();
      uint32_t batchVertexCount = BatchIndexes(context, vertexCount);
  
      if (!cpuBuffers.empty())
      {
        // if cpuBuffers not empty than on previous iteration we not move data on gpu
        // and in cpuBuffers stored first vertex of fan.
        // To avoid two separate call of glBufferSubData
        // (for first vertex and for next part of data)
        // we at first copy next part of data into
        // cpuBuffers, and than copy it from cpuBuffers to GPU
        for (size_t i = 0; i < streams->GetStreamCount(); ++i)
        {
          CPUBuffer & cpuBuffer = cpuBuffers[i];
          ASSERT(cpuBuffer.GetCurrentElementNumber() == 1, ());
          cpuBuffer.UploadData(streams->GetRawPointer(i), batchVertexCount);
  
          // now in cpuBuffer we have correct "fan" created from second part of data
          // first vertex of cpuBuffer if the first vertex of params, second vertex is
          // the last vertex of previous uploaded data. We copy this data on GPU.
          FlushData(context, streams->GetBindingInfo(i), cpuBuffer.Data(), batchVertexCount + 1);
        }
  
        uint32_t advanceCount = batchVertexCount;
        if (!IsFullUploaded())
        {
          // not all data was moved on gpu and last vertex of fan
          // will need on second iteration
          advanceCount -= 1;
        }
  
        streams->Advance(advanceCount);
      }
      else // if m_cpuBuffer empty than it's first iteration
      {
        if (IsFullUploaded())
        {
          // We can upload all input data as one peace. For upload we need only one iteration
          FlushData(context, streams, batchVertexCount);
          streams->Advance(batchVertexCount);
        }
        else
        {
          // for each stream we must create CPU buffer.
          // Copy first vertex of fan into cpuBuffer for next iterations
          // Than move first part of data on GPU
          cpuBuffers.reserve(streams->GetStreamCount());
          for (size_t i = 0; i < streams->GetStreamCount(); ++i)
          {
            const BindingInfo & binding = streams->GetBindingInfo(i);
            const void * rawDataPointer = streams->GetRawPointer(i);
            FlushData(context, binding, rawDataPointer, batchVertexCount);
  
            // "(vertexCount + 1) - batchVertexCount" we allocate CPUBuffer on all remaining data
            // + first vertex of fan, that must be duplicate in the next buffer
            // + last vertex of currently uploaded data.
            cpuBuffers.emplace_back(binding.GetElementSize(), (vertexCount + 2) - batchVertexCount);
            CPUBuffer & cpuBuffer = cpuBuffers.back();
            cpuBuffer.UploadData(rawDataPointer, 1);
  
            // Move cpu buffer cursor on second element of buffer.
            // On next iteration first vertex of fan will be also available
            cpuBuffer.Seek(1);
          }
  
          // advance on uploadVertexCount - 1 to copy last vertex also into next VAO with
          // first vertex of data from CPUBuffers
          streams->Advance(batchVertexCount - 1);
        }
      }
    }
  }
  
  void TriangleFanBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
  {
    GenerateIndices<FanIndexGenerator>(indexStorage, count, startIndex);
  }
  
  TriangleListOfStripBatch::TriangleListOfStripBatch(BatchCallbacks & callbacks)
   : TBase(callbacks)
  {}
  
  void TriangleListOfStripBatch::BatchData(ref_ptr<GraphicsContext> context,
                                           ref_ptr<AttributeProvider> streams)
  {
    while (streams->IsDataExists())
    {
      if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
        ChangeBuffer(context);
  
      uint32_t const batchVertexCount = BatchIndexes(context, streams->GetVertexCount());
      FlushData(context, streams, batchVertexCount);
      streams->Advance(batchVertexCount);
    }
  }
  
  bool TriangleListOfStripBatch::IsBufferFilled(uint32_t availableVerticesCount,
                                                uint32_t availableIndicesCount) const
  {
    uint8_t const vertexStride = GetVertexStride();
    ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
  
    uint32_t const indicesPerStride = TBase::VtoICount(vertexStride);
    return availableVerticesCount < vertexStride || availableIndicesCount < indicesPerStride;
  }
  
  uint32_t TriangleListOfStripBatch::VtoICount(uint32_t vCount) const
  {
    uint8_t const vertexStride = GetVertexStride();
    ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
    ASSERT_EQUAL(vCount % vertexStride, 0, ());
  
    uint32_t const striptCount = vCount / vertexStride;
    return striptCount * TBase::VtoICount(vertexStride);
  }
  
  uint32_t TriangleListOfStripBatch::ItoVCount(uint32_t iCount) const
  {
    uint8_t const vertexStride = GetVertexStride();
    ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
    ASSERT_EQUAL(iCount % 3, 0, ());
  
    return vertexStride * iCount / TBase::VtoICount(vertexStride);
  }
  
  uint32_t TriangleListOfStripBatch::AlignVCount(uint32_t vCount) const
  {
    return vCount - vCount % GetVertexStride();
  }
  
  uint32_t TriangleListOfStripBatch::AlignICount(uint32_t iCount) const
  {
    uint8_t const vertexStride = GetVertexStride();
    ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
  
    uint32_t const indicesPerStride = TBase::VtoICount(vertexStride);
    return iCount - iCount % indicesPerStride;
  }
  
  void TriangleListOfStripBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
  {
    uint8_t const vertexStride = GetVertexStride();
    GenerateIndices(indexStorage, count, ListOfStripGenerator(startIndex, vertexStride, VtoICount(vertexStride)));
  }
  }  // namespace dp
  