/*****************************************************************************/
/*
 *      novaserver.c -- MPEG server on top of the NetStream 2000 driver
 *
 *      Copyright (C) 2000 Sigma Designs
 *                    written by Nicolas Vignal <nicolas_vignal@sdesigns.com>
 *                    modified by Emmanuel Michon <emmanuel_michon@sdesigns.com>
 *                                Pascal Cannenterre <pascal_cannenterre@sdesigns.com>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 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 General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*****************************************************************************/
#define _LARGEFILE64_SOURCE 1

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <fcntl.h>
// #include <signal.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/user.h>
#include <sys/time.h>

#include <pthread.h>
#include <semaphore.h>

#define _DEFINE_FMP_TYPES_
#include "fmp.h"

#include "novaclientserver.h"
#include "novaserver.h"

#include "rm84mpegcmn.h"
#include "mpegsrv.h"

DWORD Event_Notifications(DWORD, DWORD, DWORD);

char defaultREALmagicdevice[]=DEFAULTREALMAGICDEVICE;
char *REALmagicdevice;

MESSAGE_QUEUE_OBJECT serversidequeue;

int b_stop = 0;
int make_playfileserver_exit = 0;
char strFileName[MAXMSGLENGTH]; 

THREADHANDLE fileplayer = NULL;
THREADHANDLE messagequeuelistener;
THREADHANDLE playfileserver;


void PlayFileThread(char * filename)
{
  int fd;
  int bytesread = 1, rc = FMPE_OK;
  FMP_BUFFER FMPBuf;

  b_stop = 0;
  
  /* Open the file related to filename */
  fd=open(filename,O_RDONLY|O_LARGEFILE);
  if (fd==-1)
    {
      perror("Open (novaserver.c)failed !!");
      fileplayer = NULL;
      pthread_exit(NULL); 
    }
  INFOFORSTDOUT("\nReady to play ...\n");
  
  while (bytesread && (!b_stop))
    {
      // get a pointer to a free buffer
      rc = FMPGetBuffer (&FMPBuf, TRUE);


      if (rc != FMPE_OK)
	{
	  INFOFORSTDOUT("FMPError : %s\n", FMPGetLastError(rc));
	  break;
	}
      // signals can cause harmless EINTR
      while (((bytesread=read(fd,FMPBuf.pBuffer,FMPBuf.dwBufferSize))==-1)
	     &&(errno==EINTR));
      if (bytesread==-1) bytesread=0;
      FMPBuf.dwDataSize=bytesread;
      // the buffer is ready to be queued
      FMPPush (&FMPBuf);
    }
  lseek (fd,0,SEEK_SET);
  close(fd);
  fileplayer = NULL;
  pthread_exit(NULL);
  return;
}

void spawnPlayFile(char *filename)
{
  while(fileplayer != NULL)
    usleep(200000);
  fileplayer = OSCreateThread("FilePlayerThread",UNUSED_ARG,
			      (FUNCPTR)PlayFileThread,(int)filename);
}

DWORD CloseFile()
{
  DWORD FMPreturn = FMPE_OK;
  
      if(fileplayer != NULL)
	{
	  if(!b_stop)
	    b_stop = 1;
	  while(fileplayer != NULL)
	    usleep(150000);
	  FMPreturn = FMPStop();
	  FMPreturn = FMPClose();
	}
      return FMPreturn;
}

DWORD StopFile()
{
  DWORD FMPreturn = FMPE_OK;
  
  if(fileplayer != NULL)
    {
      if(!b_stop)
	b_stop = 1;
      while(fileplayer != NULL)
	usleep(150000);
      FMPStop();
      FMPPause();
      spawnPlayFile(strFileName);
    }
  return FMPreturn;
}

DWORD Event_Notifications(DWORD dwContext, DWORD dwMsg, DWORD dwValue)
{
  switch (dwMsg)
    {
    case FMPM_ERROR:
      INFOFORSTDOUT("\n\nProblem !\n\n");
      break;
    case FMPM_STARVATION:
      INFOFORSTDOUT("\n\nStarving !\n\n");
      break;
    case FMPM_EOS:
      FMPStop();
      FMPPlay();
      spawnPlayFile(strFileName);
      break;
    case FMPM_TRICK_MODE_CHANGE:

      break;
	  
    case FMPM_AUTOPAUSE:
      break;
	
    default:
      break;
    }
  return 0;
}

