/********************************************************************

 AviMediaReadStream functions that are common for video and audio streams

*********************************************************************/

#include "AviMediaReadStream.h"
#include "AviMediaReadHandler.h"

#include <iostream>

using namespace std;

#define Debug if(0)

AviMediaReadStream::AviMediaReadStream(AviMediaReadHandler* parent)
    : m_pParent(parent), m_pcFormat(0), m_uiPosition(0), m_uiChunk(0),
    m_uiReadPosition(0)
{
}

AviMediaReadStream::AviMediaReadStream(const AviMediaReadStream& c)
{
    m_iId = c.m_iId;
    m_Index = c.m_Index;
    m_Header = c.m_Header;
    m_pParent = c.m_pParent;
    m_uiChunk = c.m_uiChunk;
    m_uiPosition = c.m_uiPosition;
    m_uiReadPosition = c.m_uiReadPosition;

    //printf("AviMediaReadStream  %d  %.4s   %p\n", c.m_uiFormatSize, (char*) &c.m_uiFormatSize, c.m_pcFormat);
    if (c.m_pcFormat)
    {
	m_pcFormat = new char[c.m_uiFormatSize];
	m_uiFormatSize = c.m_uiFormatSize;
	memcpy(m_pcFormat, c.m_pcFormat, m_uiFormatSize);
    }
    else
    {
	m_pcFormat = 0;
	m_uiFormatSize = 0;
    }
}

AviMediaReadStream::~AviMediaReadStream()
{
    delete[] m_pcFormat;
}

framepos_t AviMediaReadStream::find(framepos_t lSample) const
{
    if (lSample >= GetLength() || lSample == ERR)
	return ERR;

    framepos_t low_limit = 0;
    framepos_t high_limit = m_Index.size() - 1;
    while (low_limit != high_limit)
    {
	framepos_t middle = (low_limit + high_limit) / 2;
	if (lSample >= m_Index[middle].qwTimestamp)
	{
            // this fix is necessary to prevent deadlock loop here!
	    if (low_limit == middle)
                break;

	    low_limit = middle;
	    if (lSample < (m_Index[middle].qwTimestamp
			   + (m_Index[middle].dwChunkLength/m_Header.dwSampleSize)))
		break;
	}
	else
	    high_limit = middle;
    }
    return low_limit;
}

HRESULT AviMediaReadStream::GetHeader(void* pheader, size_t size) const
{

    if (!pheader || (size < sizeof(m_Header)))
	return -1;

    memset(pheader, 0, size);
    memcpy(pheader, &m_Header, sizeof(m_Header));


#if 0
    cout << "Avi HEADER SIZE " << m_Index.size() << endl;
    cout
#if 0
	<< " MicroSecPerFrame " << m_Header.dwMicroSecPerFrame << endl
	<< " MaxBytesPerSec " << m_Header.dwMaxBytesPerSec << endl
	<< " Flags: [ "
	<< (m_Header.dwFlags & AVIF_HASINDEX) ? " HasIndex " : ""
	<< (m_Header.dwFlags & AVIF_MUSTUSEINDEX) ? " MustUseIndex " : ""
	<< (m_Header.dwFlags & AVIF_ISINTERLEAVED) ? " IsInterleaved " : ""
	<< (m_Header.dwFlags & AVIF_COPYRIGHTED) ? " Copyrighted " : ""
	<< (m_Header.dwFlags & AVIF_WASCAPTUREDFILE) ? " WasCapturedFile " : ""
	<< " ]" << endl
        << " TotalFrames " <<  m_Header.dwTotalFrames << endl
        << " InitialFrames " <<  m_Header.dwInitialFrames << endl
        << " InitialFrames " <<  m_Header.dwInitialFrames << endl
	<< " Streams " <<  m_Header.dwStreams << endl
        << " Width x Height " <<  m_Header.dwWidth << " x " <<  m_Header.dwHeight << endl
#endif
	<< " Start " <<  m_Header.dwStart << endl
	<< " SuggestedBufferSize " << m_Header.dwSuggestedBufferSize << endl
	<< " Quality " << m_Header.dwQuality << endl
	<< " Length " << m_Header.dwLength << endl;
    if (m_Header.dwStart && m_Header.dwStart)
        cout << "Calc starttime " << m_Header.dwStart * (m_Header.dwRate / (double)m_Header.dwStart) << endl;

#if 0
    for (int i = 0 ; i < 40; i++)
	cout << i
            << "   " << m_Index[i].qwChunkOffset
            << "   " << m_Index[i].dwChunkLength
            << "   " << m_Index[i].dwFlags
	    << "   " << m_Index[i].qwTimestamp << endl;
#endif
#endif
    return 0;
}

