/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Thomas Bretz 1/2008 <mailto:tbretz@astro.uni-wuerzburg.de>
!
!   Copyright: MAGIC Software Development, 2000-2008
!
!
\* ======================================================================== */

/////////////////////////////////////////////////////////////////////////////
//
// MVideo
//
// Interface to Video4Linux at a simple level
//
/////////////////////////////////////////////////////////////////////////////
#include "MVideo.h"

// iostream
#include <iostream>

// open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>    // usleep
#include <errno.h>     // errno
#include <sys/mman.h>  // mmap
#include <sys/ioctl.h> // ioctl

#include <TString.h>

#include "MLog.h"
#include "MLogManip.h"

using namespace std;

//ClassImp(MVideo);

// -------------------------------------------------------------
//
// Constructor. Specify the device (e.g. "/dev/video") to be used
//
MVideo::MVideo(const char *path) : fPath(path), fFileDesc(-1), fMapBuffer(0)
{
    Reset();
}

// -------------------------------------------------------------
//
// Internal function to reset the descriptors of the device
//
void MVideo::Reset()
{
    memset(&fCaps,    0, sizeof(fCaps));
    memset(&fChannel, 0, sizeof(fChannel));
    memset(&fBuffer,  0, sizeof(fBuffer));

    fFileDesc = -1;
    fMapBuffer = 0;
    fChannel.channel = -1;
}

// -------------------------------------------------------------
//
// Mapper around ioctl for easier access to the device
//
int MVideo::Ioctl(int req, void *opt, bool allowirq) const
{
    if (fFileDesc<0)
    {
        gLog << err << "ERROR - Ioctl: Device " << fPath << " not open." << endl;
        return -1;
    }

    while (1)
    {
        // FIXME: This call is a possible source for a hangup
        const int rc = ioctl(fFileDesc, req, opt);
        if (rc>=0)
            return rc;

        // errno== 4: Interrupted system call (e.g. by alarm())
        // errno==16: Device or resource busy
        if (errno==4 || errno==16)
        {
            if (!allowirq && errno==4)
                return -4;

            gLog << err << "ERROR - MVideo::Ioctl 0x" << hex << req << ": errno=" << dec << errno << " - ";
            gLog << strerror(errno) << " (rc=" << rc << ")" << endl;
            usleep(10);
            continue;
        }

        gLog << err << "ERROR - MVideo::Ioctl 0x" << hex << req << ": errno=" << dec << errno << " - ";
        gLog << strerror(errno) << " (rc=" << rc << ")" << endl;
        return rc;
    }
    return -1;
}

// -------------------------------------------------------------
//
// Read the capabilities of the device
//
Bool_t MVideo::GetCapabilities()
{
    return Ioctl(VIDIOCGCAP, &fCaps)!=-1;
}

// -------------------------------------------------------------
//
// Read the properties of the device
//
Bool_t MVideo::GetProperties()
{
    return Ioctl(VIDIOCGPICT, &fProp)!=-1;
}

// -------------------------------------------------------------
//
// Open channel ch of the device
//
Bool_t MVideo::Open(Int_t ch)
{
    Bool_t rc = Init(ch);
    if (!rc)
        Close();
    return rc;
}

