/*
 * OggPage will carry all relevant information of an ogg page
 *
 * 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 <sstream>
#include <cstring>

#include "oggPage.h"
#include "oggHeader.h"
#include "crc.h"

/* OggPageInternal */

OggPageInternal::OggPageInternal()
    : data(0), headerLength(0), bodyLength(0), streamNo(255), empty(true)
{
}

OggPageInternal::OggPageInternal(uint8* _data, uint32 _headerLength, uint32 _bodyLength)
    : data(_data), headerLength(_headerLength), bodyLength(_bodyLength),  streamNo(255), empty(false)
{
}

OggPageInternal::~OggPageInternal()
{
  if (data)
    delete[] data;
}

/*
void OggPageInternal::fromLibogg(ogg_page page)
{
  if (data)
    delete[] data;
  data = new uint8[page.body_len+page.header_len];
  headerLength = page.header_len;
  bodyLength   = page.body_len;
}

ogg_page OggPageInternal::toLibogg()
{
  ogg_page page;
  if (data) {
    page.header = data;
    page.body   = data+headerLength;
    page.header_len = headerLength;
    page.body_len   = bodyLength;
  }
  else {
    page.header = 0;
    page.body   = 0;
    page.header_len = 0;
    page.body_len   = 0;
  }

  return(page);
}
*/
OggPage::OggPage()
{
}

OggPage::OggPage(OggPageInternal* pagePtr)
    : RefObject<OggPageInternal>(pagePtr)
{
}

OggPage::OggPage(const OggPage& refObj)
    : RefObject<OggPageInternal>(refObj)
{
}

OggPage::~OggPage()
{
}

OggPage& OggPage::operator=(const OggPage& refObj)
{
  if (this == &refObj)
    return(*this);

  (*refCounter)--;

  if ((*refCounter) == 0) {
    delete refCounter;
    delete objPtr;
  }

  refCounter = refObj.refCounter;
  objPtr     = refObj.objPtr;

  (*refCounter)++;

  return(*this);
}

bool OggPage::isContinued()
{
  return(((OggHeader*)(objPtr->data))->pack_type);
}

void OggPage::setContinued()
{
  ((OggHeader*)(objPtr->data))->pack_type = 1;
}

bool OggPage::isBOS()
{
  return(((OggHeader*)(objPtr->data))->page_type);
}

bool OggPage::isEOS()
{
  return(((OggHeader*)(objPtr->data))->last);
}

void OggPage::setBOS()
{
  ((OggHeader*)(objPtr->data))->page_type = 1;
}

void OggPage::unsetBOS()
{
  ((OggHeader*)(objPtr->data))->page_type = 0;
}

void OggPage::setEOS()
{
  ((OggHeader*)(objPtr->data))->last = 1;
}

void OggPage::unsetEOS()
{
  ((OggHeader*)(objPtr->data))->last = 0;
}

void OggPage::setStreamNo(uint8 streamNo)
{
  objPtr->streamNo = streamNo;
}

uint8 OggPage::getStreamNo()
{
  return(objPtr->streamNo);
}

uint32 OggPage::version()
{
  return(((OggHeader*)(objPtr->data))->version);
}

uint32 OggPage::packets()
{
  uint32 segments(((OggHeader*)(objPtr->data))->tableSegments);
  uint32 packets(0);
  uint8* oggPtr=objPtr->data+sizeof(OggHeader);

  for (uint32 i(0); i<segments; ++i)
    if (oggPtr[i]<0xff)packets++;

  return(packets);

}

int64 OggPage::granulepos()
{
  return(((OggHeader*)(objPtr->data))->position);
}

uint32 OggPage::serialno()
{
  return(((OggHeader*)(objPtr->data))->serial);
}

uint32 OggPage::pageno()
{
  return(((OggHeader*)(objPtr->data))->pageNo);
}

uint32 OggPage::length()
{
  return(objPtr->headerLength + objPtr->bodyLength);
}

uint8* OggPage::data()
{
  return(objPtr->data);
}

bool OggPage::isEmpty()
{
  return(objPtr->empty);
}

void OggPage::createCRC()
{
  OggHeader* hdr = (OggHeader*)(objPtr->data);
  hdr->checksum  = 0;
  hdr->checksum  = Crc::create(objPtr->data, length());
}

OggPage OggPage::clone()
{

  if (length() == 0)
    return(OggPage());

  uint8* data = new uint8[length()];
  memcpy(data, objPtr->data, length());

  OggPageInternal* newInternalPage = new OggPageInternal(data, objPtr->headerLength, objPtr->bodyLength);

  return(OggPage(newInternalPage));

}

/* print levels:
 * 0) only data length information
 * 1) header information
 * 2) additional header information
 * 3) header dump
 * 4) body dump
 */
std::string OggPage::print(uint8 level)
{
  std::stringstream retStream;

  retStream << "Ogg Page: header length = " << std::dec << objPtr->headerLength
  << " and body length = " << std::dec << objPtr->bodyLength
  << std::endl;

  if (level < 1)
    return(retStream.str());

  OggHeader* header = (OggHeader*)(objPtr->data);
  retStream << "Header Information:"
  << "\n\tOgg Version      : " << (uint32)header->version
  << "\n\tSerial No        : 0x" << std::hex << header->serial << std::dec
  << "\n\tPacket Type      : ";

  if (header->pack_type)
    retStream << "continued packet";
  else
    retStream << "fresh packet";

  retStream << "\n\tPage Type        : ";

  if (header->page_type)
    retStream << "begin of stream marker";
  else
    retStream << "normal page";

  retStream << "\n\tLast Page        : ";

  if (header->last)
    retStream << "end of stream marker";
  else
    retStream << "normal page";

  retStream << "\n\tGranule Position : " << header->position << "(0x" << std::hex << header->position << std::dec << ")";
  retStream << "\n\tPage Number      : " << header->pageNo;
  retStream << "\n\tChecksum         : 0x" << std::hex << header->checksum << std::dec;
  retStream << "\n\tTable Segments   : " << (uint32) header->tableSegments;
  retStream << std::endl << std::endl;

  if (level < 2)
    return(retStream.str());

  retStream << "Segments:";

  for (uint32 c(0); c<header->tableSegments; ++c) {
    if ((c%16) == 0)
      retStream << std::endl;
    retStream << " "<< std::hex;
    if (((unsigned int) (objPtr->data[c+sizeof(OggHeader)])) < 16)
      retStream << "0";
    retStream << (unsigned int) (objPtr->data[c+sizeof(OggHeader)]);
  }

  retStream << std::endl << std::endl;

  if (level < 3)
    return(retStream.str());

  retStream << "Header Hex dump: ";
  for (uint32 c(0); c<objPtr->headerLength; ++c) {
    if ((c%16) == 0)
      retStream << std::endl;
    retStream << " " << std::hex;
    if (((unsigned int) (objPtr->data[c])) < 16)
      retStream << "0";
    retStream << (unsigned int) (objPtr->data[c]);
  }
  retStream << std::dec << std::endl << std::endl;

  if (level < 4)
    return(retStream.str());

  retStream << "Body Hex dump: ";

  for (uint32 c(0); c<objPtr->bodyLength; ++c) {
    if ((c%16) == 0)
      retStream << std::endl;
    retStream << " " << std::hex;
    if (((unsigned int) (objPtr->data[c+objPtr->headerLength])) < 16)
      retStream << "0";
    retStream << (unsigned int) (objPtr->data[c+objPtr->headerLength]);
  }

  retStream << std::dec << std::endl;

  return(retStream.str());
}

