/*****************************************************************************************/
/*
 * File name: VioPlayback.cpp
 * Location:  ...\Matrox Imaging\MILxxx\Examples\BoardSpecific\vio\C++\vioplayback
 *             
 *
 * Synopsis:  This example demonstrates how to:
 *             - playback a compressed sequence on the VIO system.
 *
 *            This program uses 2 threads:
 *
 *             - The DiskBuffering thread is responsible to read each compressed image
 *               from disk, decompress it and put it in a FIFO.
 *
 *             - The main thread is responsible to take an image from the FIFO and copy
 *               it into the display.
 *
 */

// Headers.
#include <mil.h>
#include <queue>

#if M_MIL_USE_WINDOWS
#include <windows.h>
#else
#include <pthread.h>
#endif

// Number of images in the buffering grab queue.
#define BUFFERING_SIZE_MAX 50
MIL_UINT32 MFTYPE DiskBufferingThread(void *TPar);

// Thread parameters structure.
typedef struct ThreadParam
   {
   MIL_TEXT_PTR pFileName;
   MIL_INT SequenceSizeBand, SequenceSizeX, SequenceSizeY;
   MIL_INT FrameCount;

   std::queue<MIL_ID> FifoDecompression; // FIFO containing buffers to be decompressed.
   std::queue<MIL_ID> FifoDisplay;       // FIFO containing buffers to be displayed.

#if M_MIL_USE_WINDOWS
   CRITICAL_SECTION FifoLock;
#else
   pthread_mutex_t FifoLock;
#endif

   MIL_INT   Exit;
   } THREAD_PARAM;

#if M_MIL_USE_LINUX
int ExecuteCommand (const char *cmd, char **buffer, int *bufferSize)
   {
   FILE *sysCmdOutput = popen (cmd, "r");
   int bufferIndex = 0;

   while (*bufferSize ==
          fread (&((*buffer)[bufferIndex]), sizeof (char), *bufferSize, sysCmdOutput))
      {
      char *tmp = new char [(*bufferSize)*2];
      bufferIndex += *bufferSize;
      if (tmp == NULL)
         {
         fprintf (stderr,"Unable to allocate memory to read file in one buffer %i\n",
                  (*bufferSize)*2);
         return -1;
         }
      memset(tmp, 0, (*bufferSize)*2);
      memcpy(tmp, *buffer, *bufferSize);
      delete [] *buffer;
      *buffer = tmp;
      *bufferSize *= 2;
      }

   pclose (sysCmdOutput);
   return 0;
   }
#endif

// Main function.
int MosMain(int argc, char *argv[])
   {
   MIL_ID   MilApplication = M_NULL;
   MIL_ID   MilSystem = M_NULL;
   MIL_ID   MilDisplay = M_NULL;
   MIL_ID   DiskBufferingThreadId = M_NULL;
   MIL_ID   MilImageDisp = M_NULL;
   MIL_INT  DisplaySizeBand, DisplaySizeX, DisplaySizeY;
   MIL_INT  FrameCount=0, NbFramesReplayed=0;
   MIL_UINT BufferingSize = BUFFERING_SIZE_MAX;
   MIL_INT  DisplayType = 0;
   double   FrameRate;
   double   TimeSequence, TimeWait = 0;
   double   TotalReplay = 0.0;

   MIL_UINT i = 0;
   THREAD_PARAM ThreadParam;

   MappAlloc(M_NULL, M_DEFAULT, &MilApplication);
   MsysAlloc(M_DEFAULT, M_SYSTEM_VIO, M_DEFAULT, M_DEFAULT, &MilSystem);

   // Allocate VIO display auxiliary output.
   MdispAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_DEFAULT, &MilDisplay);

   // Enable no tearing on auxiliary display.
   MdispControl(MilDisplay, M_NO_TEARING, M_ENABLE);

   // Inquire information about the display.
   MdispInquire(MilDisplay, M_DISPLAY_TYPE, &DisplayType);

   // Select the file that we want to decompress.
