#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#include <qbutton.h>
#include <qbuttongroup.h>
#include <qcheckbox.h>
#include <qcombobox.h>
#include <qdial.h>
#include <qlistbox.h>
#include <qlcdnumber.h>
#include <qradiobutton.h>
#include <qsize.h>
#include <qslider.h>
#include <qtabwidget.h>

#include "VideoDeviceInput.h"
#include "VideoSettingsDlg.h" 
#include "pwc-ioctl.h"

static short int_sizes[7][2] =
{
  { 128,  96 },
  { 160, 120 },
  { 176, 144 },
  { 320, 240 },
  { 352, 288 },
  { 640, 480 },
  {  -1,  -1 }
};
             
static int frames[8] =  { 4, 5, 8, 10, 15, 20, 25, 30 };

CVideoSettingsDlg::CVideoSettingsDlg(CVideoDevice *video)
	: AutoTimer(this)
{
   int i, n, c;
   CVideoDeviceInput *pVI;
   QButton *pB;
   QString vidname;
   
   pVideo = video;
   CentralFrequency = 0.0;
   m_WBMode = -1;

   /* Size & Framerate */
   // Find maximum size, set label on button if larger than 640x480,
   // then disable all radio buttons that are too large 
   fps = 0;
   max_size = pVideo->GetMaxSize();
   for (i = 0; i < 6; i++) {
      sizes[i].setWidth(int_sizes[i][0]);
      sizes[i].setHeight(int_sizes[i][1]);
   }   
   if (max_size.width() > 640 || max_size.height() > 480) {
     sizes[6] = max_size;
     MaxSize->setText(tr("Max (%1x%2)").arg(max_size.width()).arg(max_size.height()));
   }
   else
     MaxSize->setEnabled(FALSE);
   for (i = 0; i < 7; i++) {
      if (max_size.width()  < sizes[i].width() ||
          max_size.height() < sizes[i].height())
        ImageSize->find(i)->setEnabled(FALSE);
   }
   GetCamSizeFrame(TRUE);

   /* brightness, contrast, gamma & saturation */
   i = pVideo->GetBrightness();
   if (i == 0xffff)
     Brightness->setDisabled(TRUE);
   else
     Brightness->setValue(i);   
   i = pVideo->GetContrast();
   if (i == 0xffff)
     Contrast->setDisabled(TRUE);
   else
     Contrast->setValue(i);   
   i = pVideo->GetWhiteness();
   if (i == 0xffff)
     Gamma->setDisabled(TRUE);
   else
     Gamma->setValue(i);   
   i = pVideo->GetColour();
   if (i == 0xffff)
     Colour->setDisabled(TRUE);
   else
     Colour->setValue(i);   

   /* input selection */
   // Determine number of inputs, set names in "Input selector", possibly
   // find out which is the current tuner.
   pTuner = NULL;
   FreqSystem = -1;
   n = pVideo->GetVideoInputs();
   c = pVideo->GetCurrentVideoInput();
   if (c < 0) {
     EnableTuning(FALSE);
     InputSelection->setButton(-1);
   }
   for (i = 0; i < 6; i++) {
      pB = InputSelection->find(i);
      if (pB != NULL) {
        if (i < n) {
          pVI = pVideo->GetVideoInput(i);
          if (pVI != NULL) {
            pB->show();
            pB->setText(pVI->GetName());
            if (i == c)
              InputSelection->setButton(i);
          }
        }
        else
          pB->hide();
      }
   }
   ChannelSelector->clear();
   
   /* Try to find out if this is a Philips cam */
   IsPhilips = FALSE;
   vidname = pVideo->GetIntfName();
   if (vidname.find("Philips") == 0 && vidname.find("webcam") > 0)
     IsPhilips = TRUE;
   else {
     struct pwc_probe probe;

     memset(&probe, 0, sizeof(probe));
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCPROBE, &probe) == 0) {
       if (vidname == probe.name)
         IsPhilips = TRUE;
     }
   }
   if (IsPhilips) {
     int agc, contour, compr, backlight, flicker, dynnoise;
     struct pwc_whitebalance wb;
     struct pwc_wb_speed wbs;

     qDebug("Philips webcam detected, enabling extensions");
     PhilipsTab->show();

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAGC, &agc) < 0) {
       qDebug("No VIDIOCPWCGAGC");
       AGCBox->setEnabled(FALSE);
       AGCValue->setEnabled(FALSE);
     }
     else {
       AGCBox->setChecked(agc < 0);
       AGCValue->setValue(abs(agc) / 256);
     }

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGCONTOUR, &contour) < 0) {
       qDebug("No VIDIOCPWCGCONTOUR");
       ContourBox->setEnabled(FALSE);
       ContourValue->setEnabled(FALSE);
     }
     else {
       ContourBox->setChecked(contour < 0);
       if (contour >= 0)
         ContourValue->setValue(contour / 256);
     }

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGBACKLIGHT, &backlight) < 0) {
       qDebug("No VIDIOCPWCGBACKLIGHT");
       BacklightBox->setEnabled(FALSE);
     }
     else
       BacklightBox->setChecked(backlight != 0);

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGFLICKER, &flicker) < 0) {
       qDebug("No VIDIOCPWCGFLICKER");
       FlickerBox->setEnabled(FALSE);
     }
     else
       FlickerBox->setChecked(flicker != 0);

     ShutterBox->setChecked(TRUE);


     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGDYNNOISE, &dynnoise) < 0) {
       qDebug("No VIDIOCPWCGDYNNOISE");
       m_NoiseReduction->setEnabled(FALSE);
     }
     else
       m_NoiseReduction->setCurrentItem(dynnoise);

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGCQUAL, &compr) < 0) {
       qDebug("VIDIOCPWCGCQUAL failed");
       m_CompressionSelection->setEnabled(FALSE);
     }
     else
       m_CompressionSelection->setCurrentItem(compr);

     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAWB, &wb) < 0) {
       m_WhiteBalance->setEnabled(FALSE);
       m_RedDial->setEnabled(FALSE);
       m_BlueDial->setEnabled(FALSE);
     }
     else {
       m_WhiteBalance->setEnabled(TRUE);
       m_WhiteBalance->setCurrentItem(wb.mode);
       ChangedWB(wb.mode);
     }
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAWBSPEED, &wbs) < 0) {
       m_WBSpeed->setEnabled(FALSE);
       m_WBDelay->setEnabled(FALSE);
     }
     
     
   }
   else {
     PhilipsTab->hide();
   }
   Tabs->setTabEnabled(PhilipsTab, IsPhilips);
   
  
   
   
   // misc. stuff
   connect(pVideo, SIGNAL(Closed()), this, SLOT(reject()));
   connect(&AutoTimer, SIGNAL(timeout()), this, SLOT(UpdateAuto()));
}


