/* -LICENSE-START-
** Copyright (c) 2009 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
** 
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
** 
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>

#include "gstdecklinksrc.h"

#include "DeckLinkAPI.h"
#include "capture.h"


int videoOutputFile = -1;
int audioOutputFile = -1;

IDeckLink *deckLink;
IDeckLinkInput *deckLinkInput;
IDeckLinkDisplayModeIterator *displayModeIterator;

static BMDTimecodeFormat g_timecodeFormat = 0;

DeckLinkCaptureDelegate::DeckLinkCaptureDelegate ():m_refCount (0)
{
  pthread_mutex_init (&m_mutex, NULL);
}

DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate ()
{
  pthread_mutex_destroy (&m_mutex);
}

ULONG
DeckLinkCaptureDelegate::AddRef (void)
{
  pthread_mutex_lock (&m_mutex);
  m_refCount++;
  pthread_mutex_unlock (&m_mutex);

  return (ULONG) m_refCount;
}

ULONG
DeckLinkCaptureDelegate::Release (void)
{
  pthread_mutex_lock (&m_mutex);
  m_refCount--;
  pthread_mutex_unlock (&m_mutex);

  if (m_refCount == 0) {
    delete this;
    return 0;
  }

  return (ULONG) m_refCount;
}

HRESULT
DeckLinkCaptureDelegate::VideoInputFrameArrived (IDeckLinkVideoInputFrame *
    videoFrame, IDeckLinkAudioInputPacket * audioFrame)
{
  GstDecklinkSrc *decklinksrc = GST_DECKLINK_SRC (priv);

  // Handle Video Frame
  if (videoFrame) {
    if (videoFrame->GetFlags () & bmdFrameHasNoInputSource) {
      GST_DEBUG("Frame received - No input signal detected");
    } else {
      const char *timecodeString = NULL;
      if (g_timecodeFormat != 0) {
        IDeckLinkTimecode *timecode;
        if (videoFrame->GetTimecode (g_timecodeFormat, &timecode) == S_OK) {
          timecode->GetString (&timecodeString);
        }
      }

      GST_DEBUG("Frame received [%s] - %s - Size: %li bytes",
          timecodeString != NULL ? timecodeString : "No timecode",
          "Valid Frame",
          videoFrame->GetRowBytes () * videoFrame->GetHeight ());

      if (timecodeString)
        free ((void *) timecodeString);

      g_mutex_lock (decklinksrc->mutex);
      if (decklinksrc->video_frame != NULL) {
        decklinksrc->dropped_frames++;
      } else {
        videoFrame->AddRef();
        decklinksrc->video_frame = videoFrame;
        if (audioFrame) {
          audioFrame->AddRef();
          decklinksrc->audio_frame = audioFrame;
        }
      }
      g_cond_signal (decklinksrc->cond);
      g_mutex_unlock (decklinksrc->mutex);
    }
  }
  return S_OK;
}

HRESULT
DeckLinkCaptureDelegate::
VideoInputFormatChanged (BMDVideoInputFormatChangedEvents events,
    IDeckLinkDisplayMode * mode, BMDDetectedVideoInputFormatFlags)
{
  GST_ERROR("moo");
  return S_OK;
}

#ifdef unused
int
usage (int status)
{
  HRESULT result;
  IDeckLinkDisplayMode *displayMode;
  int displayModeCount = 0;

  fprintf (stderr,
      "Usage: Capture -m <mode id> [OPTIONS]\n" "\n" "    -m <mode id>:\n");

  while (displayModeIterator->Next (&displayMode) == S_OK) {
    char *displayModeString = NULL;

    result = displayMode->GetName ((const char **) &displayModeString);
    if (result == S_OK) {
      BMDTimeValue frameRateDuration, frameRateScale;
      displayMode->GetFrameRate (&frameRateDuration, &frameRateScale);

      fprintf (stderr, "        %2d:  %-20s \t %li x %li \t %g FPS\n",
          displayModeCount, displayModeString, displayMode->GetWidth (),
          displayMode->GetHeight (),
          (double) frameRateScale / (double) frameRateDuration);

      free (displayModeString);
      displayModeCount++;
    }
    // Release the IDeckLinkDisplayMode object to prevent a leak
    displayMode->Release ();
  }

  fprintf (stderr,
      "    -p <pixelformat>\n"
      "         0:  8 bit YUV (4:2:2) (default)\n"
      "         1:  10 bit YUV (4:2:2)\n"
      "         2:  10 bit RGB (4:4:4)\n"
      "    -t <format>          Print timecode\n"
      "     rp188:  RP 188\n"
      "      vitc:  VITC\n"
      "    serial:  Serial Timecode\n"
      "    -f <filename>        Filename raw video will be written to\n"
      "    -a <filename>        Filename raw audio will be written to\n"
      "    -c <channels>        Audio Channels (2, 8 or 16 - default is 2)\n"
      "    -s <depth>           Audio Sample Depth (16 or 32 - default is 16)\n"
      "    -n <frames>          Number of frames to capture (default is unlimited)\n"
      "    -3                   Capture Stereoscopic 3D (Requires 3D Hardware support)\n"
      "\n"
      "Capture video and/or audio to a file. Raw video and/or audio can be viewed with mplayer eg:\n"
      "\n"
      "    Capture -m2 -n 50 -f video.raw -a audio.raw\n"
      "    mplayer video.raw -demuxer rawvideo -rawvideo pal:uyvy -audiofile audio.raw -audio-demuxer 20 -rawaudio rate=48000\n");

  exit (status);
}

int
main (int argc, char *argv[])
{
  IDeckLinkIterator *deckLinkIterator = CreateDeckLinkIteratorInstance ();
  DeckLinkCaptureDelegate *delegate;
  IDeckLinkDisplayMode *displayMode;
  BMDVideoInputFlags inputFlags = 0;
  BMDDisplayMode selectedDisplayMode = bmdModeNTSC;
  BMDPixelFormat pixelFormat = bmdFormat8BitYUV;
  int displayModeCount = 0;
  int exitStatus = 1;
  int ch;
  bool foundDisplayMode = false;
  HRESULT result;

  pthread_mutex_init (&sleepMutex, NULL);
  pthread_cond_init (&sleepCond, NULL);

  if (!deckLinkIterator) {
    fprintf (stderr,
        "This application requires the DeckLink drivers installed.\n");
    goto bail;
  }

  /* Connect to the first DeckLink instance */
  result = deckLinkIterator->Next (&deckLink);
  if (result != S_OK) {
    fprintf (stderr, "No DeckLink PCI cards found.\n");
    goto bail;
  }

  if (deckLink->QueryInterface (IID_IDeckLinkInput,
          (void **) &deckLinkInput) != S_OK)
    goto bail;

  delegate = new DeckLinkCaptureDelegate ();
  deckLinkInput->SetCallback (delegate);

  // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
  result = deckLinkInput->GetDisplayModeIterator (&displayModeIterator);
  if (result != S_OK) {
    fprintf (stderr,
        "Could not obtain the video output display mode iterator - result = %08x\n",
        result);
    goto bail;
  }
  // Parse command line options
  while ((ch = getopt (argc, argv, "?h3c:s:f:a:m:n:p:t:")) != -1) {
    switch (ch) {
      case 'm':
        g_videoModeIndex = atoi (optarg);
        break;
      case 'c':
        g_audioChannels = atoi (optarg);
        if (g_audioChannels != 2 &&
            g_audioChannels != 8 && g_audioChannels != 16) {
          fprintf (stderr,
              "Invalid argument: Audio Channels must be either 2, 8 or 16\n");
          goto bail;
        }
        break;
      case 's':
        g_audioSampleDepth = atoi (optarg);
        if (g_audioSampleDepth != 16 && g_audioSampleDepth != 32) {
          fprintf (stderr,
              "Invalid argument: Audio Sample Depth must be either 16 bits or 32 bits\n");
          goto bail;
        }
        break;
      case 'f':
        g_videoOutputFile = optarg;
        break;
      case 'a':
        g_audioOutputFile = optarg;
        break;
      case 'n':
        g_maxFrames = atoi (optarg);
        break;
      case '3':
        inputFlags |= bmdVideoInputDualStream3D;
        break;
      case 'p':
        switch (atoi (optarg)) {
          case 0:
            pixelFormat = bmdFormat8BitYUV;
            break;
          case 1:
            pixelFormat = bmdFormat10BitYUV;
            break;
          case 2:
            pixelFormat = bmdFormat10BitRGB;
            break;
          default:
            fprintf (stderr, "Invalid argument: Pixel format %d is not valid",
                atoi (optarg));
            goto bail;
        }
        break;
      case 't':
        if (!strcmp (optarg, "rp188"))
          g_timecodeFormat = bmdTimecodeRP188;
        else if (!strcmp (optarg, "vitc"))
          g_timecodeFormat = bmdTimecodeVITC;
        else if (!strcmp (optarg, "serial"))
          g_timecodeFormat = bmdTimecodeSerial;
        else {
          fprintf (stderr,
              "Invalid argument: Timecode format \"%s\" is invalid\n", optarg);
          goto bail;
        }
        break;
      case '?':
      case 'h':
        usage (0);
    }
  }

  if (g_videoModeIndex < 0) {
    fprintf (stderr, "No video mode specified\n");
    usage (0);
  }

  if (g_videoOutputFile != NULL) {
    videoOutputFile =
        open (g_videoOutputFile, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (videoOutputFile < 0) {
      fprintf (stderr, "Could not open video output file \"%s\"\n",
          g_videoOutputFile);
      goto bail;
    }
  }
  if (g_audioOutputFile != NULL) {
    audioOutputFile =
        open (g_audioOutputFile, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (audioOutputFile < 0) {
      fprintf (stderr, "Could not open audio output file \"%s\"\n",
          g_audioOutputFile);
      goto bail;
    }
  }

  while (displayModeIterator->Next (&displayMode) == S_OK) {
    if (g_videoModeIndex == displayModeCount) {
      BMDDisplayModeSupport result;
      const char *displayModeName;

      foundDisplayMode = true;
      displayMode->GetName (&displayModeName);
      selectedDisplayMode = displayMode->GetDisplayMode ();

      deckLinkInput->DoesSupportVideoMode (selectedDisplayMode, pixelFormat,
          bmdVideoInputFlagDefault, &result, NULL);

      if (result == bmdDisplayModeNotSupported) {
        fprintf (stderr,
            "The display mode %s is not supported with the selected pixel format\n",
            displayModeName);
        goto bail;
      }

      if (inputFlags & bmdVideoInputDualStream3D) {
        if (!(displayMode->GetFlags () & bmdDisplayModeSupports3D)) {
          fprintf (stderr, "The display mode %s is not supported with 3D\n",
              displayModeName);
          goto bail;
        }
      }

      break;
    }
    displayModeCount++;
    displayMode->Release ();
  }

  if (!foundDisplayMode) {
    fprintf (stderr, "Invalid mode %d specified\n", g_videoModeIndex);
    goto bail;
  }

  result =
      deckLinkInput->EnableVideoInput (selectedDisplayMode, pixelFormat,
      inputFlags);
  if (result != S_OK) {
    fprintf (stderr,
        "Failed to enable video input. Is another application using the card?\n");
    goto bail;
  }

  result =
      deckLinkInput->EnableAudioInput (bmdAudioSampleRate48kHz,
      g_audioSampleDepth, g_audioChannels);
  if (result != S_OK) {
    goto bail;
  }

  result = deckLinkInput->StartStreams ();
  if (result != S_OK) {
    goto bail;
  }
  // All Okay.
  exitStatus = 0;

  // Block main thread until signal occurs
  pthread_mutex_lock (&sleepMutex);
  pthread_cond_wait (&sleepCond, &sleepMutex);
  pthread_mutex_unlock (&sleepMutex);
  fprintf (stderr, "Stopping Capture\n");

bail:

  if (videoOutputFile)
    close (videoOutputFile);
  if (audioOutputFile)
    close (audioOutputFile);

  if (displayModeIterator != NULL) {
    displayModeIterator->Release ();
    displayModeIterator = NULL;
  }

  if (deckLinkInput != NULL) {
    deckLinkInput->Release ();
    deckLinkInput = NULL;
  }

  if (deckLink != NULL) {
    deckLink->Release ();
    deckLink = NULL;
  }

  if (deckLinkIterator != NULL)
    deckLinkIterator->Release ();

  return exitStatus;
}
#endif