/****************************************************************************
** libmatroska : parse Matroska files, see http://www.matroska.org/
**
** <file/class description>
**
** Copyright (C) 2002-2010 Steve Lhomme.  All rights reserved.
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
** See http://www.gnu.org/licenses/lgpl-2.1.html for LGPL licensing information.**
** Contact license@matroska.org if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

/*!
  \file
  \version \$Id: KaxCues.cpp 1265 2007-01-14 17:20:35Z mosu $
  \author Steve Lhomme     <robux4 @ users.sf.net>
*/
#include <cassert>

#include "matroska/KaxCues.h"
#include "matroska/KaxCuesData.h"
#include "matroska/KaxContexts.h"
#include "ebml/EbmlStream.h"
#include "matroska/KaxDefines.h"
#include "matroska/KaxSemantic.h"

// sub elements
START_LIBMATROSKA_NAMESPACE

KaxCues::~KaxCues()
{
  assert(myTempReferences.empty()); // otherwise that means you have added references and forgot to set the position
}
/* deprecated and wrong
bool KaxCues::AddBlockGroup(const KaxBlockGroup & BlockRef)
{
  // Do not add the element if it's already present.
  std::vector<const KaxBlockBlob *>::iterator ListIdx;
  KaxBlockBlob *BlockReference = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE);
  BlockReference->SetBlockGroup(*const_cast<KaxBlockGroup*>(&BlockRef));

  for (ListIdx = myTempReferences.begin(); ListIdx != myTempReferences.end(); ListIdx++)
    if (&(KaxBlockGroup&)*ListIdx == &BlockRef) {
      delete BlockReference;
      return true;
    }

  myTempReferences.push_back(BlockReference);
  return true;
}
*/
bool KaxCues::AddBlockBlob(const KaxBlockBlob & BlockReference)
{
  // Do not add the element if it's already present.
  bool present = std::any_of(myTempReferences.begin(), myTempReferences.end(),
    [&](const KaxBlockBlob *myTempReference) { return myTempReference == &BlockReference; });
  if (present) {
    return true;
  }

  myTempReferences.push_back(&BlockReference);
  return true;
}

void KaxCues::PositionSet(const KaxBlockBlob & BlockReference)
{
  // look for the element in the temporary references
  auto it = std::find_if(myTempReferences.begin(), myTempReferences.end(),
    [&](const KaxBlockBlob *myTempReference){ return myTempReference == &BlockReference; });

  if (it != myTempReferences.end()) {
    // found, now add the element to the entry list
    auto & NewPoint = AddNewChild<KaxCuePoint>(*this);
    NewPoint.PositionSet(BlockReference, GlobalTimecodeScale());
    myTempReferences.erase(it);
  }
}

void KaxCues::PositionSet(const KaxBlockGroup & BlockRef)
{
  // look for the element in the temporary references
  auto it = std::find_if(myTempReferences.begin(), myTempReferences.end(),
    [&](const KaxBlockBlob *myTempReference)
      { const KaxInternalBlock &refTmp = *myTempReference;
        return refTmp.GlobalTimecode() == BlockRef.GlobalTimecode()
            && refTmp.TrackNum() == BlockRef.TrackNumber(); });

  if(it != myTempReferences.end()) {
    // found, now add the element to the entry list
    auto & NewPoint = AddNewChild<KaxCuePoint>(*this);
    NewPoint.PositionSet(**it, GlobalTimecodeScale());
    myTempReferences.erase(it);
  }
}

/*!
  \warning Assume that the list has been sorted (Sort())
*/
const KaxCuePoint * KaxCues::GetTimecodePoint(uint64 aTimecode) const
{
  uint64 TimecodeToLocate = aTimecode / GlobalTimecodeScale();
  const KaxCuePoint * aPointPrev = nullptr;
  uint64 aPrevTime = 0;
  uint64 aNextTime = EBML_PRETTYLONGINT(0xFFFFFFFFFFFF);

  EBML_MASTER_CONST_ITERATOR Itr;
  for (Itr = begin(); Itr != end(); ++Itr) {
    if (EbmlId(*(*Itr)) == EBML_ID(KaxCuePoint)) {
      auto tmp = static_cast<const KaxCuePoint *>(*Itr);
      // check the tile
      auto aTime = static_cast<const KaxCueTime *>(tmp->FindFirstElt(EBML_INFO(KaxCueTime)));
      if (aTime != nullptr) {
        auto _Time = uint64(*aTime);
        if (_Time > aPrevTime && _Time < TimecodeToLocate) {
          aPrevTime = _Time;
          aPointPrev = tmp;
        }
        if (_Time < aNextTime && _Time > TimecodeToLocate) {
          aNextTime= _Time;
        }
      }
    }
  }

  return aPointPrev;
}

uint64 KaxCues::GetTimecodePosition(uint64 aTimecode) const
{
  const KaxCuePoint * aPoint = GetTimecodePoint(aTimecode);
  if (aPoint == nullptr)
    return 0;

  const KaxCueTrackPositions * aTrack = aPoint->GetSeekPosition();
  if (aTrack == nullptr)
    return 0;

  return aTrack->ClusterPosition();
}

END_LIBMATROSKA_NAMESPACE
