/*
 * oggStreamDecoder is a class to extract an ogg packet from an
 * ogg page stream
 *
 * Copyright (C) 2008 Joern Seger
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <iostream>
#include <vector>
#include <cstring>

#include "definition.h"
#include "oggHeader.h"
#include "oggStreamDecoder.h"
#include "exception.h"
#include "log.h"

OggStreamDecoder::SegmentElement::SegmentElement(uint8* _data, uint32 length) :
    data(_data), length(length)
{
}

OggStreamDecoder::OggStreamDecoder()
{
}

OggStreamDecoder::~OggStreamDecoder()
{
  clear();
}

void OggStreamDecoder::init(OggPage page)
{

  /* if this is not a Begin Of Stream page, do nothing */
  if (!page.isBOS()) {
    logger.error() << "OggStreamDecoder: ogg page is not a begin of stream\n";
    return;
  }

  packetCount = 0;

  /* extract and remember the serial number of this stream */
  serialNo = page.serialno();
  setConfigured();

}

void OggStreamDecoder::clear()
{
  delete[] tmpSegment.data;
  tmpSegment.data = 0;
  tmpSegment.length = 0;
}

uint32 OggStreamDecoder::getSerialNo()
{
  return(serialNo);
}

OggStreamDecoder& OggStreamDecoder::operator<<(OggPage& page)
{
  /* if this stream is not initialized, try to initialize it */
  if (!isInitialized())
    init(page);

  /* decode the packets */
  if (!isConfigured()) {
    throw OggException("OggStreamDecoder::operator<<: This stream is not is not configured yet");
  }

  if (page.serialno() != serialNo) {
    throw OggException("OggStreamDecoder::operator<<: page does not belong to this stream");
  }

  /* extract the header */
  uint8* data(page.data());
  OggHeader* header = (OggHeader*)(data);
  data += sizeof(OggHeader);

  /* extract the relevant data from the header */
  unsigned char tableSegments(header->tableSegments);

  // extract the segment table
  uint8* segment = (uint8*) data;
  data += tableSegments;

  /* will the last packet be continued on in the next page? */
  bool willBeContinued;

  if (segment[header->tableSegments-1] != 255)
    willBeContinued = false;
  else
    willBeContinued = true;

  std::vector<SegmentElement> segmentDataList;

  // extract pointers to the packets in this page
  SegmentElement segData(data,0);

  for (unsigned int i=0; i<tableSegments; ++i) {
    data += segment[i];
    segData.length += segment[i];
    if (segment[i] != 255) {
      segmentDataList.push_back(segData);
      segData = SegmentElement(data,0);
    }
  }

  // store the last packet if it does not end in this page
  if (willBeContinued)
    segmentDataList.push_back(segData);

  /* it would be good to know where the granule position belongs to */
  uint32 infoPosition(256);
  /* does the last packet do not end here */
  if (!willBeContinued)
    infoPosition = segmentDataList.size()-1;
  else if (segmentDataList.size() > 1)
    infoPosition = segmentDataList.size()-2;

  // now extract the ogg packets itself
  // every segment in the list is one packet (maybe there is a
  // remaining part in tmpSegment from the page before and
  // there might be a segment, that is not finished on this page)

  for (unsigned int i(0); i<segmentDataList.size(); ++i) {

    uint32 overallLength = tmpSegment.length+segmentDataList[i].length;
    unsigned char* newPacketPtr = new unsigned char[overallLength];

    if (tmpSegment.length)
      memcpy(newPacketPtr,tmpSegment.data, tmpSegment.length);

    memcpy(newPacketPtr+tmpSegment.length, segmentDataList[i].data, segmentDataList[i].length);

    // delete the temporary Segment
    if (tmpSegment.data) {
      delete[] tmpSegment.data;
      tmpSegment = SegmentElement();
    }
    if ((i == (segmentDataList.size()-1)) && willBeContinued) {
      //store last segment as it is does not end here
      tmpSegment = SegmentElement(newPacketPtr, overallLength);
    } else {
      // we found a full packet
      OggPacketInternal::PacketType packetType(OggPacketInternal::normal);
      int64 granulePosition(-1);

      if ((i == 0) && (page.isBOS()))
        packetType = OggPacketInternal::bos;

      if ((i == segmentDataList.size()-1) && (page.isEOS()))
        packetType = OggPacketInternal::eos;

      if (i == infoPosition)
        granulePosition = header->position;

      /* create the packet */
      OggPacket packet(new OggPacketInternal(newPacketPtr, overallLength, packetCount++, granulePosition, packetType));

      oggPacketList.push_back(packet);
    }
  }

  if (!oggPacketList.empty())
    setAvailable();

  return(*this);
}

OggPacket OggStreamDecoder::inspectNextPacket()
{
  OggPacket packet;

  if (!isAvailable()) {
    throw OggException("OggStreamDecoder::inspectNextPacket: no packet available");
  }

  // we will not harm the list in any kind
  packet = oggPacketList.front();

  return (packet);
}

OggStreamDecoder& OggStreamDecoder::operator>>(OggPacket& packet)
{
  if (!isAvailable()) {
    throw OggException("OggStreamDecoder::operator>>: no packet available");
  }

  packet = oggPacketList.front();
  oggPacketList.pop_front();

  /* is this the last packet within this stream,
   * then set the stream status */
  if (packet.isEOS()) {
    setEndOfStream();
  } else {
    if (oggPacketList.empty()) {
      setEmpty();
    }
  }
  return(*this);
}