// -------------------------------------------------------------
//
// Open a channel of the device and retriev all necessary
// informations from the driver. Initialize the shared
// memory. Other access methods are not supported yet.
//
Bool_t MVideo::Init(Int_t channel)
{
    if (IsOpen())
    {
        gLog << warn << "WARNING - Device " << fPath << " already open." << endl;
        return kTRUE;
    }

    gLog << all << "Opening " << fPath << "... " << flush;
    do
    {
        fFileDesc = open(fPath, O_RDWR);
        usleep(1);
    }
    while (errno==19 && fFileDesc==-1);

    if (fFileDesc == -1)
    {
        gLog << err << "ERROR: " << strerror(errno) << endl;
        return kFALSE;
    }

    gLog << "done (" << fFileDesc << ")." << endl;

    // Close device on exit
    if (fcntl(fFileDesc, F_SETFD, FD_CLOEXEC)<0)
    {
        gLog << err << "ERROR - Call to fnctl (F_SETFD, FD_CLOEXEC) failed." << endl;
        return kFALSE;
    }

    if (!GetCapabilities())
    {
        gLog << err << "ERROR - Getting device capabilities failed." << endl;
        return kFALSE;
    }

    if (!SetChannel(channel))
        return kFALSE;

    if (!GetProperties())
    {
        gLog << err << "ERROR - Couldn't get picture properties." << endl;
        return kFALSE;
    }

    // get mmap grab buffer info
    if (Ioctl(VIDIOCGMBUF, &fBuffer)==-1)
    {
        gLog << err << "ERROR - Couldn't get info about memory map buffer." << endl;
        return kFALSE;
    }

    // map file (device) into memory
    fMapBuffer = (char*)mmap(0, fBuffer.size, PROT_READ|PROT_WRITE, MAP_SHARED, fFileDesc, 0);
    if (fMapBuffer == (void*)-1)
    {
        gLog << err << "ERROR - Couldn't map device buffer into memory." << endl;
        fMapBuffer = 0;
        return kFALSE;
    }

    Print();

    return kTRUE;
}

// -------------------------------------------------------------
//
// Close device. Free the shared memory
//
Int_t MVideo::Close()
{
    //    if (!IsOpen())
    //        return kTRUE;

    Bool_t rc = kTRUE;

    gLog << all << "Closing " << fPath << " (" << fFileDesc << ")... " << flush;
    if (fFileDesc != -1)
    {
        if (close(fFileDesc)<0)
        {
            gLog << err << "ERROR!" << endl;
            rc = kFALSE;
        }
        fFileDesc = -1;
    }
    gLog << "done." << endl;

    // unmap device memory
    if (fMapBuffer)
        munmap(fMapBuffer, fBuffer.size);

    Reset();

    return rc;
}

// -------------------------------------------------------------
//
// Instruct hardware to start capture into framebuffer frame
//
Bool_t MVideo::CaptureStart(unsigned int frame) const
{
    struct video_mmap gb =
    {
        frame%fBuffer.frames,            // frame
        fCaps.maxheight, fCaps.maxwidth, // height, width
        VIDEO_PALETTE_RGB24              // palette
        //768, 576, 
        //VIDEO_PALETTE_RGB555             // palette
    };

    //
    // capture frame
    //
    if (Ioctl(VIDIOCMCAPTURE, &gb) != -1)
        return kTRUE;

//    if (errno == EAGAIN)
        gLog << err << "ERROR - Couldn't start capturing frame " << frame << " - unable to sync." << endl;

    return kFALSE;
}

// -------------------------------------------------------------
//
// Wait until hardware has finished capture into framebuffer frame
//
Int_t MVideo::CaptureWait(unsigned int frame, char **ptr) const
{
    frame %= fBuffer.frames;

    *ptr = NULL;

    const int SYNC_TIMEOUT = 1;

    alarm(SYNC_TIMEOUT);
    const Int_t rc = Ioctl(VIDIOCSYNC, &frame, false);
    if (rc==-4)
    {
        //cout << "ERROR - Waiting for frame " << frame << " timed out." << endl;
        return kSKIP;
    }
    alarm(0);

    if (rc==-1)
    {
        gLog << err << "ERROR - Waiting for captured frame failed." << endl;
        return kFALSE;
    }

    *ptr = fMapBuffer+fBuffer.offsets[frame];

    return kTRUE;
}