void FMPonmessagequeueServer()
{
  while (1) {
    ClientQuestion Q;
    ServerAnswer A;
    memset (&A,0,sizeof(ServerAnswer));
    
    OSBlockingReceiveFrommessagequeue(serversidequeue,(char *)&Q,
				      sizeof(ClientQuestion),MSGFROMCLIENTTOSERVER);
#define CASETEMPLATEPLAY(x, y) case x: \
                                      A.dwReturn = y ( ); \
                                      break;
    
#define CASETEMPLATE(x, y, rest...) case x: \
                                      A.dwReturn = y ( rest ); \
                                      break;   
   
    switch (Q.dwCommand) {
      
      /* special message causes clean exit of FMPonmessagequeueServer thread.
	 (better than OSCancelThread) */
    case DEATHMESSAGE:
      // if someone is connected, there will be some side effect
      OSDestroymessagequeue(serversidequeue);
      return;
      break;
      CASETEMPLATEPLAY(FMP_PLAY,     FMPPlay); 
      CASETEMPLATEPLAY(FMP_STOP,     FMPStop);
      CASETEMPLATEPLAY(FMP_PAUSE,    FMPPause);
      CASETEMPLATEPLAY(FMP_CLOSE,    FMPClose);
   
      CASETEMPLATE(FMP_GET,                  FMPGet,                 Q.dwArg1);
      CASETEMPLATE(FMP_GET_PROFILE_WORD,     FMPGetProfileWord,      Q.dwArg1);
      CASETEMPLATE(FMP_SET,                  FMPSet,                 Q.dwArg1,Q.dwArg2);
      CASETEMPLATE(FMP_WRITE_PROFILE_WORD,   FMPWriteProfileWord,    Q.dwArg1,Q.dwArg2);
      CASETEMPLATE(FMP_SET_DESTINATION,      FMPSetDestination,      Q.dwArg1,Q.dwArg2,Q.dwArg3,Q.dwArg4);
      CASETEMPLATE(FMP_ANALOG_OVERLAY,       FMPAnalogOverlay,       Q.dwArg1,Q.dwArg2,Q.dwArg3,Q.dwArg4,Q.dwArg5,Q.dwArg6);
      CASETEMPLATE(FMP_OPEN,       FMPOpen,       Q.dwArg1,Q.dwArg2,Q.dwArg3, (PFMPCALLBACK)Event_Notifications,Q.dwArg5);
    case FMP_STOPFILE:
      A.dwReturn  = StopFile();
      break;
    case FMP_CLOSEFILE:
      A.dwReturn  = CloseFile();
      break;
    case FMP_DRIVERUNLOAD:
      make_playfileserver_exit = 1;
      MPEGDriverUnload();
      get_out_of_dummy = 1;
      pthread_exit(NULL);
      return;
      break; 
    default:
      SUICIDE(ERRORUNKNOWNRPCONMSGQ);
      break;
    }
    
    OSPostInmessagequeue(serversidequeue,(char *)&A,sizeof(ServerAnswer),MSGFROMSERVERTOCLIENT);   
  }
}


