/*
 * softdevice.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: softdevice.c,v 1.21 2004/07/25 09:08:18 stefan Exp $
 */

#include <getopt.h>
#include <stdlib.h>
#include <vdr/interface.h>
#include <vdr/plugin.h>
#include <vdr/player.h>

#include <vdr/osd.h>

#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include "i18n.h"
#include "setup-softdevice2net.h"

#include "video-dummy.h"

#undef  VOUT_DEFAULT


#include "video-net.h"

#ifndef VOUT_DEFAULT
#define VOUT_DEFAULT  VOUT_NET
#endif

#include "audio.h"
#include "mpeg2decoder.h"
#include "utils.h"
static const char *VERSION        = "0.0.1";
static const char *DESCRIPTION    = "Softdevice for network streaming";
static const char *MAINMENUENTRY  = "Softdevice2Net";

#define INBUF_SIZE 4096

char filename[256];

#if VDRVERSNUM >= 10307

// --- cSoftOsd -----------------------------------------------
class cSoftOsd : public cOsd {
private:
    cVideoOut *videoOut;
protected:
public:
    cSoftOsd(cVideoOut *VideoOut, int XOfs, int XOfs);
    virtual ~cSoftOsd();
    virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas);
    virtual void Flush(void);
};

cSoftOsd::cSoftOsd(cVideoOut *VideoOut, int X, int Y) : cOsd(X, Y)
{
    videoOut = VideoOut;
    videoOut->OpenOSD(X, Y);
}

cSoftOsd::~cSoftOsd()
{
    if (videoOut) {
	videoOut->CloseOSD();
	videoOut=0;
    }
}

eOsdError cSoftOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
{
    eOsdError Result = cOsd::CanHandleAreas(Areas, NumAreas);

    return Result;
}

void cSoftOsd::Flush(void)
{
    cBitmap *Bitmap;

    for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++)
    {
	videoOut->Refresh(Bitmap);
    }
}

// --- cSoftOsdProvider -----------------------------------------------
class cSoftOsdProvider : public cOsdProvider {
private:
    cVideoOut *videoOut;
    cOsd *osd;
public:
    cSoftOsdProvider(cVideoOut *VideoOut);
    virtual cOsd *CreateOsd(int Left, int Top);
};

cSoftOsdProvider::cSoftOsdProvider(cVideoOut *VideoOut)
{
    videoOut = VideoOut;
}

cOsd * cSoftOsdProvider::CreateOsd(int Left, int Top)
{
    osd = new cSoftOsd(videoOut, Left, Top);
    return osd;
}

#else

// --- cSoftOsd -----------------------------------------------
class cSoftOsd : public cOsdBase {
private:
  int xOfs, yOfs;
  cVideoOut *videoOut;
protected:
  virtual bool OpenWindow(cWindow *Window);
  virtual void CommitWindow(cWindow *Window);
  virtual void ShowWindow(cWindow *Window);
  virtual void HideWindow(cWindow *Window, bool Hide);
  virtual void MoveWindow(cWindow *Window, int X, int Y);
  virtual void CloseWindow(cWindow *Window);
public:
  cSoftOsd(cVideoOut *VideoOut, int XOfs, int XOfs);
  virtual ~cSoftOsd();
};

cSoftOsd::cSoftOsd(cVideoOut *VideoOut, int X, int Y)
    :cOsdBase(X, Y) {
    videoOut=VideoOut;
    xOfs=X; // this position should be recalculated
    yOfs=Y;
    fprintf(stderr,"[softdevice] OSD-Position at %d x %d\n",X,Y);
    videoOut->OpenOSD(X, Y);
}
cSoftOsd::~cSoftOsd() {
    if (videoOut) {
	videoOut->CloseOSD();
	videoOut=0;
    }
    fprintf(stderr,"[softdevice] OSD is off now\n");
}

bool cSoftOsd::OpenWindow(cWindow *Window) {
    return videoOut->OpenWindow(Window);
}

void cSoftOsd::CommitWindow(cWindow *Window) {
    videoOut->CommitWindow(Window);
}

