//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / OVOPS++
//
//File: frame.cpp
//
//Version: $Revision: 1.10 $
//
//State: $State: Exp $
//
//Date: $Date: 1998/06/23 05:47:57 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Authors:
//      Pasi Nummisalo
//      Juhana Räsänen
//      Sami Raatikainen
//
//Description:
//	See corresponding header file.  
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//      
//Licence:
//     
//
//History:
//
//

#include "frame.h"
#include "pf/bytes.h"

//
//Function: FrameContainer constructors
//
//Description:
//      Allocate container's data area and initializes container.
//

pfFrame :: FrameContainer :: FrameContainer(pfUlong dataSize_)
    : _data(0),
      _dataSize(dataSize_),
      _referenceCount(1)
{
    _data = allocateFrameData(dataSize_);
    return;
}

pfFrame :: FrameContainer :: FrameContainer(const FrameContainer &other_)
    : _data(0),
      _dataSize(other_._dataSize),
      _referenceCount(1)
{
    _data = allocateFrameData(other_._dataSize);
    memcpy(_data, other_._data, other_._dataSize);
    return;
}


//
//Function: FrameContainer destructor
//
//Description:
//      Frees the allocated frame data.
//

pfFrame :: FrameContainer :: ~FrameContainer(void)
{
    delete[] _data;
    return;
}

//
//Function: allocateFrameData
//
//Description:
//      Memory allocation routine. Because endPointer in the main-class
//      (pfFrame) will always point to the next byte (null) after data,
//      we have to allocate datasize + 1 bytes.
//

pfByte* pfFrame :: FrameContainer :: allocateFrameData(pfUlong dataSize_)
{
    pfByte *data = new pfByte[dataSize_+1];
    return data;
}

//
//Function: length
//
//Description:
//      Returns the length of the buffer (ie. max datasize).
//

pfUlong pfFrame :: FrameContainer :: length(void) const
{
    return _dataSize;
}

//
//Function: read
//
//Description:
//      Returns the octet at the given index. If offset is outside
//      boundaries 0 is returned. The offset 0 indicates first octet.
//

pfByte pfFrame :: FrameContainer :: read(pfUlong offset_) const
{
    pfByte octet = 0;
    if (offset_ < length())
    {
        octet = _data[offset_];
    }
    return octet;
}

//
//Function: write
//
//Description:
//    Write the given byte to the given index.
//

void pfFrame :: FrameContainer :: write(pfByte value_, pfUlong offset_)
{
    if(offset_ < length())
    {
        _data[offset_] = value_;
    }
    return;
}

//
//Function: copyData / copyContainer
//
//Description:
//      Copies the content of the container to the given pointer.
//      Caller must ensure that the pointer is valid and the data
//      area behind it is large enough. (device needs this).
//      CopyContainer copies other container to the given offset.
//

void pfFrame :: FrameContainer :: copyData(pfUlong offset_,
                                           pfUlong length_,
                                           pfByte *destination_) const
{
    memcpy(destination_, &(_data[offset_]), length_);
    return;
}

void pfFrame :: FrameContainer :: copyContainer(pfUlong offset_,
                                                const FrameContainer &other_)
{
    other_.copyData(0, other_.length(), (_data + offset_));
    return;
}

//
//Function: FrameContainer reference count methods
//
//Description:
//      Increase, decrease and return reference count of the container.
//

void pfFrame :: FrameContainer :: incRefCount(void)
{
    _referenceCount++;
    return;
}

void pfFrame :: FrameContainer :: decRefCount(void)
{
    _referenceCount--;
    return;
}

int pfFrame :: FrameContainer :: getRefCount(void) const
{
    return _referenceCount;
}


//-----------------------------------------------------------------------
//
//Function: pfFrame constructors
//
//Description:
//      Create new instances of pfFrames and allocate FrameContainers
//      for those. Begin and end pointers are set to the end of the frame
//      by default, and can be re-initialized using setStart-method.
//      Copy constructor does not create a new container, but copies
//      the pointer to the other container instead.
//      Write-bit is set to true in every new and copied object.
//      So, only the newest object has the write-bit set to true.
//

