#include "device.h"
#include "osd.h"
#include "thread.h"
#include "common.h"
#include <vdr/config.h>
#include <fmp.h>

cDeviceEm84xx* cDeviceEm84xx::mInstance = NULL;

cDeviceEm84xx::cDeviceEm84xx(void):
mThread(new cThreadEm84xx()),
mIsOpen(false)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);

   mOutputAspectRatio169 = false;
   mInputAspectRatio169  = false;
   mOutputHandle43on169 = 0;
   mOutputHandle169on43 = 0;
   mAspectRatioDataChanged = false;
   mSpdifForward = false;
   mPlayMode = pmNone;
   mAc3 = false;

   Open();
}

cDeviceEm84xx::~cDeviceEm84xx()
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   delete mThread;
   Close();
}

void cDeviceEm84xx::Open(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   if (!mIsOpen) {
      FMPOpen(FMPF_PROGRAM, KILOBYTE(32), 10, NULL, 0);
      FMPSet(FMPI_VIDEOOUT, FMPV_VIDEOOUT_COMPOSITE | FMPV_VIDEOOUT_TV |
             FMPV_VIDEOOUT_PAL);
      SetVideoFormat(Setup.VideoFormat);
      FMPSetVideoPortDimensions(720, 576);
      FMPSet(FMPI_AUDIO_OUTPUT,mSpdifForward);
      mIsOpen = true;
   }
}

void cDeviceEm84xx::Close(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   if (mIsOpen) {
      FMPClose();
      mIsOpen = false;
   }
}

bool cDeviceEm84xx::Initialize(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);

#ifdef EM_HASNO_DVD
   DWORD res = MPEGDriverEntry(NO_DRIVE);
#else
#ifdef EM_DVD_PRIMARY
   DWORD res = MPEGDriverEntry(PRIMARY_MASTER);
#else
   DWORD res = MPEGDriverEntry(SECONDARY_MASTER);
#endif
#endif

   if (res == 0) {
      mInstance = new cDeviceEm84xx;
      return true;
   }
   return false;
}

void cDeviceEm84xx::Destruct(void)
{
   MPEGDriverUnload();
   mInstance = NULL;
}

cDeviceEm84xx* cDeviceEm84xx::GetInstance(void)
{
   return mInstance;
}

void cDeviceEm84xx::MakePrimaryDevice(bool On)
{
   Dprintf("%s(%s)\n", __PRETTY_FUNCTION__, On ? "true" : "false");
   new cOsdProviderEm84xx;
}

bool cDeviceEm84xx::SetPlayMode(ePlayMode PlayMode)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, (int)PlayMode);

   mPlayMode = PlayMode;

   switch (PlayMode) {
      case pmNone:
         mThread->Stop();
         break;

      case pmAudioVideo:
      case pmAudioOnlyBlack:
      case pmAudioOnly:
      case pmVideoOnly:
         Open();
         mThread->Start();
         break;

      case pmExtern_THIS_SHOULD_BE_AVOIDED:
         mThread->Stop();
         Close();
         break;

      default:
         break;
   }

   return true;
}

int cDeviceEm84xx::PlayVideo(const uchar *Data, int Length)
{
   //Dprintf("%s(%p,%d)\n", __PRETTY_FUNCTION__, Data, Length);
   //Dprintf("PlayVideo length=%d, first four = %02x %02x %02x %02x\n", Length,
   //(int)Data[0], (int)Data[1], (int)Data[2], (int)Data[3]);

   if (((mPlayMode == pmAudioVideo) || (mPlayMode == pmVideoOnly)) && (Length > 9)) {
      /*uint32_t*/
      int pos = 0;
      int esLength = 0;

      for (; pos + 9 < Length; pos++) {
         if (!Data[pos] && !Data[pos+1] && Data[pos+2] == 0x01 && (Data[pos+3] >= 0xE0 && Data[pos+3] <= 0xEF)) {
            if ((Data[pos + 6] & 0xC0) == 0x80) {
               if (pos + 9 + Data[pos + 8] < Length) {
                  if ((((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5]) > 0) {
                     esLength = ((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5] + (int)6 - (9 + (int)Data[pos + 8]);

                     if (pos + 9 + Data[pos + 8] + esLength <= Length) {
                        //if ((pos+9+Data[pos + 8] + esLength) < Length)
                        GetAspectRatioVideoData(&Data[pos+9+Data[pos + 8]],esLength);
                     }
                  }
               }
            }
            else {
               /*uint32_t*/
               int fpos = pos + 6;
               esLength = ((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5];

               // this looked suspicious
               if (Length >= pos + 6 + esLength) {
                  while (Data[fpos] == 0xff) ++fpos; // skip stuffing bytes
                  if ((Data[fpos] & 0xC0) == 0x40) fpos += 2; // skip std buffer scale and size

                  if ((Data[fpos] & 0xF0) == 0x20) { // pts only
                     fpos += 5;
                  }
                  else if ((Data[fpos] & 0xF0) == 0x30) { // pts and dts
                     fpos += 10;
                  }
                  else {
                     ++fpos;
                  }

                  if (esLength) esLength = esLength - (fpos - pos - 6);
                  //if ((fpos+esLength) < Length)
                  GetAspectRatioVideoData(&Data[fpos],esLength);
               }
            }
         }
      }
   }

   return mThread->PlayPacket(Data, Length);
}

void cDeviceEm84xx::GetAspectRatioVideoData(const uchar* Data, /*uint32_t*/ int Length)
{
   for (/*uint32_t*/ int i = 0; Length > /*(uint32_t)*/ 8 && i < Length - 8; i++) {
      if (Data[i] == 0 && Data[i+1] == 0 && Data[i+2] == 1) {

         if ((Data[i + 3] & 0xFF) == 0xB3) {
            switch ((Data[i + 7]) & 0xF0) {

               case 0x20:
                  SwitchAspectRatio(false);
                  break;

               case 0x30:
                  SwitchAspectRatio(true);
                  break;
               default:
                  SwitchAspectRatio(false);
                  break;
            }
         }
      }
   }
}

#if VDRVERSNUM >= 10342
int cDeviceEm84xx::PlayAudio(const uchar *Data, int Length, uchar Id)
#else
int cDeviceEm84xx::PlayAudio(const uchar *Data, int Length)
#endif
{
   //Dprintf("%s(%p,%d)\n", __PRETTY_FUNCTION__, Data, Length);
   return mThread->PlayPacket(Data, Length);
}

int64_t cDeviceEm84xx::GetSTC(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   return FMPGet(FMPI_STC);
}

void cDeviceEm84xx::TrickSpeed(int Speed)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Speed);
   //Dprintf("TrickMode %lu\n", FMPSet(FMPI_TRICKMODE, FMPV_TRICKMODE_START));
   mThread->TrickSpeed(Speed);
}

void cDeviceEm84xx::Clear(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   FMPFlush(FALSE);
}

void cDeviceEm84xx::Play(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   mThread->Play();
}

void cDeviceEm84xx::Freeze(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   mThread->Freeze();
}

void cDeviceEm84xx::Mute(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   FMPSet(FMPI_MUTE, FMPGet(FMPI_MUTE) ? 1 : 0);
}

void cDeviceEm84xx::StillPicture(const uchar *Data, int Length)
{
   Dprintf("%s(%p,%d)\n", __PRETTY_FUNCTION__, Data, Length);
}

bool cDeviceEm84xx::Poll(cPoller &Poller, int TimeoutMs)
{
   mThread->AddPoller(Poller);
   return Poller.Poll(TimeoutMs);
}

bool cDeviceEm84xx::Flush(int TimeoutMs)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, TimeoutMs);
   FMPFlush(FALSE);
   return true;
}