void cSoftOsd::ShowWindow(cWindow *Window) {
    videoOut->ShowWindow(Window);
}
void cSoftOsd::HideWindow(cWindow *Window, bool Hide) {
    videoOut->HideWindow(Window, Hide);
}

void cSoftOsd::MoveWindow(cWindow *Window, int x, int y) {
    videoOut->MoveWindow(Window, x, y);
}

void cSoftOsd::CloseWindow(cWindow *Window) {
    videoOut->CloseWindow(Window);
}

#endif

// --- cSoftDevice ------------------------------------------------------------
class cPluginSoftDevice : public cPlugin {
private:
  int voutMethod;

public:
  cPluginSoftDevice(void);
  virtual ~cPluginSoftDevice();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return tr(DESCRIPTION); }
  virtual const char *CommandLineHelp(void);
  virtual bool Initialize(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return tr(MAINMENUENTRY); }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  };

// Global variables that control the overall behaviour:


// --- cSoftDevice --------------------------------------------------
class cSoftDevice : public cDevice {
private:
  cMpeg2Decoder *decoder;

#if VDRVERSNUM < 10307
  cOsdBase *OSD;
#endif

  cVideoOut *videoOut;
  cAudioOut *audioOut;
  int       outMethod;

  bool      freezeModeEnabled;
  cMutex    playMutex;
  cCondVar  readyForPlayCondVar;

public:
  cSoftDevice(int method = 0);
  ~cSoftDevice();
  virtual bool HasDecoder(void) const;
  virtual bool CanReplay(void) const;
  virtual bool SetPlayMode(ePlayMode PlayMode);
  virtual void TrickSpeed(int Speed);
  virtual void Clear(void);
  virtual void Play(void);
  virtual void Freeze(void);
  virtual void Mute(void);
  virtual void SetVolumeDevice (int Volume);
  virtual void StillPicture(const uchar *Data, int Length);
  virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
  virtual int PlayVideo(const uchar *Data, int Length);
#if VDRVERSNUM >= 10307
  virtual int ProvidesCa(const cChannel *Channel) const;
  virtual void MakePrimaryDevice(bool On);
#else
  int ProvidesCa(int Ca);
  virtual cOsdBase *NewOsd(int x, int y);
#endif
};

cSoftDevice::cSoftDevice(int method)
{
    freezeModeEnabled = false;

    fprintf(stderr,"[softdevice] Initializing Video Out\n");
    fprintf(stderr,
            "[softdevice] ffmpeg version(%s) build(%d)\n",
            FFMPEG_VERSION, LIBAVCODEC_BUILD);
    setupStore.outputMethod = method;
    switch (method) {
      case VOUT_NET:
        videoOut=new cFBVideoOut(filename);
        break;
      default:
        break;
    }
    fprintf(stderr,"[softdevice] Video Out seems to be OK\n");
    fprintf(stderr,"[softdevice] Initializing Audio Out\n");
    audioOut=new cAlsaAudioOut();
    fprintf(stderr,"[softdevice] Audio out seems to be OK\n");
    fprintf(stderr,"[softdevice] A/V devices initialized, now initializing MPEG2 Decoder\n");
    decoder= new cMpeg2Decoder(audioOut, videoOut);
}

cSoftDevice::~cSoftDevice()
{
    delete(decoder);
    delete(audioOut);
    delete(videoOut);
}



#if VDRVERSNUM >= 10307

void cSoftDevice::MakePrimaryDevice(bool On)
{
  fprintf(stderr,"cSoftDevice::MakePrimaryDevice\n");

  new cSoftOsdProvider(videoOut);
}

int cSoftDevice::ProvidesCa(const cChannel *Channel) const
{
    return 0;
}

#else

cOsdBase *cSoftDevice::NewOsd(int X, int Y)
{
    return new cSoftOsd(videoOut,X,Y);
}

int cSoftDevice::ProvidesCa(int Ca)
{
    return 0;
}

#endif




bool cSoftDevice::HasDecoder(void) const
{
    return true; // We can decode MPEG2
}

bool cSoftDevice::CanReplay(void) const
{
    return true;  // We can replay
}