pfFrame :: pfFrame(pfUlong dataSize_)
    : _container(0),
      _write(1),
      _beginPointer(dataSize_),
      _endPointer(dataSize_)
{
    _container = new FrameContainer(dataSize_);
    return;
}

pfFrame :: pfFrame(pfByte *data_, pfUlong length_, pfUlong dataSize_)
    : _container(0),
      _write(1),
      _beginPointer(0),
      _endPointer(0)
{
    if(dataSize_ < length_)
    {
        dataSize_ = DEFAULT_FRAMESIZE;
    }
    _container = new FrameContainer(dataSize_);
    _beginPointer = dataSize_;
    _endPointer = dataSize_;
    putFirst(data_, length_);
    return;
}

pfFrame :: pfFrame(pfFrame &other_)
    : _container(other_._container),
      _write(other_.giveWriteBit()),
      _beginPointer(other_._beginPointer),
      _endPointer(other_._endPointer)
{
    _container->incRefCount();
    return;
}

//
//Function: pfFrame destructor
//
//Description:
//      Does not delete FrameContainer instance if it is still
//      referenced by other frame(s), just decreases refcount instead.
//

pfFrame :: ~pfFrame(void)
{
    giveUpFrameContainer();
    return;
}

//
//Function: pfFrame assignment operator
//
//Description:
//      Copies only the pointer of the container of the other frame.
//

const pfFrame &pfFrame :: operator=(pfFrame &other_)
{
    if (this != &other_)
    {
        giveUpFrameContainer();
        _container = other_._container;
        _container->incRefCount();
        _beginPointer = other_._beginPointer;
        _endPointer = other_._endPointer;
        _write = other_.giveWriteBit();
    }
    return *this;
}


//
//Function: length
//
//Description:
//      Returns the number of octets in the frame.
//

pfUlong  pfFrame :: length(void) const
{
    pfUlong frameLength = _endPointer - _beginPointer;
    return frameLength;
}


//
//Function: write (private method)
//
//Description:
//      Writes given length (1-4) in bytes from value into the container.
//      Offset is the new endPointer or the old beginPointer.
//

void pfFrame :: write(pfUlong value_, pfUlong length_, pfUlong offset_)
{
    for(pfUlong i = 0; i < length_; i++)
    {    
        offset_--;
        _container->write((value_ & B1111_1111), offset_);
        value_ >>= 8;
    }
    return;
}

//
//Function: read methods
//
//Description:
//      Returns the octet at the given index. (offset 0 = data[0])
//      16/24/32-bit versions read a 16/24/32 bit integer.
//

pfByte pfFrame :: read(pfUlong offset_) const
    throw (pfOutOfRangeException)
{
    if((_beginPointer + offset_) >= _endPointer)
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    pfByte value = _container->read(_beginPointer + offset_);
    return value;
}

pfUlong pfFrame :: read16bit(pfUlong offset_) const
    throw (pfOutOfRangeException)
{
    pfUlong value = read(offset_) << 8;
    value += read(offset_ + 1);
    value &= 0xFFFF; // 64-bit safe
    return value;
}

pfUlong pfFrame :: read24bit(pfUlong offset_) const
    throw (pfOutOfRangeException)
{
    pfUlong value = read(offset_) << 16;
    value += read(offset_ + 1) << 8;
    value += read(offset_ + 2);
    value &= 0xFFFFFF; // 64-bit safe
    return value;
}

pfUlong pfFrame :: read32bit(pfUlong offset_) const
    throw (pfOutOfRangeException)
{
    pfUlong value = read(offset_) << 24;
    value += read(offset_ + 1) << 16;
    value += read(offset_ + 2) << 8;
    value += read(offset_ + 3);
    value &= 0xFFFFFFFF; // 64-bit safe
    return value;
}


//
//Function: putFirst methods
//
//Description:
//      Insert an octet or a string to the head of the frame.
//      If the container of this frame is shared by other frames,
//      and this one doesn't have the access (write-bit), a copy must
//      be made before inserting. 16/24/32-bit versions
//      insert a 16/24/32 bit integer respectively, MSB first.
//

void pfFrame :: putFirst(pfByte byte_)
{
    checkBegin();
    _container->write(byte_, --_beginPointer);
    return;
}

