/*
 *      Copyright (C) 2005-2012 Team Kodi
 *      https://kodi.tv
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************
 *  This file is a modified version from Team MediaPortal's
 *  TsReader DirectShow filter
 *  MediaPortal is a GPL'ed HTPC-Application
 *  Copyright (C) 2005-2012 Team MediaPortal
 *  http://www.team-mediaportal.com
 *
 * Changes compared to Team MediaPortal's version:
 * - Code cleanup for PVR addon usage
 * - Code refactoring for cross platform usage
 */

#ifdef LIVE555

#include "os-dependent.h"
#include "MemoryBuffer.h"
#include <kodi/General.h> //for kodi::Log
#include "TSDebug.h"

#include <thread>

#define MAX_MEMORY_BUFFER_SIZE (1024L*1024L*12L)

CMemoryBuffer::CMemoryBuffer(void)
{
  m_bRunning = true;
  m_BytesInBuffer = 0;
}

CMemoryBuffer::~CMemoryBuffer()
{
  Clear();
}

bool CMemoryBuffer::IsRunning()
{
  return m_bRunning;
}

void CMemoryBuffer::Clear()
{
  std::lock_guard<std::mutex> BufferLock(m_BufferLock);

  for (auto& item : m_Array)
  {
    if (item)
    {
      if (item->data)
        delete[] item->data;
      delete item;
    }
  }

  m_Array.clear();
  m_BytesInBuffer = 0;
}

size_t CMemoryBuffer::Size()
{
  return m_BytesInBuffer;
}

void CMemoryBuffer::Run(bool onOff)
{
  TSDEBUG(ADDON_LOG_DEBUG, "memorybuffer: run:%d %d", onOff, m_bRunning);

  if (m_bRunning != onOff)
  {
    m_bRunning = onOff;

    if (m_bRunning == false)
    {
      Clear();
    }
  }

  TSDEBUG(ADDON_LOG_DEBUG, "memorybuffer: running:%d", onOff);
}

size_t CMemoryBuffer::ReadFromBuffer(unsigned char *pbData, size_t lDataLength)
{
  if (pbData == NULL || lDataLength <= 0 || !m_bRunning)
    return 0;

  while (m_BytesInBuffer < lDataLength)
  {
    if (!m_bRunning)
      return 0;

    std::unique_lock<std::mutex> lock(m_BufferLock);
    m_condition.wait_for(lock, std::chrono::milliseconds(5000));

    if (!m_bRunning)
      return 0;
  }

  // kodi::Log(ADDON_LOG_DEBUG, "get..%d/%d", lDataLength, m_BytesInBuffer);
  size_t bytesWritten = 0;
  std::lock_guard<std::mutex> BufferLock(m_BufferLock);

  while (bytesWritten < lDataLength)
  {
    if (m_Array.empty())
    {
      kodi::Log(ADDON_LOG_DEBUG, "memorybuffer: read:empty buffer\n");
      return 0;
    }
    BufferItem *item = m_Array.at(0);

    if (NULL == item)
    {
      kodi::Log(ADDON_LOG_DEBUG, "memorybuffer: item==NULL\n");
      return 0;
    }

    size_t copyLength;
    if ( (item->nDataLength - item->nOffset) < (lDataLength - bytesWritten) )
    {
      copyLength = item->nDataLength - item->nOffset;
    }
    else
    {
      copyLength = lDataLength - bytesWritten;
    }

    if (NULL == item->data)
    {
      kodi::Log(ADDON_LOG_DEBUG, "memorybuffer: item->data==NULL\n");
      return 0;
    }

    memcpy(&pbData[bytesWritten], &item->data[item->nOffset], copyLength);

    bytesWritten += copyLength;
    item->nOffset += copyLength;
    m_BytesInBuffer -= copyLength;

    if (item->nOffset >= item->nDataLength)
    {
      m_Array.erase(m_Array.begin());
      if (item->data)
        delete[] item->data;
      delete item;
    }
  }
  return bytesWritten;
}

long CMemoryBuffer::PutBuffer(unsigned char *pbData, size_t lDataLength)
{
  if (lDataLength == 0 || pbData == NULL) return E_FAIL;

  BufferItem* item = new BufferItem();
  item->nOffset = 0;
  item->nDataLength = lDataLength;
  item->data = new unsigned char[lDataLength];
  memcpy(item->data, pbData, lDataLength);
  bool sleep = false;
  {
    std::lock_guard<std::mutex> BufferLock(m_BufferLock);
    m_Array.push_back(item);
    m_BytesInBuffer += lDataLength;

    //kodi::Log(ADDON_LOG_DEBUG, "add..%d/%d",lDataLength,m_BytesInBuffer);
    while (m_BytesInBuffer > MAX_MEMORY_BUFFER_SIZE)
    {
      sleep = true;
      kodi::Log(ADDON_LOG_DEBUG, "memorybuffer:put full buffer (%d)", (unsigned long) m_BytesInBuffer);
      BufferItem *item2 = m_Array.at(0);
      size_t copyLength = item2->nDataLength - item2->nOffset;

      m_BytesInBuffer -= copyLength;
      m_Array.erase(m_Array.begin());
      if (item2->data)
        delete[] item2->data;
      delete item2;
    }
    if (m_BytesInBuffer > 0)
    {
      m_condition.notify_one();
    }
  }

  if (sleep)
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
  return S_OK;
}

#endif //LIVE555
