/*
 * thread.c: The thread for the Atmolight-plugin
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */
#include <stdlib.h>
#include <vdr/skins.h>
#include <vdr/tools.h>
#include <vdr/thread.h>
#include "math.h"

#include "defs.h"

// filter class
#include "filter.h"

// input classes
#include "input.h"
#include "inputffdvb.h"
#include "inputsoftdevice.h"

// output classes
#include "output.h"
#include "outputserial.h"
#include "outputnetwork.h"

#include "setup.h"
#include "thread.h"

// seconds the next timer must be in the future to assume manual start
#define MANUALSTART       600

//************************************************************************************************************
//***             cAtmoThread                                                                              ***
//************************************************************************************************************

cAtmoThread::cAtmoThread()
{
  // input device
  switch (AtmoSetup.AtmoInput)
  {
    case ffdvb:
         Input = new cInputFFDVB();
    break;

    case softdevice:
         Input = new cInputSoftdevice();
    break;

    default:
    case no_input:
         Input = NULL;
         Skins.QueueMessage(mtInfo, tr("Atmolight: No input device given!"));
    break;
  }

  // filter
  Filter = new cAtmoFilter();

  // output device
  switch (AtmoSetup.AtmoOutput)
  {
    case serial:
         Output = new cOutputSerial();
    break;

    case network:
         Output = new cOutputNetwork();
    break;

    default:
    case no_output:
         Output = NULL;
         Skins.QueueMessage(mtInfo, tr("Atmolight: No output device given!"));
    break;
  }
}

cAtmoThread::~cAtmoThread()
{  
  AtmoSetup.atmo_on = false;
  cCondWait::SleepMs(200);
  OutputColors(ColorPacket); // send all off

  if (Input)
  {
    Input->Close();
    delete Input;
    Input = NULL;
  }

  delete Filter;
  Filter = NULL;

  if (Output)
  {
    Output->Close();
    delete Output;
    Output = NULL;
  }
}

void cAtmoThread::SwitchOn(void)
{
  if (false == AtmoSetup.atmo_on)
  {
    if (false == Input->Open(NULL))
    {
      AtmoSetup.AtmoInput = no_input;
      Skins.QueueMessage(mtInfo, tr("Atmolight: Could not open input device!"));
      return;
    }
    if (false == Output->Open(AtmoSetup.output_arg))
    {
      AtmoSetup.AtmoOutput = no_output;
      Skins.QueueMessage(mtInfo, tr("Atmolight: Could not open output device!"));
      return;
    }
    Filter->ResetFilter();
    AtmoSetup.atmo_on = true;
  }  
}

void cAtmoThread::SwitchOff(void)
{
  if (true == AtmoSetup.atmo_on)
  {
    AtmoSetup.atmo_on = false;
    cCondWait::SleepMs(200);
    OutputColors(ColorPacket); // send all off
    Input->Close();
    Output->Close();
  }
}

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

  if ((NULL == Input) || (NULL == Output)) { return; } // input and output existing?

  cCondWait::SleepMs(1000); // wait 1000ms for loaded timers

  GetStartMode(); // manual or automatic start of vdr?

  while (Running())
  {
    t.Set(0);

    if (AtmoSetup.atmo_on)
    {
      // get calculated color from input
      ColorPacket = Input->GetColorPacket();

      // send to filter
      ColorPacket = Filter->Filtering(ColorPacket);

      // apply gamma
      ColorPacket = ApplyGamma(ColorPacket);

      // apply white calibration
      ColorPacket = WhiteCalibration(ColorPacket);

      // send to output device
      OutputColors(ColorPacket);
    }

    if (t.Elapsed() < 20) // ensure that the filter is called every 20ms
    {
      cCondWait::SleepMs(20 - t.Elapsed());
    }
  }
}

// assumption of manual/automatic start of vdr
void cAtmoThread::GetStartMode(void)
{
       if (AtmoSetup.StartMode == 0) { SwitchOn(); } // on
  else if (AtmoSetup.StartMode == 1) { SwitchOff(); } // off
  else if (AtmoSetup.StartMode == 2) // timer dependent
  {
    time_t Now = time(NULL);
    cTimer *timer = Timers.GetNextActiveTimer();
    time_t Next  = timer ? timer->StartTime() : 0;
    time_t Delta = timer ? Next - Now : 0;

    if (!timer || Delta > MANUALSTART)
    { // Apparently the user started VDR manually
      SwitchOn();
    }
    else
    {
      SwitchOff();
    }
  }
}