void cDeviceEm84xx::SetVideoFormat(bool VideoFormat16_9)
{
   FMPSet(FMPI_VIDEOASPECT, VideoFormat16_9 ? FMPV_VIDEOASPECT_16x9_16x9_NORMAL
          : FMPV_VIDEOASPECT_4x3_4x3_NORMAL);
}

eVideoSystem cDeviceEm84xx::GetVideoSystem(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   DWORD mode = FMPGet(FMPI_VIDEOOUT);
   return(mode & FMPV_VIDEOOUT_STANDARDTV_MASK) == FMPV_VIDEOOUT_NTSC
   ? vsNTSC
   : vsPAL;
}

void cDeviceEm84xx::SetAudioTrackDevice(eTrackType Type)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Type);
}

int cDeviceEm84xx::GetAudioChannelDevice(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   return FMPGet(FMPI_AUDIO_MODE);
}

void cDeviceEm84xx::SetAudioChannelDevice(int AudioChannel)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, AudioChannel);
   FMPSet(FMPI_AUDIO_MODE, AudioChannel);
}

void cDeviceEm84xx::SetVolumeDevice(int Volume)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Volume);
   int fmpvol = Volume * 100 / 255;
   FMPSet(FMPI_LEFT_VOLUME, fmpvol);
   FMPSet(FMPI_RIGHT_VOLUME, fmpvol);

}

void cDeviceEm84xx::SetDigitalAudioDevice(bool On)
{
   Dprintf("%s(%s)\n", __PRETTY_FUNCTION__, On ? "true" : "false");

   if (On != mAc3) {
      mThread->Stop();
      mThread->Start();
   }

   const tTrackId* track = GetTrack(GetCurrentAudioTrack());
   if ((track != NULL) && !On) {
      FMPSet(FMPI_AUDIO_SELECT,track->id-0xC0);
   }

   mAc3 = On;
}

void cDeviceEm84xx::SwitchAspectRatio(bool Is169)
{
   if ((Is169 == mInputAspectRatio169) && (!mAspectRatioDataChanged))
      return;

   mInputAspectRatio169 = Is169;

   if (mInputAspectRatio169) {
      if (mOutputAspectRatio169)
         FMPSet(FMPI_VIDEOASPECT, FMPV_VIDEOASPECT_16x9_16x9_NORMAL);
      else {
         if (mOutputHandle169on43 == 0)
            FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_LETTERBOX);
         else
            FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_PANSCAN);
      }
   }
   else {
      if (!mOutputAspectRatio169)
         FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_4x3_NORMAL);
      else {
         if (mOutputHandle43on169 == 0)
            FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_ON);

         else if (mOutputHandle43on169 == 1)
            FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_OFF);

         else
            FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_4x3_NORMAL);

      }
   }
   mAspectRatioDataChanged = false;
}

void cDeviceEm84xx::SetOutputAspectRatio(bool Is169)
{
   mOutputAspectRatio169 = Is169;
   mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetOutputHandle43on169(int Handle)
{
   mOutputHandle43on169 = Handle;
   mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetOutputHandle169on43(int Handle)
{
   mOutputHandle169on43 = Handle;
   mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetAudioOutput(bool EnableSpdifForward)
{
   if (EnableSpdifForward != mSpdifForward) {
      mSpdifForward = EnableSpdifForward;
      mThread->Stop();
      FMPSet(FMPI_AUDIO_OUTPUT,mSpdifForward);
      mThread->Start();
   }
}