// private

void CVideoSettingsDlg::GetCamSizeFrame(bool First)
{
   int ifs;
   QSize ns;
   
   ns = pVideo->GetSize();
   ifs = pVideo->GetFramerate();
   if (First) {
     org_size = ns;
     org_fps  = ifs;
   }
   SizeChanged(ns);
   FramerateChanged(ifs);
}

void CVideoSettingsDlg::EnableTuning(bool doit)
{
   TunerSelection->setEnabled(doit);
   FrequencyShow->setEnabled(doit);
   TuningSystem->setEnabled(doit);
   FineTuning->setEnabled(doit);
   ChannelSelector->setEnabled(doit);
   ClickedFrequencySystem(FreqSystem);
}


// private slots

/**
  \brief Pick up size changes (even if we do it ourselves!)
*/
void CVideoSettingsDlg::SizeChanged(const QSize &ns)
{
   int i, sz;

qDebug("CVideoSettingsDlg::SizeChanged(%dx%d)", ns.width(), ns.height());

   // Determine image size (this better be exact)
   sz = -1;
   for (i = 0; i < 7; i++) {
      if (ns.width()  == sizes[i].width() &&
          ns.height() == sizes[i].height()) {
        sz = i;
        break;
      }
   }
   // Set radio button group
   if (sz != -1)
     ImageSize->setButton(sz);
   if (ns != size)
     size = ns;
}

void CVideoSettingsDlg::FramerateChanged(int ifs)
{
   int i, fs, nfs, diff, dev;

qDebug("CVideoSettingsDlg::FramerateChanged(%d)", ifs);
   // Determine closest matching framerate
   fs = -1;
   nfs = -1;
   dev = 9999;
   for (i = 0; i < 8; i++) {
      diff = abs(frames[i] - ifs);
      if (diff < dev) { // Closest so far...
        dev = diff;
        nfs = frames[i];
        fs = i;
      }
   }
   if (fs != -1)
     ImageFrames->setButton(fs);
   if (nfs != fps)
     fps = nfs;
}

