/*
 * dxr3demuxdevice.c
 *
 * Copyright (C) 2002-2004 Kai Möller
 *
 * This program 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 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 Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include "dxr3demuxdevice.h"
#include <linux/em8300.h>
#include "dxr3pesframe.h"
#include "dxr3configdata.h"

// ==================================
//! constructor
cDxr3DemuxDevice::cDxr3DemuxDevice(cDxr3Interface& dxr3Device) :
    m_dxr3Device(dxr3Device),
    m_aBuf(AUDIO_MAX_BUFFER_SIZE, AUIDO_MAX_FRAME_SIZE, m_dxr3Device),
    m_vBuf(VIDEO_MAX_BUFFER_SIZE, VIDEO_MAX_FRAME_SIZE, m_dxr3Device)
{
    m_ReUseFrame = 1;
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;
    m_pAudioThread = new cDxr3AudioOutThread(dxr3Device, m_aBuf);
    if (!m_pAudioThread)
    {
	esyslog("dxr3: fatal: unable to allocate memory for audio thread");
	exit(1);
    }
    m_pAudioThread->Start();

    m_pVideoThread = new cDxr3VideoOutThread(dxr3Device, m_vBuf);
    if (!m_pVideoThread)
    {
	esyslog("dxr3: fatal: unable to allocate memory for video thread");
	exit(1);
    }
    m_pVideoThread->Start();
    m_aDecoder.Init();
}

// ==================================
cDxr3DemuxDevice::cDxr3DemuxDevice() : // dummy constructor
    m_dxr3Device(cDxr3Interface::Instance()),
    m_aBuf(AUDIO_MAX_BUFFER_SIZE, AUIDO_MAX_FRAME_SIZE, m_dxr3Device),
    m_vBuf(VIDEO_MAX_BUFFER_SIZE, VIDEO_MAX_FRAME_SIZE, m_dxr3Device)
{
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;
}

// ==================================
// deconstr.
cDxr3DemuxDevice::~cDxr3DemuxDevice()
{
    if (m_pVideoThread)
    {
	delete m_pVideoThread;
    }

    if (m_pAudioThread)
    {
	delete m_pAudioThread;
    }
}

// ==================================
// stop demuxing process
void cDxr3DemuxDevice::Stop()
{
    m_dxr3Device.DisableVideo();
    m_dxr3Device.DisableAudio();
    m_vBuf.Clear();
    m_aBuf.Clear();
    m_vBuf.WakeUp();
    m_aBuf.WakeUp();
    m_aDecoder.Init();
    m_vBuf.WaitForReceiverStopped();
    m_aBuf.WaitForReceiverStopped();
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;

    m_dxr3Device.PlayBlackFrame();
    m_dxr3Device.ReOpenAudio();
}

// ==================================
void cDxr3DemuxDevice::Resync()
{
    m_dxr3Device.DisableVideo();
    m_dxr3Device.DisableAudio();
    m_vBuf.Clear();
    m_aBuf.Clear();
    m_vBuf.WakeUp();
    m_aBuf.WakeUp();
    m_aDecoder.Init();
    m_vBuf.WaitForReceiverStopped();
    m_aBuf.WaitForReceiverStopped();
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;
}

// ==================================
void cDxr3DemuxDevice::Clear()
{
    m_dxr3Device.DisableVideo();
    m_dxr3Device.DisableAudio();
    m_vBuf.Clear();
    m_aBuf.Clear();
    m_vBuf.WakeUp();
    m_aBuf.WakeUp();
    m_aDecoder.Init();
    m_vBuf.WaitForReceiverStopped();
    m_aBuf.WaitForReceiverStopped();
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;
}

// ==================================
void cDxr3DemuxDevice::Init()
{
    m_vBuf.Clear();
    m_aBuf.Clear();
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_OFF_MODE;
    m_aDecoder.Init();
}

// ==================================
void cDxr3DemuxDevice::SetTvMode()
{
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode = DXR3_DEMUX_TV_MODE;
    m_aBuf.SetDemuxMode(DXR3_DEMUX_TV_MODE);
    m_vBuf.SetDemuxMode(DXR3_DEMUX_TV_MODE);
    m_aBuf.Start();
    m_vBuf.Start();
}

// ==================================
void cDxr3DemuxDevice::SetAudioOnlyMode()
{
    m_synchState = DXR3_DEMUX_UNSYNCHED;
    m_demuxMode =  DXR3_DEMUX_AUDIO_ONLY_MODE;
    m_aBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);
    m_vBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);
    m_aBuf.Start();
    m_vBuf.Start();
}

// ==================================
void cDxr3DemuxDevice::SetReplayMode()
{
    if (m_demuxMode != DXR3_DEMUX_REPLAY_MODE)
    {
	if (m_demuxMode == DXR3_DEMUX_TRICK_MODE &&
	    m_trickState == DXR3_FREEZE)
	{
	    m_dxr3Device.SetPlayMode();
	    m_dxr3Device.SetSysClock(m_stopScr);
	    m_dxr3Device.EnableVideo();
	    m_dxr3Device.EnableAudio();
	    m_vBuf.Start();
	    m_aBuf.Start();
	    m_vBuf.WakeUp();
	    m_aBuf.WakeUp();
	}
	else
	{
	    m_synchState = DXR3_DEMUX_UNSYNCHED;
	    m_aBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);
	    m_vBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);
	}
    }
    m_demuxMode = DXR3_DEMUX_REPLAY_MODE;
}

// ==================================
void cDxr3DemuxDevice::SetTrickMode(eDxr3TrickState trickState, int Speed)
{
    m_demuxMode = DXR3_DEMUX_TRICK_MODE;
    m_trickState = trickState;
    m_dxr3Device.DisableAudio();

    if (m_demuxMode == DXR3_DEMUX_TRICK_MODE &&
	m_trickState == DXR3_FREEZE)
    {
	m_stopScr = m_dxr3Device.GetSysClock();
	// m_dxr3Device.Pause();
	m_vBuf.Stop();
	m_aBuf.Stop();
    }
    else
    {
	m_vBuf.Clear();
	m_aBuf.Clear();
    }

    m_ReUseFrame = 1;//Speed;
}

// ==================================
void cDxr3DemuxDevice::SetVideoOnlyMode()
{
    m_demuxMode = DXR3_DEMUX_VIDEO_ONLY_MODE;
    m_dxr3Device.DisableAudio();

    if (m_demuxMode == DXR3_DEMUX_TRICK_MODE && m_trickState == DXR3_FREEZE)
    {
	m_stopScr = m_dxr3Device.GetSysClock();
	// m_dxr3Device.Pause();
	m_vBuf.Stop();
	m_aBuf.Stop();
    }
    else
    {
	m_vBuf.Clear();
	m_aBuf.Clear();
    }
    m_dxr3Device.SetPlayMode();
}

// ==================================
void cDxr3DemuxDevice::StillPicture(const uint8_t* buf, int length)
{
    m_vBuf.Clear();
    m_aBuf.Clear();
    m_demuxMode = DXR3_DEMUX_TRICK_MODE;
    m_trickState = DXR3_FREEZE;
    m_dxr3Device.SingleStep();

    dsyslog("dxr3: demux: stillpicture length: %d", length);

    DemuxPes(buf, length);
    DemuxPes(buf, length);
    DemuxPes(buf, length);
}

// ==================================
int cDxr3DemuxDevice::DemuxPes(const uint8_t* buf, int length, bool bAc3Dts)
{
    uint32_t pts = 0;
    static uint32_t aPts = 0;
    static uint32_t vPts = 0;
    static uint32_t lastPts = 0;
    static bool bPlaySuc = false;
    static bool bPlayedFrame = false;
    int origLength = length;

    int scr = 0;
    int pcr = 0;

    scr = m_dxr3Device.GetSysClock();

    //printf("vBuf size = %d\n", m_vBuf.Available());
    //printf("aBuf size = %d\n", m_aBuf.Available());
    /*
    if (cDxr3ConfigData::Instance().GetAc3OutPut())
	cDxr3Interface::Instance().SetAudioDigitalAC3(); // !!! FIXME
    */

    if (m_pAudioThread->NeedResync() || m_pVideoThread->NeedResync())
    {
	Resync();
	if (m_demuxMode == DXR3_DEMUX_REPLAY_MODE)
	{
	    SetReplayMode();
	}
	m_aBuf.Clear();
	m_vBuf.Clear();
	m_pAudioThread->ClearResyncRequest();
	m_pVideoThread->ClearResyncRequest();
	m_aDecoder.Reset();
	lastPts = 0;
	aPts = 0;
	vPts = 0;
	bPlaySuc = false;
    }

    if (m_demuxMode == DXR3_DEMUX_OFF_MODE)
    {
	m_demuxMode = DXR3_DEMUX_TV_MODE;
	m_synchState = DXR3_DEMUX_UNSYNCHED;

	lastPts = 0;
	aPts = 0;
	vPts = 0;
	bPlaySuc = false;
	/*
	if (cDxr3ConfigData::Instance().GetAc3OutPut())
	    cDxr3Interface::Instance().SetAudioDigitalAC3(); // !!! FIXME
	*/
    }

    // find start code
    try
    {
	cDxr3PesFrame pesFrame;

	pesFrame.ExtractNextFrame(buf, length);

	while (pesFrame.IsValid())
	{
	    if (pesFrame.GetEsLength() > (uint32_t) VIDEO_MAX_FRAME_SIZE)
	    {
		throw (cDxr3PesFrame::PES_GENERAL_ERROR);
	    };
	    if (pesFrame.GetPts() != lastPts)
	    {
		pts = lastPts = pesFrame.GetPts();
	    }
	    else
	    {
		pts = 0;
	    }

	    if (pesFrame.GetPesDataType() == cDxr3PesFrame::PES_VIDEO_DATA)
	    {
		/*
		m_dxr3Device.PlayVideoFrame(pesFrame.GetEsStart(),
					    (int) (pesFrame.GetEsLength()));
		*/

		if (m_demuxMode == DXR3_DEMUX_TRICK_MODE)
		{
		    switch (pesFrame.GetFrameType())
		    {
		    case I_FRAME:
			dsyslog("dxr3: demux: I-frame");
			m_dxr3Device.SingleStep();
			bPlaySuc = true;
			//if (bPlayedFrame) return length;
			bPlayedFrame = true;
			//usleep(30000); // otherwise there is problem with audio (driver bug?)
			m_dxr3Device.SetHorizontalSize(pesFrame.GetHorizontalSize());
			m_dxr3Device.SetVerticalSize(pesFrame.GetVerticalSize());
			m_dxr3Device.PlayVideoFrame(pesFrame.GetEsStart(), (int) (pesFrame.GetEsLength()), m_ReUseFrame);
			break;

		    case UNKNOWN_FRAME:
			dsyslog("dxr3: demux: unknown frame");
			if (bPlaySuc)
			{
			    m_dxr3Device.PlayVideoFrame(pesFrame.GetEsStart(), (int) (pesFrame.GetEsLength()), m_ReUseFrame);
			}
			break;

		    default:
			dsyslog("dxr3: demux: default frame");
			if (bPlaySuc)
			{
			    m_dxr3Device.PlayVideoFrame(pesFrame.GetEsStart(), (int) (pesFrame.GetOffset()), m_ReUseFrame);
			}

			bPlaySuc = false;
			break;
		    }

		}
		else if (m_demuxMode == DXR3_DEMUX_VIDEO_ONLY_MODE)
		{
		    m_dxr3Device.PlayVideoFrame(pesFrame.GetEsStart(),
						(int)(pesFrame.GetEsLength()));
		}
		else if (m_synchState == DXR3_DEMUX_VIDEO_SYNCHED ||
			 m_synchState == DXR3_DEMUX_SYNCHED)
		{
		    m_dxr3Device.SetHorizontalSize(pesFrame.GetHorizontalSize());
		    m_dxr3Device.SetVerticalSize(pesFrame.GetVerticalSize());
		    while (!Poll(100));
		    cFixedLengthFrame* pTempFrame = m_vBuf.Push(pesFrame.GetEsStart(), (int) (pesFrame.GetEsLength()), pts, ftVideo);
		    if (!pTempFrame) /* Push Timeout */
			throw (cDxr3PesFrame::PES_GENERAL_ERROR);

		    pTempFrame->SetAspectRatio(pesFrame.GetAspectRatio());

		    m_aBuf.WakeUp();

		    if (m_vBuf.GetFillLevel() > 5 &&
			m_synchState != DXR3_DEMUX_SYNCHED)
		    {
			m_synchState = DXR3_DEMUX_SYNCHED;
			pcr = vPts - PRE_BUFFER_LENGTH;
			m_dxr3Device.SetSysClock(pcr);
			m_dxr3Device.SetPlayMode();
			m_dxr3Device.EnableVideo();
			m_dxr3Device.EnableAudio();
			m_vBuf.Start();
			m_aBuf.Start();
		    }
		}
		else
		{
		    if (pesFrame.GetFrameType() == I_FRAME)
		    {
			vPts = pts;

			m_dxr3Device.SetHorizontalSize(pesFrame.GetHorizontalSize());
			m_dxr3Device.SetVerticalSize(pesFrame.GetVerticalSize());
			cFixedLengthFrame* pTempFrame = m_vBuf.Push(pesFrame.GetEsStart(), (int) (pesFrame.GetEsLength()), pts, ftVideo);
			if (!pTempFrame) /* Push Timeout */
			    throw (cDxr3PesFrame::PES_GENERAL_ERROR);

			pTempFrame->SetAspectRatio(pesFrame.GetAspectRatio());

			if (m_synchState == DXR3_DEMUX_AUDIO_SYNCHED)
			{
			    m_synchState = DXR3_DEMUX_SYNCHED;
			}
			else
			{
			    m_synchState = DXR3_DEMUX_VIDEO_SYNCHED;
			}
			if (m_synchState == DXR3_DEMUX_SYNCHED)
			{
			    if (!vPts) vPts = aPts;
			    if (aPts < vPts)
			    {
				pcr = aPts - PRE_BUFFER_LENGTH;
			    }
			    else
			    {
				pcr = vPts - PRE_BUFFER_LENGTH;
			    }
			    m_dxr3Device.SetSysClock(pcr);
			    m_dxr3Device.SetPlayMode();
			    m_dxr3Device.EnableVideo();
			    m_dxr3Device.EnableAudio();
			    m_vBuf.Start();
			    m_aBuf.Start();
			}
		    }
		}

	    }
	    else if (pesFrame.GetPesDataType() == cDxr3PesFrame::PES_AUDIO_DATA
		     && m_demuxMode != DXR3_DEMUX_VIDEO_ONLY_MODE
		     && !cDxr3ConfigData::Instance().GetAc3OutPut())
	    {
		if (m_synchState == DXR3_DEMUX_AUDIO_SYNCHED ||
		    m_synchState == DXR3_DEMUX_SYNCHED)
		{
		    if (pts && m_synchState != DXR3_DEMUX_SYNCHED)
		    {
			m_synchState = DXR3_DEMUX_SYNCHED;
			pcr = aPts - PRE_BUFFER_LENGTH;
			m_dxr3Device.SetSysClock(pcr);
			m_dxr3Device.SetPlayMode();
			m_dxr3Device.EnableVideo();
			m_dxr3Device.EnableAudio();
			m_vBuf.Start();
			m_aBuf.Start();
		    }
		    while(!Poll(100));
		    m_aDecoder.Decode(pesFrame.GetEsStart(),
				      (int) (pesFrame.GetEsLength()),
				      pts, m_aBuf);

		}
		else
		{
		    if (pts)
		    {
			aPts = pts;

			m_aDecoder.Decode(pesFrame.GetEsStart(),
					  (int) (pesFrame.GetEsLength()),
					  pts, m_aBuf);

			if (m_synchState == DXR3_DEMUX_VIDEO_SYNCHED)
			{
			    m_synchState = DXR3_DEMUX_SYNCHED;
			}
			else
			{
			    m_synchState = DXR3_DEMUX_AUDIO_SYNCHED;
			}
			if (m_synchState == DXR3_DEMUX_SYNCHED)
			{
			    if (!vPts) vPts = aPts;
			    if (aPts < vPts)
			    {
				pcr = aPts - PRE_BUFFER_LENGTH;
			    }
			    else
			    {
				pcr = vPts - PRE_BUFFER_LENGTH;
			    }
			    m_dxr3Device.SetSysClock(pcr);
			    m_dxr3Device.SetPlayMode();
			    m_dxr3Device.EnableVideo();
			    m_dxr3Device.EnableAudio();
			    m_vBuf.Start();
			    m_aBuf.Start();
			}
		    }
		}
	    }
	    else if (pesFrame.GetPesDataType() == cDxr3PesFrame::PES_PRIVATE_DATA
		     && m_demuxMode != DXR3_DEMUX_VIDEO_ONLY_MODE
		     && !cDxr3ConfigData::Instance().GetAc3OutPut()
		     && !bAc3Dts)
	    {
		if (m_synchState == DXR3_DEMUX_AUDIO_SYNCHED ||
		    m_synchState == DXR3_DEMUX_SYNCHED)
		{
		    m_aDecoder.DecodeLpcm(pesFrame.GetEsStart(),
					  pesFrame.GetEsLength(), pts, m_aBuf);
		}
		else
		{
		    if (pts)
		    {
			aPts = pts;
			m_aDecoder.DecodeLpcm(pesFrame.GetEsStart(),
					      pesFrame.GetEsLength(),
					      pts, m_aBuf);

			if (m_synchState == DXR3_DEMUX_VIDEO_SYNCHED)
			{
			    m_synchState = DXR3_DEMUX_SYNCHED;
			}
			else
			{
			    m_synchState = DXR3_DEMUX_AUDIO_SYNCHED;
			}
			if (m_synchState == DXR3_DEMUX_SYNCHED)
			{
			    if (!vPts) vPts = aPts;
			    if (aPts < vPts)
			    {
				pcr = aPts - PRE_BUFFER_LENGTH;
			    }
			    else
			    {
				pcr = vPts - PRE_BUFFER_LENGTH;
			    }
			    m_dxr3Device.SetSysClock(pcr);
			    m_dxr3Device.SetPlayMode();
			    m_dxr3Device.EnableVideo();
			    m_dxr3Device.EnableAudio();
			    m_vBuf.Start();
			    m_aBuf.Start();
			}
		    }
		}
	    }
	    else if (pesFrame.GetPesDataType() == cDxr3PesFrame::PES_PRIVATE_DATA
		     && m_demuxMode != DXR3_DEMUX_VIDEO_ONLY_MODE
		     && cDxr3ConfigData::Instance().GetAc3OutPut()
		     && bAc3Dts)
	    {
		if (m_synchState == DXR3_DEMUX_AUDIO_SYNCHED ||
		    m_synchState == DXR3_DEMUX_SYNCHED)
		{
		    m_aDecoder.DecodeAc3Dts(pesFrame.GetPesStart(),
					    pesFrame.GetEsStart(),
					    pesFrame.GetEsLength(),
					    pts, m_aBuf);
		}
		else
		{
		    if (pts)
		    {
			aPts = pts;
			m_aDecoder.DecodeAc3Dts(pesFrame.GetPesStart(),
						pesFrame.GetEsStart(),
						pesFrame.GetEsLength(),
						pts, m_aBuf);

			if (m_synchState == DXR3_DEMUX_VIDEO_SYNCHED)
			{
			    m_synchState = DXR3_DEMUX_SYNCHED;
			}
			else
			{
			    m_synchState = DXR3_DEMUX_AUDIO_SYNCHED;
			}
			if (m_synchState == DXR3_DEMUX_SYNCHED)
			{
			    if (!vPts) vPts = aPts;
			    if (aPts < vPts)
			    {
				pcr = aPts - PRE_BUFFER_LENGTH;
			    }
			    else
			    {
				pcr = vPts - PRE_BUFFER_LENGTH;
			    }
			    m_dxr3Device.SetSysClock(pcr);
			    m_dxr3Device.SetPlayMode();
			    m_dxr3Device.EnableVideo();
			    m_dxr3Device.EnableAudio();
			    m_vBuf.Start();
			    m_aBuf.Start();
			}
		    }
		}

	    }

	    if (pesFrame.IsValid())
	    {
		pesFrame.ExtractNextFrame(pesFrame.GetNextStart(),
					  pesFrame.GetRemainingLength());
	    }

	}

	length -= pesFrame.GetRemainingLength();

	//if (m_demuxMode == DXR3_DEMUX_TRICK_MODE) return origLength;
	return length;
    }
    catch (cDxr3PesFrame::ePesFrameError err)
    {
	dsyslog("cDxr3DemuxDevice::DemuxPes() ePesFrameError skipping data and resync");
	Resync();
	return origLength;
    }
    catch (cDxr3SyncBuffer::eSyncBufferException err)
    {
	Stop();
	return origLength;
    }
}