#if M_MIL_USE_WINDOWS
   OPENFILENAME ofn;
   MIL_TEXT_CHAR szFile[MAX_PATH];

   // Initialize OPENFILENAME
   ZeroMemory(&ofn, sizeof(ofn));
   ofn.lStructSize = sizeof(ofn);
   ofn.lpstrFile = szFile;
   ofn.lpstrFile[0] = '\0';
   ofn.nMaxFile = sizeof(szFile);
   ofn.lpstrFilter = MIL_TEXT("Avi Files\0*.avi\0All Files\0*.*\0");
   ofn.nFilterIndex = 1;
   ofn.lpstrFileTitle = NULL;
   ofn.nMaxFileTitle = 0;
   ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

   MosPrintf(MIL_TEXT("Please select avi file to replay.\n"));

   // Display the Open dialog box.
   BOOL FileNameFound = false;
   DWORD lRetVal = 1;
   do
      {
      FileNameFound = GetOpenFileName(&ofn);
      if(!FileNameFound)
         {
         lRetVal = CommDlgExtendedError();
         if(lRetVal == 0) // Cancel Selected.
            break;
         }
      }
   while(!FileNameFound);

   if(lRetVal == 0)
      goto EXIT;

   ThreadParam.pFileName = ofn.lpstrFile;
#else
   MosPrintf(MIL_TEXT("Please select avi file to replay.\n"));
   char FileDialogChooser[] =
      "zenity  --title=\"Please select avi file to replay\" --file-selection";
   int FilenameLength = 512;
   char *Filename = new char[FilenameLength];
   bool bValidFilename = false;

   while(!bValidFilename)
      {
      memset(Filename, 0, FilenameLength);
      ExecuteCommand(FileDialogChooser, &Filename, &FilenameLength);
      if(strlen(Filename))
         {
         char *NewLine = strchr(Filename, '\n');
         *NewLine = 0;
         ThreadParam.pFileName = Filename;
         bValidFilename= true;
         }
      else
         {
         goto EXIT;
         }
      }
#endif

   // Inquire information about the sequence.
   MbufDiskInquire(ThreadParam.pFileName, M_NUMBER_OF_IMAGES, &FrameCount);
   MbufDiskInquire(ThreadParam.pFileName, M_FRAME_RATE, &FrameRate);
   ThreadParam.FrameCount = FrameCount;

   MbufDiskInquire(ThreadParam.pFileName, M_SIZE_BAND, &ThreadParam.SequenceSizeBand);
   MbufDiskInquire(ThreadParam.pFileName, M_SIZE_X,    &ThreadParam.SequenceSizeX);
   MbufDiskInquire(ThreadParam.pFileName, M_SIZE_Y,    &ThreadParam.SequenceSizeY);

   // Allocate buffers for buffering.
   for(i = 0; i < BufferingSize; i++)
      {
      MIL_ID BufferingBuffer = M_NULL;
      MbufAllocColor(MilSystem,
                     ThreadParam.SequenceSizeBand,
                     ThreadParam.SequenceSizeX,
                     ThreadParam.SequenceSizeY,
                     8 + M_UNSIGNED,
                     M_IMAGE + (ThreadParam.SequenceSizeBand == 3 ? (M_YUV16 + M_PACKED): 0),
                     &BufferingBuffer);

      MbufClear(BufferingBuffer, M_COLOR_BLACK);

      if(BufferingBuffer)
         ThreadParam.FifoDecompression.push(BufferingBuffer);
      else
         break;
      }

   // Allocate display buffer.
   if(MdispInquire(MilDisplay, M_DISPLAY_TYPE, 0) == M_AUXILIARY)
      {
      DisplaySizeBand = MdispInquire(MilDisplay, M_SIZE_BAND, 0);
      DisplaySizeX = MdispInquire(MilDisplay, M_SIZE_X, 0);
      DisplaySizeY = MdispInquire(MilDisplay, M_SIZE_Y, 0);
      }
   else
      {
      DisplaySizeBand = ThreadParam.SequenceSizeBand;
      DisplaySizeX = ThreadParam.SequenceSizeX;
      DisplaySizeY = ThreadParam.SequenceSizeY;
      }

   MbufAllocColor(MilSystem,
                  DisplaySizeBand,
                  DisplaySizeX,
                  DisplaySizeY,
                  8 + M_UNSIGNED,
                  M_IMAGE + M_DISP  + (DisplaySizeBand == 3 ? (M_YUV16 + M_PACKED): 0),
                  &MilImageDisp);

   MbufClear(MilImageDisp, M_COLOR_BLACK);
   MdispSelect(MilDisplay, MilImageDisp);

   MosPrintf(MIL_TEXT("Sequence: %s \n"), ThreadParam.pFileName);
   MosPrintf(MIL_TEXT("Sequence SizeX: %d SizeY: %d \n"), ThreadParam.SequenceSizeX,
             ThreadParam.SequenceSizeY);

   MosPrintf(MIL_TEXT("Frame count: %d\n"), FrameCount);
   MosPrintf(MIL_TEXT("Frame rate: %2.1f f/s\n"), FrameRate);
   MosPrintf(MIL_TEXT("Buffering Size: %d buffers\n"), BufferingSize);

   // Allocate disk buffering thread.