bool AviMediaReadStream::IsKeyFrame(framepos_t pos) const
{
    if (m_Header.dwSampleSize)
	return true; //audio

    framepos_t chunk_id = (pos == ERR) ? GetPos() : pos;
    //cout << "ISKF " << chunk_id << "   " << pos << endl;
    if (chunk_id >= m_Index.size() || chunk_id == ERR)
	return true; //or whatever

    return m_Index[chunk_id].dwFlags & AVIIF_KEYFRAME;
}

HRESULT AviMediaReadStream::Seek(framepos_t pos)
{
    Debug cout << "AviMediaReadStream::Seek() " << pos << endl;
    framepos_t chunk_id = (m_Header.dwSampleSize) ? find(pos) : pos;

    if (chunk_id == ERR)
	return -1;

    //if (m_uiChunk != chunk_id || m_uiPosition != pos)
    {
	m_uiChunk = chunk_id;
	m_uiPosition = pos;
        m_uiReadPosition = pos;
	//ClearCache(); // ?? not needed - cache will do this when necessary
    }

    Debug cout << "AviMediaReadStream::Seek() p: " << pos << " c: " << chunk_id << endl;

    return 0;
}

HRESULT AviMediaReadStream::SeekTime(double timepos)
{
    framepos_t p = (framepos_t)(timepos / (m_Header.dwScale / double(m_Header.dwRate)));
    return Seek(p);
}

framepos_t AviMediaReadStream::GetPos() const
{
    //return (m_uiPosition) ? m_uiPosition - 1: 0;
    //return m_uiPosition;
    return m_uiReadPosition;
}