// ==================================
int cDxr3DemuxDevice::DemuxAudioPes(const uint8_t* buf, int length)
{
    static int syncCounter = 0;
    int origLength = length;

    m_demuxMode = DXR3_DEMUX_AUDIO_ONLY_MODE;
    m_aBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);
    m_vBuf.SetDemuxMode(DXR3_DEMUX_REPLAY_MODE);

    try
    {
	cDxr3PesFrame pesFrame;

	pesFrame.ExtractNextFrame(buf, length);

	while (pesFrame.IsValid())
	{
	    if (pesFrame.GetPesDataType() == cDxr3PesFrame::PES_PRIVATE_DATA)
	    {
		if (m_synchState != DXR3_DEMUX_AUDIO_SYNCHED &&
		    syncCounter > 2)
		{
		    m_synchState = DXR3_DEMUX_AUDIO_SYNCHED;
		    m_dxr3Device.SetPlayMode();
		    m_dxr3Device.EnableVideo();
		    m_dxr3Device.EnableAudio();
		    m_vBuf.Start();
		    m_aBuf.Start();
		}
		if (m_synchState != DXR3_DEMUX_AUDIO_SYNCHED &&
		    syncCounter <= 2)
		{
		    syncCounter++;
		}
		while (!m_aBuf.Poll(100));
		m_aDecoder.DecodeLpcm(pesFrame.GetEsStart(),
				      pesFrame.GetEsLength(), 0, m_aBuf);

	    }

	    if (pesFrame.IsValid())
	    {
		pesFrame.ExtractNextFrame(pesFrame.GetNextStart(),
					  pesFrame.GetRemainingLength());
	    }
	}

	length -= pesFrame.GetRemainingLength();

	return length;
    }
    catch (cDxr3PesFrame::ePesFrameError err)
    {
	dsyslog("cDxr3DemuxDevice::DemuxAudioPes() ePesFrameError skipping data and resync");
	Stop();
	return origLength;
    }
}

// Local variables:
// mode: c++
// c-file-style: "stroustrup"
// c-file-offsets: ((inline-open . 0))
// indent-tabs-mode: t
// End:
