#include "SDL.h"

#include "MPEG.h"

#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#ifndef O_BINARY
#define O_BINARY 0
#endif

MPEG::MPEG(const char * name, bool SDLaudio) :
  MPEGerror()
{
  SDL_RWops *source;

  mpeg_mem = 0;

  source = SDL_RWFromFile(name, "rb");
  if (!source) {
    InitErrorState();
    SetError(SDL_GetError());
    return;
  }
  Init(source, SDLaudio);
}

MPEG::MPEG(int Mpeg_FD, bool SDLaudio) :
  MPEGerror()
{
  SDL_RWops *source;

  mpeg_mem = 0;

  // *** FIXME we're leaking a bit of memory for the FILE *
  // best solution would be to have SDL_RWFromFD
  FILE *file = fdopen(Mpeg_FD, "rb");
  if (!file) {
    InitErrorState();
    SetError(strerror(errno));
    return;
  }

  source = SDL_RWFromFP(file,false);
  if (!source) {
    InitErrorState();
    SetError(SDL_GetError());
    return;
  }
  Init(source, SDLaudio);
}

MPEG::MPEG(void *data, int size, bool SDLaudio) :
  MPEGerror()
{
  SDL_RWops *source;

  // The semantics are that the data passed in should be copied
  // (?)
  mpeg_mem = new char[size];
  memcpy(mpeg_mem, data, size);

  source = SDL_RWFromMem(mpeg_mem, size);
  if (!source) {
    InitErrorState();
    SetError(SDL_GetError());
    return;
  }
  Init(source, SDLaudio);
}

MPEG::MPEG(SDL_RWops *mpeg_source, bool SDLaudio) :
  MPEGerror()
{
  mpeg_mem = 0;
  Init(mpeg_source, SDLaudio);
}

void MPEG::Init(SDL_RWops *mpeg_source, bool SDLaudio)
{
    source = mpeg_source;
    sdlaudio = SDLaudio;

    /* Create the system that will parse the MPEG stream */
    system = new MPEGsystem(source);

    /* Initialize everything to invalid values for cleanup */
    error = NULL;

    audiostream = videostream = NULL;
    audioaction = NULL;
    videoaction = NULL;
    audio = NULL;
    video = NULL;
    audioaction_enabled = videoaction_enabled = false;
    loop = false;
    pause = false;

    parse_stream_list();

    EnableAudio(audioaction_enabled);
    EnableVideo(videoaction_enabled);

    if ( ! audiostream && ! videostream ) {
      SetError("No audio/video stream found in MPEG");
    }

    if ( system && system->WasError() ) {
      SetError(system->TheError());
    }

    if ( audio && audio->WasError() ) {
      SetError(audio->TheError());
    }

    if ( video && video->WasError() ) {
      SetError(video->TheError());
    }

    if ( WasError() ) {
      SetError(TheError());
    }
}

void MPEG::InitErrorState() {
    audio = NULL;
    video = NULL;
    system = NULL;
    error = NULL;
    source = NULL;

    audiostream = videostream = NULL;
    audioaction = NULL;
    videoaction = NULL;
    audio = NULL;
    video = NULL;
    audioaction_enabled = videoaction_enabled = false;
    loop = false;
    pause = false;
}

MPEG::~MPEG()
{
  Stop();
  if(video) delete video;
  if(audio) delete audio;
  if(system) delete system;
  
  if(source) SDL_RWclose(source);
  if ( mpeg_mem )
    delete[] mpeg_mem;
}

bool MPEG::AudioEnabled(void) {
  return(audioaction_enabled);
}
void MPEG::EnableAudio(bool enabled) {
  if ( enabled && ! audioaction ) {
    enabled = false;
  }
  audioaction_enabled = enabled;

  /* Stop currently playing stream, if necessary */
  if ( audioaction && ! audioaction_enabled ) {
    audioaction->Stop();
  } 
  /* Set the video time source */
  if ( videoaction ) {
    if ( audioaction_enabled ) {
      videoaction->SetTimeSource(audioaction);
    } else {
      videoaction->SetTimeSource(NULL);
    }
  }
  if(audiostream)
    audiostream->enable(enabled);
}
bool MPEG::VideoEnabled(void) {
  return(videoaction_enabled);
}
void MPEG::EnableVideo(bool enabled) {
  if ( enabled && ! videoaction ) {
    enabled = false;
  }
  videoaction_enabled = enabled;

  /* Stop currently playing stream, if necessary */
  if ( videoaction && ! videoaction_enabled ) {
    videoaction->Stop();
  } 
  if(videostream)
    videostream->enable(enabled);
}

