/*
 * inputffdvb.c: FF-DVB input for the Atmolight-plugin
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <vdr/device.h>
#include <vdr/tools.h>

#include "defs.h"
#include "calculations.h"
#include "input.h"
#include "inputffdvb.h"

#define DEV_VIDEO      "/dev/video"

// --- cInputFFDVB -----------------------------------------------------------

cInputFFDVB::cInputFFDVB()
{
}

cInputFFDVB::~cInputFFDVB()
{
}

bool cInputFFDVB::Open(const char* param)
{
  int res;

  // init variables
  ColorPacket.channel[0].r = ColorPacket.channel[0].g = ColorPacket.channel[0].g = 0;
  ColorPacket.channel[4] = ColorPacket.channel[3] = ColorPacket.channel[2] =
                           ColorPacket.channel[1] = ColorPacket.channel[0];
  fd = -1;
  nextframe = 0;
  actframe  = -1;

  char buffer[20];
  snprintf(buffer, sizeof(buffer), "%s%d", DEV_VIDEO, cDevice::PrimaryDevice()->DeviceNumber());
  if ((fd = open(buffer, O_RDWR)) == -1)
  {
    esyslog("Atmolight: Error opening %s", buffer);
    return false;
  }

  // get capabilities and check if capture is possible
  struct video_capability cap;
  ioctl(fd,VIDIOCGCAP,&cap);
  // dsyslog("Atmolight: videosizes: minh=%d maxh=%d minw=%d maxw=%d",cap.minheight,cap.maxheight,cap.minwidth,cap.maxwidth);
  if ((cap.minheight > CAP_HEIGHT) || (cap.maxheight < CAP_HEIGHT) ||  (cap.minwidth > CAP_WIDTH) || (cap.maxwidth < CAP_WIDTH))
  {
    esyslog("Atmolight: videosize not supported!");
    return false;
  }

  // set capture parameters
  struct video_window win;
  res = ioctl(fd,VIDIOCGWIN,&win);
  win.x = win.y = win.chromakey = 0;
  win.width  = CAP_WIDTH;
  win.height = CAP_HEIGHT;
  win.flags = 0;
  if (ioctl(fd,VIDIOCSWIN,&win) == -1)
  {
    esyslog("Atmolight: ioctl: VIDIOCSWIN");
    return false;
  }

  struct video_picture pic;
  res = ioctl(fd,VIDIOCGPICT,&pic);

  // set parameters of captured pictures
  pic.depth = 24;
  pic.palette = VIDEO_PALETTE_RGB24;
  pic.brightness = pic.hue = pic.colour = pic.contrast = pic.whiteness = 32767;
  if (ioctl(fd,VIDIOCSPICT,&pic) == -1)
  {
    esyslog("Atmolight: ioctl: VIDIOCSPICT");
    return false;
  }

  // assign capture buffer
  if (ioctl(fd,VIDIOCGMBUF,&m_buf) == -1)
  {
    esyslog("Atmolight: ioctl: VIDIOCGMBUF");
    return false;
  }

#if 0
  if (m_buf.frames < 2)
  {
    esyslog("Atmolight: videodevice isn't able to capture 2 frames!");
    return false;
  }
#endif

  map = mmap(0,m_buf.size,PROT_READ | PROT_WRITE, MAP_SHARED,fd,0);

  if (map == MAP_FAILED)
  {
    esyslog("Atmolight: framebuffer not initialized!");
    return false;
  }

  v_map.frame = 0;
  v_map.width = CAP_WIDTH;
  v_map.height = CAP_HEIGHT;
  v_map.format = VIDEO_PALETTE_RGB24;

  Start(); // start thread

  return true;
}

bool cInputFFDVB::Close(void)
{
  Cancel(); // stop thread

  cCondWait::SleepMs(100);

  if (map)
  {
    munmap(map, m_buf.size);
  }

  if (fd != -1)
  {
    close(fd);
    fd = -1;
    return true;
  }
  return false;
}

void cInputFFDVB::Action(void)
{
  cTimeMs t;

  while (Running()) // in this loop the picture is read and the colors are calculated
  {
    t.Set(0);

    CalcColors(); // read picture and calculate colors

    if (t.Elapsed() < 20) // wait 20ms to avoid 100% CPU usage
    {
      cCondWait::SleepMs(20);
    }
  }
}

void cInputFFDVB::CalcColors(void)
{
  tRGBColor RGB_Img;
  tHSVColor HSV_Img[IMAGE_SIZE];
  unsigned char *map;

  map = (unsigned char *) Capture();
  if (map == NULL) { return; } // handle start problems ;-)

  // generate HSV image
  for (int i = 0; i < IMAGE_SIZE; i++) // walk through map
  {
    // generate RGB pixel
    RGB_Img.b = *map++;
    RGB_Img.g = *map++;
    RGB_Img.r = *map++;

    // generate HSV pixel
    HSV_Img[i] = RGB2HSV(RGB_Img);
  }
  ColorPacket = CalcColorsAnalyzeHSV(HSV_Img);
}

void* cInputFFDVB::Capture(void)
{
  if (fd != -1)
  {
    v_map.frame = nextframe; // set frame to be captured until next

    if (ioctl(fd,VIDIOCMCAPTURE,&v_map) == -1)
    {
      dsyslog("Atmolight: ioctl: VIDIOCMCAPTURE");
      return NULL;
    }

    v_map.frame = actframe; // set frame to read out now

    if (actframe != -1)
    {
      if (ioctl(fd,VIDIOCSYNC,&v_map) == -1)
      {
        dsyslog("Atmolight: ioctl: VIDIOSYNC");
        return NULL;
      }

      if (actframe == 0)
      {
        actframe = 1;
        nextframe = 0;
        return (char*)map;
      }
      else
      {
        nextframe = 1;
        actframe = 0;
        return (char*)map+m_buf.offsets[1];
      }
    }
    else
    {
      actframe = 0;
      nextframe = 1;
      return NULL; // no frame captured yet
    }
  }
  else
  {
    return NULL; // videodev not open
  }
}
