/****************************************************************************
** libmatroska : parse Matroska files, see http://www.matroska.org/
**
** <file/class description>
**
** Copyright (C) 2002-2003 Steve Lhomme.  All rights reserved.
**
** This file is part of libmatroska.
**
** 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$
    \author Steve Lhomme     <robux4 @ users.sf.net>
*/
//#include "StdInclude.h"
#include "matroska/FileKax.h"
#include <ebml/EbmlVersion.h>
#include <ebml/EbmlContexts.h>
//#include "Cluster.h"
//#include "Track.h"
//#include "Block.h"
//#include "Frame.h"
//#include "Version.h"

namespace libmatroska {

//typedef Track *TrackID;

FileMatroska::FileMatroska(IOCallback & output)
  :myFile(output)
#ifdef OLD
  ,myCurrReadBlock(NULL)
  ,myMinClusterSize(5*1024)      // 5KB is the min size of a cluster
  ,myMaxClusterSize(2*1024*1024) // 2MB is the max size of a cluster
  ,myCurrReadBlockTrack(0)
  ,myCurrWriteCluster(2*1024*1024) // myMaxClusterSize
  ,myCurrReadCluster(NULL)
  ,myReadBlockNumber(0)
#endif // OLD
{
#ifdef OLD
  myStreamInfo.MainHeaderSize = TypeHeader::default_size() +
    ActualHeader::default_size() +
    ExtendedInfo::default_size() +
    ContentInfo::default_size();
  myStreamInfo.TrackEntrySize = Track::default_size();
  myStreamInfo.BlockHeadSize = BLOCK_HEADER_SIZE;
  myStreamInfo.ClusterHeadSize = CLUSTER_HEADER_SIZE;
  myStreamInfo.ClusterFootSize = CLUSTER_TRAILER_SIZE;
#endif // OLD
}

/* FileMatroska::~FileMatroska()
{
  //  if (myCurrCluster != NULL)
  //    throw 0; // there are some data left to write
  //  if (myCurrReadCluster != NULL || myCurrReadBlock != NULL)
  //    throw 0; // there are some data left to write
} */

#ifdef OLD
void FileMatroska::SetMaxClusterSize(const uint32 value)
{
  myMaxClusterSize = value;
  myCurrWriteCluster.setMaxSize(value);
}

void FileMatroska::Close(const uint32 aTimeLength)
{
  Flush();

  // get the file size
  myFile.setFilePointer(0,seek_end);
  myMainHeader.type_SetSize(myFile.getFilePointer());

  // rewrite the header at the beginning
  myFile.setFilePointer(0,seek_beginning);

  // get the Track-entry size
  uint32 track_entries_size = 0;
  for (auto&& myTrack : myTracks) {
    track_entries_size += myTrack->default_size();
  }

  myStreamInfo.TrackEntriesSize = track_entries_size;
  myStreamInfo.TimeLength = aTimeLength;
  myMainHeader.Render(myFile, myStreamInfo);

  for (auto&& track : myTracks) {
    delete track;
  }
}

/*!
  \warning after rendering the head, some parameters are locked
*/
filepos_t FileMatroska::RenderHead(const std::string & aEncoderApp)
{
  try {
    uint32 track_entries_size = 0;
    for (auto&& myTrack : myTracks) {
      track_entries_size += myTrack->default_size();
    }

    std::string aStr = LIB_NAME;
    aStr += " ";
    aStr += VERSION;
    myStreamInfo.EncoderLib = aStr;

    myStreamInfo.EncoderApp = aEncoderApp;

    myStreamInfo.TrackEntryPosition = 0 + myStreamInfo.MainHeaderSize;
    myStreamInfo.TrackEntriesSize = myTracks.size() * myStreamInfo.TrackEntrySize;

    myStreamInfo.CodecEntryPosition = myStreamInfo.MainHeaderSize + myStreamInfo.TrackEntriesSize;
    myStreamInfo.CodecEntrySize = 4;
    for (auto&& myTrack : myTracks) {
      myStreamInfo.CodecEntrySize += myTrack->CodecSize();
    }

    // Main Header
    filepos_t result = myMainHeader.Render(myFile, myStreamInfo);

    // Track Entries
    for (i=0; i<myTracks.size(); i++) {
      myTracks[i]->RenderEntry(myFile, i+1);
    }
    myStreamInfo.ClusterPosition = myStreamInfo.CodecEntryPosition + myStreamInfo.CodecEntrySize;

    // Codec Header
    result = CodecHeader::Render(myFile, myTracks);

    return result;
  }
  catch (exception & Ex) {
    throw Ex;
  }
}

/*!
  \return 0 if the track was not created, or a valid track number
*/
Track * FileMatroska::CreateTrack(const track_type aType)
{
  myTracks.emplace_back(aType);
  return myTracks.back();
}

/*Track *FileMatroska::findTrack(Track * aTrack) const {
  for (size_t i=0; i<myTracks.size(); i++) {
  if (myTracks[i] == aTrack)
  return myTracks[i];
  }

  return NULL;
  }*/

void FileMatroska::track_SetName(Track * aTrack, const std::string & aName)
{
  if (IsMyTrack(aTrack)) {
    aTrack->SetName(aName);
  }
}

void FileMatroska::track_SetLaced(Track * aTrack, bool bLaced)
{
  if (IsMyTrack(aTrack)) {
    aTrack->SetLaced(bLaced);
  }
}

bool FileMatroska::AddFrame(Track * aTrack, const uint32 aTimecode, const binary *aFrame, const uint32 aFrameSize,
                            bool aKeyFrame, bool aBFrame)
{
  try {
    // make sure we know that track
    if (IsMyTrack(aTrack)) {
      // pass the cluster to the track
      // handle the creation of a new cluster if needed
      if (aTrack->AddFrame(aTimecode, aFrame, aFrameSize, aKeyFrame, aBFrame)) {
        while (!aTrack->SerialiseBlock(myCurrWriteCluster)) {
          /// \todo handle errors
          uint32 aNbBlock;
          myStreamInfo.ClusterSize += myCurrWriteCluster.Render(myFile, aNbBlock);
          myStreamInfo.NumberBlock += aNbBlock;
          myCurrWriteCluster.Flush();
        }
      }
      return true;
    }
    return false;
  }
  catch (exception & Ex) {
    throw Ex;
  }
}

void FileMatroska::Flush()
{
  uint32 aNbBlock;
  myStreamInfo.ClusterSize += myCurrWriteCluster.Render(myFile,aNbBlock);
  myStreamInfo.NumberBlock += aNbBlock;
}

uint32 FileMatroska::ReadHead()
{
  try {
    uint32 result = myMainHeader.Read(myFile, myStreamInfo);

    return result;
  }
  catch (exception & Ex) {
    throw Ex;
  }
}

uint32 FileMatroska::ReadTracks()
{
  try {
    uint32 result = 0;

    // seek to the start of the Track Entries
    myFile.setFilePointer(myStreamInfo.TrackEntryPosition);
    // get the number of Track Entries
    uint8 TrackNumber = myStreamInfo.TrackEntriesSize / myStreamInfo.TrackEntrySize;
    // read all the Track Entries
    myTracks.clear();
    for (uint8 TrackIdx = 0; TrackIdx<TrackNumber; TrackIdx ++) {
      Track * tmpTrack = Track::ReadEntry(myFile, TrackIdx+1, myStreamInfo);
      if (tmpTrack == NULL)
        throw 0;

      myTracks.push_back(tmpTrack);
    }

    return result;
  }
  catch (exception & Ex) {
    throw Ex;
  }
}

uint32 FileMatroska::ReadCodec()
{
  try {
    // seek to the start of the Track Entries
    myFile.setFilePointer(myStreamInfo.CodecEntryPosition);

    uint32 result = CodecHeader::Read(myFile, myTracks);

    return result;
  }
  catch (exception & Ex) {
    throw Ex;
  }
}

inline bool FileMatroska::IsMyTrack(const Track * aTrack) const
{
  if (aTrack == 0)
    throw 0;

  return std::any_of(myTracks.begin(), myTracks.end(), aTrack);
}

void FileMatroska::SelectReadingTrack(Track * aTrack, bool select)
{
  if (IsMyTrack(aTrack)) {
    // here we have the right track
    // check if it's not already selected
    for (auto&& num : mySelectedTracks) {
      if (num == aTrack->TrackNumber())
        break;
    }

    if (select && j == mySelectedTracks.end())
      mySelectedTracks.push_back(aTrack->TrackNumber());
    else if (!select && j != mySelectedTracks.end())
      mySelectedTracks.erase(j);

    std::sort(mySelectedTracks.begin(), mySelectedTracks.end());
  }
}

inline bool FileMatroska::IsReadingTrack(const uint8 aTrackNumber) const
{
  return std::any_of(mySelectedTracks.begin(), mySelectedTracks.end(), [=](int track){ return track >= aTrackNumber; })
}

//

void FileMatroska::Track_GetInfo(const Track * aTrack, TrackInfo & aTrackInfo) const
{
  if (IsMyTrack(aTrack)) {
    aTrack->GetInfo(aTrackInfo);
  }
}

// Audio related getters/setters

void FileMatroska::Track_GetInfo_Audio(const Track * aTrack, TrackInfoAudio & aTrackInfo) const
{
  if (IsMyTrack(aTrack)) {
    aTrack->GetInfoAudio(aTrackInfo);
  }
}

void FileMatroska::Track_SetInfo_Audio(Track * aTrack, const TrackInfoAudio & aTrackInfo)
{
  if (IsMyTrack(aTrack)) {
    aTrack->SetInfoAudio(aTrackInfo);
  }
}

// Video related getters/setters

void FileMatroska::Track_GetInfo_Video(const Track * aTrack, TrackInfoVideo & aTrackInfo) const
{
  if (IsMyTrack(aTrack)) {
    aTrack->GetInfoVideo(aTrackInfo);
  }
}

void FileMatroska::Track_SetInfo_Video(Track * aTrack, const TrackInfoVideo & aTrackInfo)
{
  if (IsMyTrack(aTrack)) {
    aTrack->SetInfoVideo(aTrackInfo);
  }
}

/*!
  \todo exit when there is no Block left
*/
bool FileMatroska::ReadFrame(Track * & aTrack, uint32 & aTimecode, const binary * & aFrame, uint32 & aFrameSize,
                             bool & aKeyFrame, bool & aBFrame)
{
  if (myCurrReadBlockTrack == 0) {
    do {
      if (myReadBlockNumber >= myStreamInfo.NumberBlock) {
        //    myReadBlockNumber = myStreamInfo.NumberBlock;
        return false;
      }

      // get the next frame in the file
      if (!myCurrReadCluster.BlockLeft()) {
        myCurrReadCluster.Flush();
        try {
          myCurrReadCluster.FindHead(myFile);
        }
        catch (exception & Ex) {
          return false;
        }
      }

      myCurrReadCluster.GetBlock( myCurrReadBlock, myCurrReadBlockSize, myCurrReadBlockTrack );
      myReadBlockNumber++;
    } while (!IsReadingTrack(myCurrReadBlockTrack));

    // get the track associated (normally from myTracks)
    aTrack = myTracks[myCurrReadBlockTrack-1];
    // get the next frame from the current block
    aTrack->HandleBlock(myCurrReadBlock, myCurrReadBlockSize);
  } else {
    // get the track associated (normally from myTracks)
    aTrack = myTracks[myCurrReadBlockTrack-1];
  }

  Frame *  myReadFrame;
  aTrack->GetNextFrame(aTimecode, myReadFrame, aKeyFrame, aBFrame);
  aFrame = myReadFrame->buf();
  aFrameSize = myReadFrame->length();

  if (aTrack->NoFrameLeft()) {
    aTrack->FlushBlock();
    myCurrReadBlockTrack = 0;
  }

  return true;
}
#endif // OLD

} // namespace libmatroska

void matroska_init()
{
#if defined(HAVE_EBML2) || defined(HAS_EBML2)
  ebml_init();
#endif
}

void matroska_done()
{
#if defined(HAVE_EBML2) || defined(HAS_EBML2)
  ebml_done();
#endif
}