HRESULT AviMediaReadStream::Read(unsigned int lSamples, void *lpBuffer,
				 unsigned int cbBuffer, unsigned int *plBytes,
				 unsigned int *plSamples)
{
    Debug cout<<"AviMediaReadStream::Read("<<lSamples<<","<<cbBuffer<<")"<<endl;
    Debug cout<<"chunk "<<m_uiChunk<<", position "<<m_uiPosition<<endl;

    if (0 && m_Header.dwSampleSize)
	cout << "AviMediaReadStream::Read("<<lSamples<<","<<cbBuffer<<")"
	    << " chunk " << m_uiChunk << ", position " << m_uiPosition
	    << " CLEN "  << m_Index[m_uiChunk].dwChunkLength
  	    << " TSTAMP " << ( uint ) m_Index[m_uiChunk].qwTimestamp
	    << endl;

    if (m_uiChunk >= m_Index.size() && m_uiChunk != ERR)
    { //end of file
	if (plBytes)
	    *plBytes = 0;
	if (plSamples)
	    *plSamples = 0;
	return -1;
    }

    if (!lpBuffer)
    {
        // only query
	size_t s = (m_Header.dwSampleSize) ?
	    lSamples * m_Header.dwSampleSize : m_Index[m_uiChunk].dwChunkLength;
	if (plBytes)
	    *plBytes = s;
	return 0;
    }

    size_t read_samples = 0;
    size_t read_bytes = 0;

    if (m_Header.dwSampleSize)
    {
	cbBuffer -= (cbBuffer % m_Header.dwSampleSize);
	if (cbBuffer < (lSamples * m_Header.dwSampleSize))
	    lSamples = cbBuffer / m_Header.dwSampleSize;
    }
    /*while((m_uiChunk<m_Index.size()) && (read_samples<lSamples) && (m_Index[m_uiChunk].dwChunkLength==0))
    {
	m_uiChunk++;
	read_samples++;
	}*/

    m_uiReadPosition = m_uiPosition;

    while (m_uiChunk < m_Index.size() && read_samples < lSamples
	   && ((read_bytes < cbBuffer) || (!m_Index[m_uiChunk].dwChunkLength)))
    {
	int bytes;
	if (!m_Header.dwSampleSize)
	{ //video
	    if (cbBuffer - read_bytes < (unsigned) m_Index[m_uiChunk].dwChunkLength)
		break;
	    if (m_Index[m_uiChunk].dwChunkLength)
	    {
		bytes = m_pParent->m_Input.read((char*)lpBuffer + read_bytes,
						m_iId, m_uiChunk,
						m_Index[m_uiChunk].dwChunkLength);
		if (!bytes)
		    break;
	    }
	    else
		bytes = 0;

	    read_samples++;
	    read_bytes += bytes;

	    m_uiPosition++;
	    m_uiChunk++;
	    continue;
	}
	else
	{ // audio
	    int read_offset = (m_uiPosition - m_Index[m_uiChunk].qwTimestamp)
		* m_Header.dwSampleSize + read_bytes;
	    size_t bytes_to_read =
		m_Index[m_uiChunk].dwChunkLength - read_offset;
	    if (bytes_to_read > (cbBuffer - read_bytes))
		bytes_to_read = cbBuffer - read_bytes;
	    if (m_Index[m_uiChunk].dwChunkLength)
	    {
		bytes = m_pParent->m_Input.read((char*)lpBuffer + read_bytes,
						m_iId, m_uiChunk,
						bytes_to_read, read_offset);
		if (!bytes)
		    break;
	    }
	    else
		bytes = 0;

	    read_bytes += bytes;
	    if (bytes == (m_Index[m_uiChunk].dwChunkLength - read_offset))
		m_uiChunk++;
	}
    }
    if (m_Header.dwSampleSize)
    {
	read_samples = read_bytes / m_Header.dwSampleSize;
	m_uiPosition += read_samples;
    }
    Debug cout << "bytes " << read_bytes << ", samples " << read_samples << endl;
    if (plBytes)
	*plBytes = read_bytes;
    if (plSamples)
	*plSamples = read_samples;

    return 0;
}

framepos_t AviMediaReadStream::GetPrevKeyFrame(framepos_t pos) const
{
    if (pos == ERR)
	pos = GetPos();

    if (m_Header.dwSampleSize)
	return pos;

    if (pos >= m_Index.size() || pos == ERR)
	return 0;

    framepos_t inpos = pos;
    while (pos > 0)
	if (m_Index[--pos].dwFlags & AVIIF_KEYFRAME)
	    break;

    return pos;
}

framepos_t AviMediaReadStream::GetNextKeyFrame(framepos_t pos) const
{
    if (pos == ERR)
	pos = GetPos();

    if (m_Header.dwSampleSize)
	return pos;

    if (pos == ERR)
	return GetPrevKeyFrame(pos);

    bool kf = false;
    while (!kf && pos < (m_Index.size() - 1))
    {
	if (m_Index[++pos].dwFlags & AVIIF_KEYFRAME)
	    kf = true;
    }

    return (kf) ? pos : GetPrevKeyFrame(m_Index.size() - 1);
}

framepos_t AviMediaReadStream::GetNearestKeyFrame(framepos_t pos) const
{
    if (pos == ERR)
	pos = GetPos();

    if (m_Header.dwSampleSize)
	return pos;

    if (pos == ERR || pos >= m_Index.size())
	return 0;

    if (m_Index[pos].dwFlags & AVIIF_KEYFRAME)
	return pos;

    framepos_t prev = GetPrevKeyFrame(pos);
    framepos_t next = GetNextKeyFrame(pos);

    return (pos - prev < next - pos) ? prev : next;
}