void pfFrame :: putFirst16bit(pfUlong value_)
{
    checkBegin(LENGTH_16BIT);
    write(value_, LENGTH_16BIT, _beginPointer);
    _beginPointer -= LENGTH_16BIT;
    return;
}

void pfFrame :: putFirst24bit(pfUlong value_)
{
    checkBegin(LENGTH_24BIT);
    write(value_, LENGTH_24BIT, _beginPointer);
    _beginPointer -= LENGTH_24BIT;
    return;
}

void pfFrame :: putFirst32bit(pfUlong value_)
{
    checkBegin(LENGTH_32BIT);
    write(value_, LENGTH_32BIT, _beginPointer);
    _beginPointer -= LENGTH_32BIT;
    return;
}

void pfFrame :: putFirst(const pfByte *byte_, pfUlong length_)
{
    checkBegin(length_);
    pfUlong beginPointer = _beginPointer - length_;
    for(pfUlong i=0; i<length_; i++)
    {
        _container->write(*(byte_+i), (beginPointer + i));
    }
    _beginPointer = beginPointer;
    return;
}

//
//Function: getFirst methods
//
//Description:
//      Returns the first octet or 16/24/32-bit integer.
//      Container will not be changed. 
//      After getting any amount of data from the container,
//      writing access will be set to false. So, if a frame
//      wants to put data into the container again, the container has
//      to be copied if referenceCounter > 1.
//