#if M_MIL_USE_WINDOWS
   InitializeCriticalSection(&ThreadParam.FifoLock);
#else
   pthread_mutex_init(&ThreadParam.FifoLock, NULL);
#endif
   ThreadParam.Exit = false;
   MthrAlloc(MilSystem, M_THREAD, M_DEFAULT, &DiskBufferingThread, &ThreadParam,
             &DiskBufferingThreadId);

   // Wait for the disk buffering thread to fill the buffers.
   do
      {
      MosSleep(100);
      MosPrintf(MIL_TEXT("\rLoading %2.0f%%  "),
                ((ThreadParam.FifoDisplay.size()*100.0) / BufferingSize));
      }
   while(ThreadParam.FifoDisplay.size() < BufferingSize-1);

   MosPrintf(MIL_TEXT("Done.\n\n"));

   NbFramesReplayed = 0;
   MappTimer(M_DEFAULT, M_TIMER_RESET, M_NULL);

   while (!MosKbhit())
      {
      MIL_ID MilBufferToDisplay = M_NULL;
      NbFramesReplayed++;

      // Wait if buffering FIFO is empty.
      while(ThreadParam.FifoDisplay.empty())
         {
         MosPrintf(MIL_TEXT("\n\nCannot display sequence at the recorded frame rate.\n"));
         MosPrintf(MIL_TEXT("Disk access and JPEG decompression operations on this system ")
                   MIL_TEXT("are too slow.\n\n"));

         TotalReplay = 0.0;
         MosSleep(1000);
         }

      // Print statistics.
      MappTimer(M_DEFAULT, M_TIMER_READ, &TotalReplay);
      if((NbFramesReplayed % 10) == 0)
         {
         MosPrintf(MIL_TEXT("Frame #: %d, Frame rate: %.1f fps, Buffering: %.0f%%         \r"),
                   NbFramesReplayed,
                   NbFramesReplayed/TotalReplay,
                   (ThreadParam.FifoDisplay.size()* 100.0) / BufferingSize);
         }

      // Get buffer from UsedPool FIFO.
#if M_MIL_USE_WINDOWS
      EnterCriticalSection(&ThreadParam.FifoLock);
#else
      pthread_mutex_lock(&ThreadParam.FifoLock);
#endif
      MilBufferToDisplay = ThreadParam.FifoDisplay.front();
      ThreadParam.FifoDisplay.pop();
#if M_MIL_USE_WINDOWS
      LeaveCriticalSection(&ThreadParam.FifoLock);
#else
      pthread_mutex_unlock(&ThreadParam.FifoLock);
#endif

      // Copy to the display.
      MbufCopy(MilBufferToDisplay, MilImageDisp);

      // Put buffer back in FreePool FIFO.
      if(MilBufferToDisplay)
         {
#if M_MIL_USE_WINDOWS
         EnterCriticalSection(&ThreadParam.FifoLock);
#else
         pthread_mutex_lock(&ThreadParam.FifoLock);
#endif
         ThreadParam.FifoDecompression.push(MilBufferToDisplay);
#if M_MIL_USE_WINDOWS
         LeaveCriticalSection(&ThreadParam.FifoLock);
#else
         pthread_mutex_unlock(&ThreadParam.FifoLock);
#endif
         }

      // Wait to have a proper frame rate when in windowed mode.
      // Not needed in auxiliary display mode.
      if(!(DisplayType & M_AUXILIARY))
         {
         MappTimer(M_DEFAULT, M_TIMER_READ, &TimeSequence);
         TimeWait = ((1/FrameRate) - (TimeSequence - TotalReplay));
         MappTimer(M_DEFAULT, M_TIMER_WAIT, &TimeWait);
         }
      }

   // Close buffering thread.
   ThreadParam.Exit = true;
   MthrWait(DiskBufferingThreadId, M_THREAD_END_WAIT, M_NULL);
   MthrFree(DiskBufferingThreadId);

   MosGetchar();

   while(!ThreadParam.FifoDisplay.empty())
      {
      MbufFree(ThreadParam.FifoDisplay.front());
      ThreadParam.FifoDisplay.pop();
      }

   while(!ThreadParam.FifoDecompression.empty())
      {
      MbufFree(ThreadParam.FifoDecompression.front());
      ThreadParam.FifoDecompression.pop();
      }