void FMPonmessagequeuePlayFileServer()
{
  while(!make_playfileserver_exit)
    {
      FMPPROPERTY_INFO P;
      RM_WRITE RMW;
      DWORD sizeOut = 0, fVal = 0;
      ClientPlayRequest PQ;
      ServerAnswer B;

      memset (&P,0,sizeof(FMPPROPERTY_INFO));
      memset (&B,0,sizeof(ServerAnswer));
      memset (&RMW,0,sizeof(RM_WRITE));

      OSBlockingReceiveFrommessagequeue(serversidequeue,(char *)&PQ,
					sizeof(ClientPlayRequest),MSGFROMCLIENTTOPLAYFILESERVER);
      switch (PQ.dwPlayCommand)
	{
	case FMP_PLAYFILE:	    
	  FMPPlay();
	  strcpy(strFileName, PQ.filename);
	  spawnPlayFile(PQ.filename);
	  B.dwReturn = FMP_PLAYFILE;
	  break;
	case FMP_DRIVERUNLOAD:
	  pthread_exit(NULL);
	  return;
	  break;

	case FMP_PROPERTY:
	  memcpy(&P,PQ.filename,sizeof(FMPPROPERTY_INFO));
	  if(P.dwPropSizeIn == sizeof(DWORD))
	    {
	      fVal = P.PropData.Data;
	      sizeOut = P.dwPropSizeOut;
	      // INFOFORSTDOUT("\nBefore FMPProperty()\n");
	      /* 
	       * INFOFORSTDOUT("List of args we pass to the func : %lx, %lx, %lx, %lx, %lx, %lx, %lx\n",
	       * P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, fVal, P.dwPropSizeIn, sizeOut); 
	       */
	      B.dwReturn = FMPProperty(P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, &fVal, P.dwPropSizeIn, &sizeOut );
	      /*
	       * INFOFORSTDOUT("List of args returns by the func : %lx, %lx, %lx, %lx, %lx, %lx, %lx\n",
	       *    P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, fVal, P.dwPropSizeIn, sizeOut);
	       */
	      /* INFOFORSTDOUT("\nReturn Value = %ld\n" ,B.dwReturn); */
	      /* INFOFORSTDOUT("\nAfter FMPProperty()\n"); */
	      P.PropData.Data = fVal;
	      P.dwPropSizeOut = sizeOut;
	    }
	  else
	    {
	      RMW.Data = P.PropData.Data;
	      RMW.Addr = P.PropData.Addr;	
	      /*
	       * INFOFORSTDOUT("List of args we pass to the func : %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx\n",
	       *    P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, fVal, RMW.Addr, RMW.Data, sizeOut);
	       */
	      B.dwReturn = FMPProperty(P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, &RMW, P.dwPropSizeIn, &sizeOut);
	      /*
	       * INFOFORSTDOUT("List of args returns by the func : %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx\n",
	       * P.dwFlags, P.dwPropSet, P.dwPropId, P.dwPropFlags, fVal, RMW.Addr, RMW.Data, sizeOut);
	       */
	       /* INFOFORSTDOUT("\nReturn Value = %ld\n" ,B.dwReturn); */
	      
	      // INFOFORSTDOUT("\nAfter FMPProperty()\n");
	      P.PropData.Data = RMW.Data;
	      P.PropData.Addr = RMW.Addr;
	      P.dwPropSizeOut = sizeOut;
	    }
	  
	  OSPostInmessagequeue(serversidequeue,(char *)&P,sizeof(FMPPROPERTY_INFO),MSGFROMPLAYFILESERVERTOCLIENT_PROPERTY);
	  break;

	default:
	  SUICIDE(ERRORUNKNOWNRPCONMSGQ);
	  break;
	}
      
      OSPostInmessagequeue(serversidequeue,(char *)&B,sizeof(ServerAnswer),MSGFROMPLAYFILESERVERTOCLIENT);
      
    }
}

void FMPcreatemessagequeue()
{
	  int msgkey;

	  REALmagicdevice=getenv(REALMAGICDEVICEFROMENV);
	  if (REALmagicdevice==NULL)
		  REALmagicdevice=defaultREALmagicdevice;

	  msgkey=ftok(REALmagicdevice,IPC_PROJECT_KEY);
	  if (msgkey==-1) PERRORIZE("ftok failed");
	  serversidequeue=OSCreatemessagequeue(msgkey,IPC_CREAT|REASONABLE_UMASK);

	// creation of the message queue and thread listening for FMP commands on it

	messagequeuelistener =
	  OSCreateThread((TCHAR *)UNUSED_ARG,UNUSED_ARG,(FUNCPTR)FMPonmessagequeueServer,(int)UNUSED_ARG);
	playfileserver =
	  OSCreateThread((TCHAR *)UNUSED_ARG,UNUSED_ARG,(FUNCPTR)FMPonmessagequeuePlayFileServer,(int)UNUSED_ARG);
}