unsigned int AviMediaReadStream::GetLength() const
{
    if (!m_Header.dwSampleSize)
	return m_Index.size();

    if (m_Index.size() == 0)
	return 0;

    const AVIINDEXENTRY2& end = m_Index.back();
    return end.qwTimestamp + end.dwChunkLength / m_Header.dwSampleSize;
}

double AviMediaReadStream::GetLengthTime() const
{
    return GetSampleTime(GetLength());
}

StreamInfo* AviMediaReadStream::GetStreamInfo() const
{
    if (m_StreamInfo.m_dLengthTime == 0.0)
    {
	unsigned int kfmax = 0;
	unsigned int kfmin = ~0U;
	unsigned int kfchunks = 0;
	int64_t kfsize = 0;
	unsigned int max = 0;
	unsigned int min = ~0U;
	unsigned int chunks = 0;
	int64_t size = 0;

	for (unsigned i = 0; i < m_Index.size(); i++)
	{
	    unsigned int l = m_Index[i].dwChunkLength;
	    if (m_Index[i].dwFlags & AVIIF_KEYFRAME)
	    {
		kfmax = (kfmax > l) ? kfmax : l;
		kfmin = (kfmin < l) ? kfmin : l;
		kfsize += l;
                kfchunks++;
	    }
	    else
	    {
		max = (max > l) ? max : l;
		min = (min < l) ? min : l;
		size += l;
		chunks++;
	    }
	}

	m_StreamInfo.m_uiMaxKfFrameSize = kfmax;
        m_StreamInfo.m_uiMinKfFrameSize = kfmin;
        m_StreamInfo.m_uiKfFramesSize = kfsize;
        m_StreamInfo.m_uiKfFrames = kfchunks;

	if (min > max)
            min = max; // usually no delta frames
	m_StreamInfo.m_uiMaxFrameSize = max;
        m_StreamInfo.m_uiMinFrameSize = min;
        m_StreamInfo.m_uiFramesSize = size;
        m_StreamInfo.m_uiFrames = chunks;

	m_StreamInfo.m_dLengthTime = GetLengthTime();
    }

    return new StreamInfo(m_StreamInfo);
}

double AviMediaReadStream::GetSampleTime(framepos_t pos) const
{
    //cout << "GETSAMPLETIME " << pos << "  " << GetPos() << endl;
    if (pos == ERR)
	pos = GetPos();

    double t = pos * (m_Header.dwScale / (double)m_Header.dwRate);
    // black magic here - some files place
    // here weird numbers - according to .avi specs they are
    // actually correct, but to fix this in general will require
    // some more work FIXME...
    if (m_Header.dwScale == 1  && m_Header.dwSampleSize > 1
	&& m_Header.dwSampleSize < 4)
        t /= m_Header.dwSampleSize;

    if (m_Header.dwSampleSize)
    {
	Debug cout << "GET SAMPLE SIZE pos " << pos
	    << " scale:  " << m_Header.dwScale
	    << " / rate: " << m_Header.dwRate
	    << " start: " << m_Header.dwStart
	    << " pos: " << m_uiChunk
	    << " stamp: " << ( uint )m_Index[m_uiChunk].qwTimestamp
	    << " ret: " << t
	    << " ssize: " << m_Header.dwSampleSize
	    << endl;
    }

    return t;
}

HRESULT AviMediaReadStream::GetFormat(void *pFormat, size_t* plSize) const
{
    if (pFormat)
	memcpy(pFormat, m_pcFormat, m_uiFormatSize);

    if (plSize)
	*plSize = m_uiFormatSize;

    return 0;
}

HRESULT AviMediaReadStream::GetFormatSize(size_t* plSize) const
{
    if (plSize)
	*plSize = m_uiFormatSize;

    return 0;
}

double AviMediaReadStream::CacheSize() const
{
    return m_pParent->m_Input.cacheSize();
}

void AviMediaReadStream::ClearCache()
{
    m_pParent->m_Input.clear();
}