/**
  This is called once a second to update the sliders in the Philips section
*/
void CVideoSettingsDlg::UpdateAuto()
{
   int agc;
   struct pwc_whitebalance wb;

   if (IsPhilips) {
     if (AGCBox->isChecked()) {
       if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAGC, &agc) == 0) {
         AGCValue->setValue(-agc / 256);
       }
     }
     if (m_WBMode == PWC_WB_AUTO) {
       if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAWB, &wb) == 0) {
         m_RedDial->setValue(wb.read_red / 256);
         m_BlueDial->setValue(wb.read_blue / 256);
       }
     }
   }
}


// protected


/// We broadcast the fact that the dialog got hidden, to update the toolbar.
void CVideoSettingsDlg::hideEvent(QHideEvent *e)
{
   AutoTimer.stop();
   emit DialogClosed();
}

void CVideoSettingsDlg::showEvent(QShowEvent *e)
{
   if (IsPhilips)
     AutoTimer.start(1000);
}


// protected slots
/// Called when the user clicked on a Size radio button 
void CVideoSettingsDlg::ClickedSize(int s)
{
   if (pVideo == NULL)
     return;

   /* Try to set the cam to the desired size; this may trigger a Resized() signal */
   if (!pVideo->SetSize(sizes[s].width(), sizes[s].height()))
     return;
//   GetCam(); // read back from cam
}

/// Called when the user clicked on a Framerate radio button
void CVideoSettingsDlg::ClickedRate(int f)
{
   if (pVideo == NULL)
     return;

   /* Leave flags intact */
   if (!pVideo->SetFramerate(frames[f]))
     return;
   GetCamSizeFrame();
}


void CVideoSettingsDlg::MovedBrightness(int value)
{
   if (pVideo == NULL)
     return;
   pVideo->SetBrightness(value);
}

void CVideoSettingsDlg::MovedContrast(int value)
{
   if (pVideo == NULL)
     return;
   pVideo->SetContrast(value);
}

void CVideoSettingsDlg::MovedGamma(int value)
{
   if (pVideo == NULL)
     return;
   pVideo->SetWhiteness(value);
}

void CVideoSettingsDlg::MovedColour(int value)
{
   if (pVideo == NULL)
     return;
   pVideo->SetColour(value);
}


void CVideoSettingsDlg::ClickedInputSelector(int input)
{
   int i, n, c;
   CVideoDeviceInput *pVI;
   CVideoDeviceTuner *pVT;
   QButton *pB;

qDebug("CVideoSettingsDlg::ClickedInputSelector(%d)", input);
   if (pVideo == NULL)
     return;
   pVideo->SelectVideoInput(input);

   /* determine number of available tuners for this input */
   pTuner = NULL;
   pVI = pVideo->GetVideoInput();
   if (pVI == NULL)
     return; // should not happen...   
   n = pVI->GetTuners();
   c = pVI->GetCurrentTuner();
   if (c < 0)
     TunerSelection->setButton(-1);
   for (i = 0; i < 3; i++) {
      pB = TunerSelection->find(i);
      if (pB != NULL) {
        if (i < n) {
          pVT = pVI->GetTuner(i);
          if (pVT != NULL) {
            pB->show();
            pB->setText(pVT->GetName());
            if (i == c) {
              TunerSelection->setButton(i);
              pTuner = pVT;
            }
          }
        }
        else
          pB->hide();
      }
   }
   EnableTuning(n > 0 ? TRUE : FALSE);
}

void CVideoSettingsDlg::ClickedTunerSelector(int tuner)
{
    CVideoDeviceInput *pVI;

qDebug("CVideoSettingsDlg::ClickedTunerSelector(%d)", tuner);
    if (pVideo == NULL)
      return;
    pVI = pVideo->GetVideoInput();
    if (pVI == NULL)
      return;
    pVI->SelectTuner(tuner);
    pTuner = pVI->GetTuner();
}

void CVideoSettingsDlg::ClickedFrequencySystem(int system)
{
   int i, j;
   QString fname;

   if (system == FreqSystem)
     return;

   ChannelSelector->clear();
   j = 0;
   switch(system) {
     case 0: // Europe, including cable
       Frequencies.resize(109);
       for (i = 0; i < 3; i++) {
          fname.sprintf("VHF %d", 2 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 48.25 + 7.0 * i;
       }
       for (i = 0; i < 8; i++) {
          fname.sprintf("VHF %d", 5 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 175.25 + 7.0 * i;
       }
       /* The following set of frequencies make no sense at all. */
       ChannelSelector->insertItem(tr("Italy A"), j);
       Frequencies[j++] = 53.75;
       ChannelSelector->insertItem(tr("Italy B"), j);
       Frequencies[j++] = 62.25;
       ChannelSelector->insertItem(tr("Italy C"), j);
       Frequencies[j++] = 82.25;
       ChannelSelector->insertItem(tr("Italy D"), j);
       Frequencies[j++] = 175.25;
       ChannelSelector->insertItem(tr("Italy E"), j);
       Frequencies[j++] = 183.75;
       ChannelSelector->insertItem(tr("Italy F"), j);
       Frequencies[j++] = 192.25;
       ChannelSelector->insertItem(tr("Italy G"), j);
       Frequencies[j++] = 201.25;
       ChannelSelector->insertItem(tr("Italy H"), j);
       Frequencies[j++] = 210.25;
       /* UHF band */
       for (i = 0; i < 49; i++) {
          fname.sprintf("UHF %d", 21 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 471.25 + 8.0 * i;
       }
       /* Cable */
       for (i = 0; i < 10; i++) {
          fname.sprintf("CATV S%d", 1 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 105.25 + 7.0 * i;
       }
       for (i = 0; i < 10; i++) {
          fname.sprintf("CATV S%d", 11 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 231.25 + 7.0 * i;
       }
       for (i = 0; i < 21; i++) {
          fname.sprintf("CATV S%d", 21 + i);
          ChannelSelector->insertItem(fname, j);
          Frequencies[j++] = 303.25 + 8.0 * i;
       }
       break;
   }
}

void CVideoSettingsDlg::ClickedChannel()
{
   QString fstring;
   int channel;
   
   channel = ChannelSelector->currentItem();
qDebug("CVideoSettingsDlg::ClickedChannel(%d)", channel);

   if (pTuner == NULL)
     return;
   CentralFrequency = Frequencies[channel] * 1000000;
   pTuner->SetFrequency(CentralFrequency);
   fstring.sprintf("%6.2f", Frequencies[channel]);
   FrequencyShow->display(fstring);
   FineTuning->setValue(0); // reset
}

void CVideoSettingsDlg::MovedFineTuning(int ftune)
{
   QString fstring;

qDebug("CVideoSettingsDlg::MovedFineTuning(%d)", ftune);
   if (pTuner == NULL)
     return;
   if (!pTuner->SetFrequency(CentralFrequency + ftune * 10000))
     qWarning("Device did not accept new frequency (out of range?)");
   fstring.sprintf("%6.2f", (CentralFrequency + ftune * 10000) / 1000000.0);
   FrequencyShow->display(fstring);
}


void CVideoSettingsDlg::ToggledAGC(bool on)
{
   AGCValue->setEnabled(!on);
   if (on) {
     int agc = -1;
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAGC, &agc) < 0)
       perror("VIDIOCPWCSAGC");
   }
}

void CVideoSettingsDlg::MovedAGC(int value)
{
   int agc;

   if (!AGCBox->isChecked()) {
     agc = value * 256;
     ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAGC, &agc);
   }
}

void CVideoSettingsDlg::ToggledShutter(bool on)
{
   if (on)
     ShutterValue->hide();
   else
     ShutterValue->show();
   if (on) {
     int speed = -1;
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSSHUTTER, &speed) < 0)
       perror("VIDIOCPWCSSHUTTER");
   }
}

void CVideoSettingsDlg::MovedShutter(int value)
{
   int speed;

   if (!ShutterBox->isChecked()) {
     speed = value * 256;
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSSHUTTER, &speed) < 0)
       perror("VIDIOCPWCSSHUTTER");
   }
}

void CVideoSettingsDlg::ToggledContour(bool on)
{
   if (on)
     ContourValue->hide();
   else
     ContourValue->show();
   if (on) {
     int contour = -1;
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSCONTOUR, &contour) < 0)
       perror("VIDIOCPWCSCONTOUR");
   }
}

void CVideoSettingsDlg::MovedContour(int value)
{
   int contour;

   if (!ContourBox->isChecked()) {
     contour = value * 256;
     if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSCONTOUR, &contour) < 0)
       perror("VIDIOCPWCSCONTOUR");
   }
}

void CVideoSettingsDlg::ToggledBacklight(bool on)
{
   int backlight;
   backlight = on ? 1 : 0;
   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSBACKLIGHT, &backlight) < 0)
     perror("VIDIOCPWCSBACKLIGHT");
}

void CVideoSettingsDlg::ToggledFlicker(bool on)
{
   int flicker;
   flicker = on ? 1 : 0;
   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSFLICKER, &flicker) < 0)
     perror("VIDIOCPWCSFLICKER");
}


void CVideoSettingsDlg::ChangedNoise(int noise)
{
   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSDYNNOISE, &noise) < 0)
     perror("VIDIOCPWCSDYNNOISE");
}

void CVideoSettingsDlg::ChangedCompression(int compression)
{
   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSCQUAL, &compression) < 0)
     perror("VIDIOCPWCSCQUAL");
}

void CVideoSettingsDlg::ClickedSaveUser()
{
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCSUSER);
}

void CVideoSettingsDlg::ClickedRestoreUser()
{
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCRUSER);
}

void CVideoSettingsDlg::ClickedRestoreFactory()
{
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCFACTORY);
}

/**
  \brief Called when user chooses different white balance
  
  This function will modify the dialog. When using a preset (indooor/outdoor),
  the red and blue dials are hidden. In auto mode, the dials track the 
  values as reported by the camera. In manual mode, the dials can be set
  by the user.
*/  
void CVideoSettingsDlg::ChangedWB(int new_mode)
{
   struct pwc_whitebalance wb;
   
   m_WBMode = new_mode;
   wb.mode = m_WBMode;
   wb.manual_red = m_RedDial->value() * 256;
   wb.manual_blue = m_BlueDial->value() * 256;
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAWB, &wb);
   
   if (m_WBMode == PWC_WB_MANUAL || m_WBMode == PWC_WB_AUTO) {
     m_RedDial->show();
     m_BlueDial->show();
     m_RedDial->setEnabled(m_WBMode == PWC_WB_MANUAL);
     m_BlueDial->setEnabled(m_WBMode == PWC_WB_MANUAL);
     if (m_WBMode == PWC_WB_MANUAL) {
       m_RedDial->setValue(wb.manual_red / 256);
       m_BlueDial->setValue(wb.manual_blue / 256);
     }
     else {
       if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCGAWB, &wb) < 0)
         perror("VIDIOCPWCGAWB 3");
       m_RedDial->setValue(wb.read_red / 256);
       m_BlueDial->setValue(wb.read_blue / 256);
     }
   }
   else {
     m_RedDial->hide();
     m_BlueDial->hide();
   }
}

void CVideoSettingsDlg::MovedRed(int red)
{
   struct pwc_whitebalance wb;
   
   wb.mode = m_WBMode;
   wb.manual_red = m_RedDial->value() * 256;
   wb.manual_blue = m_BlueDial->value() * 256;
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAWB, &wb);
}

void CVideoSettingsDlg::MovedBlue(int blue)
{
   struct pwc_whitebalance wb;
   
   wb.mode = m_WBMode;
   wb.manual_red = m_RedDial->value() * 256;
   wb.manual_blue = m_BlueDial->value() * 256;
   ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAWB, &wb);
}

void CVideoSettingsDlg::MovedWBSpeed(int speed)
{
   struct pwc_wb_speed wbs;

   /* The speed is reversed... the more the right, the lower the value should be */   
   wbs.control_speed = (255 - speed) << 8;
   wbs.control_delay = 0;

   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAWBSPEED, &wbs) < 0)
     perror("VIDIOCPWCSAWBSPEED speed");
}

void CVideoSettingsDlg::MovedWBDelay(int delay)
{
   struct pwc_wb_speed wbs;
   
   wbs.control_speed = 0;
   wbs.control_delay = delay << 8;

   if (ioctl(pVideo->GetDescriptor(), VIDIOCPWCSAWBSPEED, &wbs) < 0)
     perror("VIDIOCPWCSAWBSPEED delay");
}