/* MPEG actions */
void MPEG::Loop(bool toggle) {
  loop = toggle;
}
void MPEG::Play(void) {
  if ( AudioEnabled() ) {
    audioaction->Play();
  }
  if ( VideoEnabled() ) {
    videoaction->Play();
  }
}
void MPEG::Stop(void) {
  if ( VideoEnabled() ) {
    videoaction->Stop();
  }
  if ( AudioEnabled() ) {
    audioaction->Stop();
  }
}

void MPEG::Rewind(void) {
  seekIntoStream(0);
}

void MPEG::Pause(void) {
  pause = !pause;

  if ( VideoEnabled() ) {
    videoaction->Pause();
  }
  if ( AudioEnabled() ) {
    audioaction->Pause();
  }
}

/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
MPEGstatus MPEG::GetStatus(void) {
  MPEGstatus status;

  status = MPEG_STOPPED;
  if ( VideoEnabled() ) {
		/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
    switch (videoaction->GetStatus()) {
      case MPEG_PLAYING:
        status = MPEG_PLAYING;
      break;
      default:
      break;
    }
  }
  if ( AudioEnabled() ) {
		/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
    switch (audioaction->GetStatus()) {
      case MPEG_PLAYING:
        status = MPEG_PLAYING;
      break;
      default:
      break;
    }
  }

  if(status == MPEG_STOPPED && loop && !pause)
  {
    /* Here we go again */
    Rewind();
    Play();

    if ( VideoEnabled() ) {
		/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
      switch (videoaction->GetStatus()) {
      case MPEG_PLAYING:
        status = MPEG_PLAYING;
	break;
        default:
        break;
      }
    }
    if ( AudioEnabled() ) {
		/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
      switch (audioaction->GetStatus()) {
      case MPEG_PLAYING:
        status = MPEG_PLAYING;
	break;
        default:
        break;
      }
    }
  }

  return(status);
}


/* MPEG audio actions */
bool MPEG::GetAudioInfo(MPEG_AudioInfo *info) {
  if ( AudioEnabled() ) {
    return(audioaction->GetAudioInfo(info));
  }
  return(false);
}
void MPEG::Volume(int vol) {
  if ( AudioEnabled() ) {
    audioaction->Volume(vol);
  }
}
bool MPEG::WantedSpec(SDL_AudioSpec *wanted) {
  if( audiostream ) {
    return(GetAudio()->WantedSpec(wanted));
  }
  return(false);
}
void MPEG::ActualSpec(const SDL_AudioSpec *actual) {
  if( audiostream ) {
    GetAudio()->ActualSpec(actual);
  }
}
MPEGaudio *MPEG::GetAudio(void) { // Simple accessor used in the C interface
  return audio;
}

/* MPEG video actions */
bool MPEG::GetVideoInfo(MPEG_VideoInfo *info) {
  if ( VideoEnabled() ) {
    return(videoaction->GetVideoInfo(info));
  }
  return(false);
}
bool MPEG::SetDisplay(SDL_Surface *dst, SDL_mutex *lock,
		MPEG_DisplayCallback callback) {
  if ( VideoEnabled() ) {
    return(videoaction->SetDisplay(dst, lock, callback));
  }
  return(false);
}
void MPEG::MoveDisplay(int x, int y) {
  if ( VideoEnabled() ) {
    videoaction->MoveDisplay(x, y);
  }
}
void MPEG::ScaleDisplayXY(int w, int h) {
  if ( VideoEnabled() ) {
    videoaction->ScaleDisplayXY(w, h);
  }
}
void MPEG::SetDisplayRegion(int x, int y, int w, int h) {
  if ( VideoEnabled() ) {
    videoaction->SetDisplayRegion(x, y, w, h);
  }
}
void MPEG::RenderFrame(int frame)
{
    if ( VideoEnabled() ) {
        videoaction->RenderFrame(frame);
    }
}
void MPEG::RenderFinal(SDL_Surface *dst, int x, int y)
{
    Stop();
    if ( VideoEnabled() ) {
        videoaction->RenderFinal(dst, x, y);
    }
    Rewind();
}