// apply gamma-correction
tColorPacket cAtmoThread::ApplyGamma(tColorPacket ColorPacket)
{
  switch (AtmoSetup.Gamma_Mode)
  {
    case by_color: // gamma correction for each color
    {
      double GammaRed   = (double)AtmoSetup.Gamma.r;
      double GammaGreen = (double)AtmoSetup.Gamma.g;
      double GammaBlue  = (double)AtmoSetup.Gamma.b;

      for (int i = 0; i < 5; i++)
      {
        ColorPacket.channel[i].r = (unsigned char)(pow((double)ColorPacket.channel[i].r / 255.0f, GammaRed   / 10.0f) * 255.0f);
        ColorPacket.channel[i].g = (unsigned char)(pow((double)ColorPacket.channel[i].g / 255.0f, GammaGreen / 10.0f) * 255.0f);
        ColorPacket.channel[i].b = (unsigned char)(pow((double)ColorPacket.channel[i].b / 255.0f, GammaBlue  / 10.0f) * 255.0f);
      }
    }
    break;

    default:
    break;
  }
  return ColorPacket;
}

// apply white-calibration
tColorPacket cAtmoThread::WhiteCalibration(tColorPacket ColorPacket)
{
  for (int i = 0; i < 5; i++)
  {
    ColorPacket.channel[i].r = (unsigned char)((int)AtmoSetup.WhiteCalibration.r * (int)ColorPacket.channel[i].r / 255);
    ColorPacket.channel[i].g = (unsigned char)((int)AtmoSetup.WhiteCalibration.g * (int)ColorPacket.channel[i].g / 255);
    ColorPacket.channel[i].b = (unsigned char)((int)AtmoSetup.WhiteCalibration.b * (int)ColorPacket.channel[i].b / 255);
  }
  return ColorPacket;
}

// send filter-output to output-device
void cAtmoThread::OutputColors(const tColorPacket ColorPacket)
{
  tColorPacket col;
  static tColorPacket last_col;

  if (AtmoSetup.atmo_on)
  {
    switch (AtmoSetup.Mode)
    {
      case live_picture_hsv: // live mode
           col = ColorPacket;
      break;

      case default_color: // default color
           switch (AtmoSetup.DefaultColor)
           {
             case black:
             default:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 0; // red (0-255)
                    col.channel[i].g = 0; // green (0-255)
                    col.channel[i].b = 0; // blue (0-255)
                  }
             break;

             case white:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 255; // red (0-255)
                    col.channel[i].g = 255; // green (0-255)
                    col.channel[i].b = 255; // blue (0-255)
                  }
             break;

             case red:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 255; // red (0-255)
                    col.channel[i].g = 0;   // green (0-255)
                    col.channel[i].b = 0;   // blue (0-255)
                  }
             break;

             case green:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 0;   // red (0-255)
                    col.channel[i].g = 255; // green (0-255)
                    col.channel[i].b = 0;   // blue (0-255)
                  }
             break;

             case blue:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 0;   // red (0-255)
                    col.channel[i].g = 0;   // green (0-255)
                    col.channel[i].b = 255; // blue (0-255)
                  }
             break;

             case yellow:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 255; // red (0-255)
                    col.channel[i].g = 255; // green (0-255)
                    col.channel[i].b = 0;   // blue (0-255)
                  }
             break;

             case cyan:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 0;   // red (0-255)
                    col.channel[i].g = 255; // green (0-255)
                    col.channel[i].b = 255; // blue (0-255)
                  }
             break;

             case magenta:
                  for (int i=0; i<5; i++)
                  {
                    col.channel[i].r = 255; // red (0-255)
                    col.channel[i].g = 0;   // green (0-255)
                    col.channel[i].b = 255; // blue (0-255)
                  }
             break;
           }
      break;

      case static_color: // static color
           col = AtmoSetup.StaticColor;
      break;
    }
  }
  else // (false == AtmoSetup.atmo_on)
  {
    col.channel[0].r = col.channel[0].g = col.channel[0].b = 0;
    col.channel[4] = col.channel[3] = col.channel[2] = col.channel[1] = col.channel[0];
  }

  if (Output)
  {
    if (0 != memcmp(&last_col, &col, sizeof(last_col)))
    {
      Output->OutputColors(col);
    }
    memcpy(&last_col, &col, sizeof(last_col));
  }
}