// -------------------------------------------------------------
//
// Change the channel of a priviously opened device
//
Bool_t MVideo::SetChannel(Int_t chan)
{
    if (fChannel.channel==chan)
        return kTRUE;

    if (chan<0 || chan>=fCaps.channels)
    {
        gLog << err << "ERROR - Set channel " << chan << " out of range." << endl;
        return kFALSE;
    }

    // Switch to channel
    struct video_channel ch = { chan, "", 0, 0, 0, 0 };
    if (Ioctl(VIDIOCSCHAN, &ch)==-1)
    {
        gLog << err << "ERROR - Couldn't switch to channel " << chan << "." << endl;
        gLog << "        You might need a bttv version > 0.5.13" << endl;
        return kFALSE;
    }

    // Get information about channel
    if (Ioctl(VIDIOCGCHAN, &ch)==-1)
    {
        gLog << err << "ERROR - Getting information for channel " << chan << " failed." << endl;
        return kFALSE;
    }

    memcpy(&fChannel, &ch, sizeof(fChannel));

    gLog << all << "Switched to channel " << chan << endl;

    return kTRUE;
}

// -------------------------------------------------------------
//
// Has the device capture capabilities?
//
Bool_t MVideo::CanCapture() const
{
    return fCaps.type&VID_TYPE_CAPTURE;
}

// -------------------------------------------------------------
//
// Returns the number of frame buffers which can be used
//
Int_t MVideo::GetNumBuffers() const
{
    return fBuffer.frames;
}

// -------------------------------------------------------------
//
// Maximum width of the frame which can be captured
//
Int_t MVideo::GetWidth() const
{
    return fCaps.maxwidth;
}

// -------------------------------------------------------------
//
// Maximum height of the frame which can be captured
//
Int_t MVideo::GetHeight() const
{
    return fCaps.maxheight;
}

// -------------------------------------------------------------
//
// Return the device type as string
//
TString MVideo::GetDevType(int type) const
{
    TString rc;
    if (CanCapture())
        rc += " capture";
    if (type&VID_TYPE_TUNER)
        rc += " tuner";
    if (type&VID_TYPE_TELETEXT)
        rc += " teletext";
    if (type&VID_TYPE_OVERLAY)
        rc += " overlay";
    if (type&VID_TYPE_CHROMAKEY)
        rc += " chromakey";
    if (type&VID_TYPE_CLIPPING)
        rc += " clipping";
    if (type&VID_TYPE_FRAMERAM)
        rc += " frameram";
    if (type&VID_TYPE_SCALES)
        rc += " scales";
    if (type&VID_TYPE_MONOCHROME)
        rc += " monochrom";
    if (type&VID_TYPE_SUBCAPTURE)
        rc += " subcapature";
    return rc;
}

// -------------------------------------------------------------
//
// Return the channel flags as string
//
TString MVideo::GetChannelFlags(Int_t flags) const
{
    TString rc = "video";
    if (flags&VIDEO_VC_TUNER)
        rc += " tuner";
    if (flags&VIDEO_VC_AUDIO)
        rc += " audio";
    return rc;
}

// -------------------------------------------------------------
//
// Return the channel type as string
//
TString MVideo::GetChannelType(Int_t type) const
{
    if (type&VIDEO_TYPE_TV)
        return "TV";
    if (type&VIDEO_TYPE_CAMERA)
        return "camera";
    return "unknown";
}