pfByte pfFrame :: getFirst(void)
    throw (pfOutOfRangeException)
{
    pfByte value = read();
    _beginPointer++;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getFirst16bit(void)
    throw (pfOutOfRangeException)
{
    pfUlong value = read16bit();
    _beginPointer += LENGTH_16BIT;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getFirst24bit(void)
    throw (pfOutOfRangeException)
{
    pfUlong value = read24bit();
    _beginPointer += LENGTH_24BIT;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getFirst32bit(void)
    throw (pfOutOfRangeException)
{
    pfUlong value = read32bit();
    _beginPointer += LENGTH_32BIT;
    _write = 0;
    return value;
}


//
//Function: putLast methods
//
//Description:
//      Append an octet or 16/24/32-bit integer or a string to the
//      end of the frame. If the container of this frame is shared
//      by other frames, and this one doesn't have the access
//      (write-bit), a copy must be made before inserting.
//

void pfFrame :: putLast(pfByte byte_)
{
    checkEnd();
    _container->write(byte_, _endPointer++);
    return;
}

void pfFrame :: putLast16bit(pfUlong value_)
{
    checkEnd(LENGTH_16BIT);
    write(value_, LENGTH_16BIT, (_endPointer + LENGTH_16BIT));
    _endPointer += LENGTH_16BIT;
    return;
}

void pfFrame :: putLast24bit(pfUlong value_)
{
    checkEnd(LENGTH_24BIT);
    write(value_, LENGTH_24BIT, (_endPointer + LENGTH_24BIT));
    _endPointer += LENGTH_24BIT;
    return;
}

void pfFrame :: putLast32bit(pfUlong value_)
{
    checkEnd(LENGTH_32BIT);
    write(value_, LENGTH_32BIT, (_endPointer + LENGTH_32BIT));
    _endPointer += LENGTH_32BIT;
    return;
}

void pfFrame :: putLast(const pfByte *byte_, pfUlong length_)
{
    checkEnd(length_);
    for(pfUlong i=0; i<length_; i++)
    {
        _container->write(*(byte_+i), _endPointer++);
    }
    return;
}

//
//Function: getLast methods
//
//Description:
//      Returns the last octet or 16/24/32-bit integer.
//

pfByte pfFrame :: getLast(void)
    throw (pfOutOfRangeException)
{
    if((_beginPointer + 1) > _endPointer)
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    pfUlong offset = _endPointer - (_beginPointer + 1);
    pfByte value = read(offset);
    _endPointer--;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getLast16bit(void)
    throw (pfOutOfRangeException)
{
    if((_beginPointer + LENGTH_16BIT) > _endPointer)
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    pfUlong offset = _endPointer - (_beginPointer + LENGTH_16BIT);
    pfUlong value = read16bit(offset);
    _endPointer -= LENGTH_16BIT;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getLast24bit(void)
    throw (pfOutOfRangeException)
{
    if((_beginPointer + LENGTH_24BIT) > _endPointer)
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    pfUlong offset = _endPointer - (_beginPointer + LENGTH_24BIT);
    pfUlong value = read24bit(offset);
    _endPointer -= LENGTH_24BIT;
    _write = 0;
    return value;
}

pfUlong pfFrame :: getLast32bit(void)
    throw (pfOutOfRangeException)
{
    if((_beginPointer + LENGTH_32BIT) > _endPointer)
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    pfUlong offset = _endPointer - (_beginPointer + LENGTH_32BIT);
    pfUlong value = read32bit(offset);
    _endPointer -= LENGTH_32BIT;
    _write = 0;
    return value;
}


//
//Function: getSubFrame
//
//Description:
//    Get a sub frame of given length starting at given offset.
//

pfFrame pfFrame :: getSubFrame(pfUlong start_, pfUlong length_) const
    throw (pfOutOfRangeException)
{
    pfFrame frame(length_);
    pfUlong offset = start_ + length_;
    while((offset > start_))
    {
        offset--;
        frame.putFirst(read(offset));
    }
    return frame;
}

//Function: setStart
//
//Description:
//      If the container of this frame is shared with other frames,
//      a new (empty) instance of a FrameContainer is created.
//      BeginPointer and endPointer are set to given value.
//

void pfFrame :: setStart(pfUlong offset_)
    throw (pfOutOfRangeException)
{
    if(offset_ > _container->length())
    {
        throw pfOutOfRangeException(PF_EX_INFO);
    }
    if (_container->getRefCount() > 1)
    {
        giveUpFrameContainer();
        _container = new FrameContainer(_container->length());
        _container->incRefCount();
    }
    _beginPointer = offset_;
    _endPointer = offset_;
    return;
}

//
//Function: copyData
//
//Description:
//      Copies the data in the container to the given memory location.
//

void pfFrame :: copyData(pfByte *destination_) const
{
    _container->copyData(_beginPointer, length(), destination_);
    return;
}

//
//Function: giveWriteBit
//
//Description:
//      Returns _write-bit, and sets it to false.
//

pfBoolean pfFrame :: giveWriteBit(void)
{
    pfBoolean value = _write;
    _write = 0;
    return value;
}

//
//Function: giveUpFrameContainer
//
//Description:
//      Gives up the current container. If it is referenced by other
//      frames, just decrease the reference count, otherwise delete
//      the container instance.
//

void pfFrame :: giveUpFrameContainer(void)
{
    if (_container->getRefCount() > 1)
    {
        _container->decRefCount();
    }
    else
    {
        delete _container;
    }
    _container = 0;
    return;
}


//
//Function: checkBegin / checkEnd
//
//Description:
//      Copies the container if necessary, and makes a whole
//      new bigger container if there's not enough room (length_)
//      for new data.
//

void pfFrame :: checkBegin(pfUlong length_)
{
    copyIfNecessary();
    if(_beginPointer < length_)
    {
        expandBuffer();
    }
    return;
}

void pfFrame :: checkEnd(pfUlong length_)
{
    copyIfNecessary();
    if(_container->length() < (_endPointer + length_))
    {
        expandBuffer();
    }
    return;
}

//
//Function: copyIfNecessary
//
//Description:
//      If this frame doesn't have the access and the container attached to
//      is referenced by other frames, make a new copy of the container.
//

void pfFrame :: copyIfNecessary(void)
{
    if(_write == 0)
    {
        if(_container->getRefCount() > 1)
        {
            _container->decRefCount();
            _container = new FrameContainer(*_container);
            _container->incRefCount();
        }
        _write = 1;
    }
    return;
}

//
//Function: expandBuffer
//
//Description:
//      Expand data area by growth_ from both ends of frame.
//

void pfFrame :: expandBuffer(pfUlong growth_)
{
    FrameContainer *newContainer =
        new FrameContainer(growth_ + _container->length() + growth_);
    newContainer->copyContainer(growth_, *_container);
    giveUpFrameContainer();
    _container = newContainer;
    _endPointer += growth_;
    _beginPointer += growth_;
    return;
}