bool cSoftDevice::SetPlayMode(ePlayMode PlayMode)
{
    switch(PlayMode) {
      // FIXME - Implement audio or video only Playmode (is this really needed?)
      case pmAudioVideo:
          decoder->Start();
          playMutex.Lock();
          freezeModeEnabled = false;
          playMutex.Unlock();
	    break;
	default:
	    decoder->Stop();
	    break;

    }
//    printf("setting Playmode not implemented yet... %d\n",PlayMode);
    return true;
}

void cSoftDevice::TrickSpeed(int Speed)
{
    fprintf(stderr,"[softdevice] Trickspeed not implemented yet...\n");
}
void cSoftDevice::Clear(void)
{
    fprintf(stderr,"[softdevice] Clear not implemented yet...\n");
}
void cSoftDevice::Play(void)
{
    playMutex.Lock();
    freezeModeEnabled = false;
    playMutex.Unlock();
    readyForPlayCondVar.Broadcast();
}
void cSoftDevice::Freeze(void)
{
    playMutex.Lock();
    freezeModeEnabled = true;
    playMutex.Unlock();
}
void cSoftDevice::Mute(void)
{
    fprintf(stderr,"[softdevice] Mute not implemented yet...\n");
}

void cSoftDevice::SetVolumeDevice(int Volume)
{
  fprintf (stderr, "[softdevice] should set volume to %d\n", Volume);
  audioOut->SetVolume(Volume);
}

void cSoftDevice::StillPicture(const uchar *Data, int Length)
{
    decoder->StillPicture((uchar *)Data,Length);
}

bool cSoftDevice::Poll(cPoller &Poller, int TimeoutMs)
{
  playMutex.Lock();
  if (freezeModeEnabled)
    readyForPlayCondVar.TimedWait(playMutex,TimeoutMs);
  playMutex.Unlock();
  return true;
}


int cSoftDevice::PlayVideo(const uchar *Data, int Length)
{
    bool freezeMode;

    playMutex.Lock();
    freezeMode = freezeModeEnabled;
    playMutex.Unlock();

    if (freezeMode)
      return 0;

    int result=decoder->Decode(Data, Length);
    // restart the decoder
    if (result == -1) {
        delete(decoder);
        decoder= new cMpeg2Decoder(audioOut,videoOut);
        return 0;
    }
    return Length;
}

// --- cPluginSoftDevice ----------------------------------------------------------

cPluginSoftDevice::cPluginSoftDevice(void)
{
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
#ifdef VOUT_DEFAULT
  voutMethod = VOUT_DEFAULT;
#else
  voutMethod = 0;
#endif
}

cPluginSoftDevice::~cPluginSoftDevice()
{
  // Clean up after yourself!
}

const char *cPluginSoftDevice::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return
  "  -vo net:                  enable output via framebuffer\n"
  "\n";
}

bool cPluginSoftDevice::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  fprintf (stderr, "[softdevice] processing args\n");
  if (argc == 2) {
    fprintf (stderr, "[softdevice]   argv [1] = %s\n", argv [1]);
    strcpy(filename, argv[1]);
  } 
  return true;
}

bool cPluginSoftDevice::Start(void)
{
  // Start any background activities the plugin shall perform.
  RegisterI18n(Phrases);
  return true;
}

bool cPluginSoftDevice::Initialize(void)
{
  // Start any background activities the plugin shall perform.
  fprintf(stderr,"[softdevice] initializing Plugin\n");
  new cSoftDevice(voutMethod);
  return true;
}

void cPluginSoftDevice::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginSoftDevice::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.
  fprintf (stderr, "[MainMenuAction]\n");
  return new cMenuSetupSoftdevice;
//  return NULL;
}

cMenuSetupPage *cPluginSoftDevice::SetupMenu(void)
{
  // Return our setup menu
  return new cMenuSetupSoftdevice;
}

bool cPluginSoftDevice::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.
  return setupStore.SetupParse(Name, Value);
}

VDRPLUGINCREATOR(cPluginSoftDevice); // Don't touch this!