// -------------------------------------------------------------
//
// Return the palette pal as string
//
TString MVideo::GetPalette(Int_t pal) const
{
    if (pal&VIDEO_PALETTE_GREY)
        return "Linear intensity grey scale";
    if (pal&VIDEO_PALETTE_HI240)
        return "BT848 8-bit color cube";
    if (pal&VIDEO_PALETTE_RGB565)
        return "RGB565 packed into 16-bit words";
    if (pal&VIDEO_PALETTE_RGB555)
        return "RGB555 packed into 16-bit words, top bit undefined";
    if (pal&VIDEO_PALETTE_RGB24)
        return "RGB888 packed into 24-bit words";
    if (pal&VIDEO_PALETTE_RGB32)
        return "RGB888 packed into the low three bytes of 32-bit words. Top bits undefined.";
    if (pal&VIDEO_PALETTE_YUV422)
        return "Video style YUV422 - 8-bit packed, 4-bit Y, 2-bits U, 2-bits V";
    if (pal&VIDEO_PALETTE_YUYV)
        return "YUYV";
    if (pal&VIDEO_PALETTE_UYVY)
        return "UYVY";
    if (pal&VIDEO_PALETTE_YUV420)
        return "YUV420";
    if (pal&VIDEO_PALETTE_YUV411)
        return "YUV411";
    if (pal&VIDEO_PALETTE_RAW)
        return "Raw capture (Bt848)";
    if (pal&VIDEO_PALETTE_YUV422P)
        return "YUV 4:2:2 planar";
    if (pal&VIDEO_PALETTE_YUV411P)
        return "YUV 4:1:1 planar";

    return "unknown";
}

// -------------------------------------------------------------
//
// Print informations about the device, the capabilities, the
// channel and all available information
//
void MVideo::Print() const
{
    gLog << all;

    gLog << "Device " << fPath << " " << (IsOpen()?"open":"closed") << "." << endl;

    if (!IsOpen())
        return;

    gLog  << " - Name:       " << fCaps.name << endl;
    gLog  << " - DevType:   "  << GetDevType(fCaps.type) << endl;
    gLog  << " - Channels:   " << fCaps.channels << endl;
    gLog  << " - Audios:     " << fCaps.audios << endl;
    gLog  << " - Size:       ";
    gLog  << fCaps.minwidth << "x" << fCaps.minheight << " to ";
    gLog  << fCaps.maxwidth << "x" << fCaps.maxheight << endl;
    gLog  << endl;
    if (fChannel.channel>=0)
    {
        gLog  << " - Channel:    " << fChannel.channel << " (" << fChannel.name << ")" << endl;
        gLog  << " - IsA:        " << GetChannelType(fChannel.type) << " with " << GetChannelFlags(fChannel.flags) << endl;
        //if (fChannel.flags&VIDEO_VC_NORM)
        gLog  << " - Norm:       " << fChannel.norm << endl;
        gLog  << endl;
    }
    gLog  << " - Brightness: " << fProp.brightness << endl;
    gLog  << " - Hue:        " << fProp.hue << endl;
    gLog  << " - Color:      " << fProp.colour << endl;
    gLog  << " - Contrast:   " << fProp.contrast << endl;
    gLog  << " - Whiteness:  " << fProp.whiteness << endl;
    gLog  << " - Depth:      " << fProp.depth << endl;
    gLog  << " - Palette:    " << GetPalette(fProp.palette) << " (" << fProp.palette << ")" << endl;
    gLog  << endl;

    gLog  << " - BufferSize: 0x" << hex << fBuffer.size << " (" << dec << fBuffer.frames << " frames)" << endl;
    gLog  << " - Offsets:   " << hex;
    for (int i=0; i<fBuffer.frames; i++)
        gLog  << " 0x" << fBuffer.offsets[i];
    gLog  << dec << endl;
}

/*
void MVideo::SetPicPar(int bright, int hue, int contrast)
{
    struct video_picture pict;

    Ioctl(VIDIOCGPICT, &pict);  // get

    if (contrast != -1)
        pict.contrast = contrast;

    if (bright != -1)
        pict.brightness = bright;

    if (hue != -1)
	pict.hue = hue;

    Ioctl(VIDIOCSPICT, &pict);  //set
}

void MVideo::GetPicPar(int *bright, int *hue, int *contrast)
{
    struct video_picture pict;

    Ioctl(VIDIOCGPICT, &pict);   // get

    *contrast = pict.contrast;
    *bright   = pict.brightness;
    *hue      = pict.hue;
}
*/