#if M_MIL_USE_WINDOWS
   DeleteCriticalSection(&ThreadParam.FifoLock);
#else
   pthread_mutex_destroy(&ThreadParam.FifoLock);
#endif

   MbufFree(MilImageDisp);

 EXIT:

   MdispFree(MilDisplay);
   MsysFree(MilSystem);
   MappFree(MilApplication);

   return 0;
   }


/////////////////////////////////////////////////////////////////////////
// The DiskBuffering thread is responsible to read each compressed image
// from disk, decompress it and put it in a FIFO.
MIL_UINT32 MFTYPE DiskBufferingThread(void *ThreadParameters)
   {
   THREAD_PARAM *pThreadParam = (THREAD_PARAM *) ThreadParameters;
   MIL_ID CompressionBuffer = M_NULL;
   MIL_INT n = 0;

   // Allocate compression buffer.
   MbufAllocColor(M_DEFAULT_HOST, pThreadParam->SequenceSizeBand, pThreadParam->SequenceSizeX,
                  pThreadParam->SequenceSizeY, 8 + M_UNSIGNED,
                  M_IMAGE + M_COMPRESS + M_JPEG_LOSSY +
                     ((pThreadParam->SequenceSizeBand == 3) ?M_YUV16 + M_PACKED: 0),
                  &CompressionBuffer);

   // Open the sequence file.
   MbufImportSequence(pThreadParam->pFileName, M_DEFAULT, M_NULL,
                      M_NULL, M_NULL, M_NULL, M_NULL, M_OPEN);

   while(!pThreadParam->Exit)
      {
      MIL_ID MilBuf = M_NULL;

      // Read image from disk.
      MbufImportSequence(pThreadParam->pFileName, M_DEFAULT, M_LOAD,
                         M_NULL, &CompressionBuffer, n % pThreadParam->FrameCount, 1, M_READ);
      n++;

      // Decompress image from disk and put it in the FIFO.
      // Wait if there are no free buffers.
      while(pThreadParam->FifoDecompression.size() <= 1  && !pThreadParam->Exit)
         MosSleep(10);

      if(pThreadParam->Exit) break;

      // Get a free buffer from the FreePool FIFO.
#if M_MIL_USE_WINDOWS
      EnterCriticalSection(&pThreadParam->FifoLock);
#else
      pthread_mutex_lock(&pThreadParam->FifoLock);
#endif
      MilBuf = pThreadParam->FifoDecompression.front();
      pThreadParam->FifoDecompression.pop();
#if M_MIL_USE_WINDOWS
      LeaveCriticalSection(&pThreadParam->FifoLock);
#else
      pthread_mutex_unlock(&pThreadParam->FifoLock);
#endif

      // Decompress the image.
      MbufCopy(CompressionBuffer, MilBuf);
   
      // Put the decompressed image in the UsedPool FIFO.
#if M_MIL_USE_WINDOWS
      EnterCriticalSection(&pThreadParam->FifoLock);
#else
      pthread_mutex_lock(&pThreadParam->FifoLock);
#endif
      pThreadParam->FifoDisplay.push(MilBuf);
#if M_MIL_USE_WINDOWS
      LeaveCriticalSection(&pThreadParam->FifoLock);
#else
      pthread_mutex_unlock(&pThreadParam->FifoLock);
#endif
      }

   // Close the sequence file.
   MbufImportSequence(pThreadParam->pFileName, M_DEFAULT, M_NULL,
                      M_NULL, M_NULL, M_NULL, M_NULL, M_CLOSE);

   // Free compress buffer.
   MbufFree(CompressionBuffer);


   return 0;
   }