SMPEG_Filter * MPEG::Filter(SMPEG_Filter * filter)
{
  if ( VideoEnabled() ) {
    return(videoaction->Filter(filter));
  }
  return 0;
}

void MPEG::Seek(int position)
{
  int was_playing = 0;

  /* Cannot seek past end of file */
  if((Uint32)position > system->TotalSize()) return;
  
	/* Michel Darricau from eProcess <mdarricau@eprocess.fr> conflict name with popcorn */
  /* get info whrether we need to restart playing at the end */
  if( GetStatus() == MPEG_PLAYING )
    was_playing = 1;

  if(!seekIntoStream(position)) return;

  /* If we were playing and not rewind then play again */
  if (was_playing)
    Play();

  if (VideoEnabled() && !was_playing) 
    videoaction->RenderFrame(0);

  if ( pause && VideoEnabled() ) {
    videoaction->Pause();
  }
  if ( pause && AudioEnabled() ) {
    audioaction->Pause();
  }
}

bool MPEG::seekIntoStream(int position)
{
  /* First we stop everything */
  Stop();

  /* Go to the desired position into file */
  if(!system->Seek(position)) return(false);

  /* Seek first aligned data */
  if(audiostream && audioaction_enabled)
    while(audiostream->time() == -1)
      if ( ! audiostream->next_packet() ) return false;
  if(videostream && videoaction_enabled)
    while(videostream->time() == -1)
      if ( ! videostream->next_packet() ) return false;

  /* Calculating current play time on audio only makes sense when there
     is no video */  
  if ( audioaction && !videoaction) {
    audioaction->Rewind();
    audioaction->ResetSynchro(system->TimeElapsedAudio(position));
  }
  /* And forget what we previouly buffered */
  else if ( audioaction ) {
    audioaction->Rewind();
    audioaction->ResetSynchro(audiostream->time());
  }
  if ( videoaction ) {
    videoaction->Rewind();
    videoaction->ResetSynchro(videostream->time());
  }

  return(true);
}

void MPEG::Skip(float seconds)
{
  if(system->get_stream(SYSTEM_STREAMID))
  {
    system->Skip(seconds);
  }
  else
  {
    /* No system information in MPEG */
    if( VideoEnabled() ) videoaction->Skip(seconds);
    if( AudioEnabled() ) audioaction->Skip(seconds);
  }
}

void MPEG::GetSystemInfo(MPEG_SystemInfo * sinfo)
{
  sinfo->total_size = system->TotalSize();
  sinfo->current_offset = system->Tell();
  sinfo->total_time = system->TotalTime();

  /* Get current time from audio or video decoder */
  /* TODO: move timing reference in MPEGsystem    */
  sinfo->current_time = 0;
  if( videoaction ) 
    sinfo->current_time = videoaction->Time();
  if( audioaction )
    sinfo->current_time = audioaction->Time();
}

void MPEG::parse_stream_list()
{
  MPEGstream ** stream_list;
  register int i;

  /* A new thread is created for each video and audio */
  /* stream                                           */ 
  /* TODO: support MPEG systems containing more than  */
  /*       one audio or video stream                  */
  i = 0;
  do
  {
    /* Retreive the list of streams */
    stream_list = system->GetStreamList();

    switch(stream_list[i]->streamid)
    {
      case SYSTEM_STREAMID:
      break;

      case AUDIO_STREAMID:
	audiostream = stream_list[i];
	audioaction_enabled = true;
	audiostream->next_packet();
	audio = new MPEGaudio(audiostream, sdlaudio);
	audioaction = audio;
      break;

      case VIDEO_STREAMID:
	videostream = stream_list[i];
	videoaction_enabled = true;
	videostream->next_packet();
	video = new MPEGvideo(videostream);
	videoaction = video;
      break;
    }

    i++;
  }
  while(stream_list[i]);
}
