/*
 * lirc.c: LIRC remote control
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * LIRC support added by Carsten Koch <Carsten.Koch@icem.de>  2000-06-16.
 *
 * $Id: lirc.c 2.0 2006/05/28 08:48:13 kls Exp $
 */

#include "lirc.h"
#include <netinet/in.h>
#include <sys/socket.h>
#ifdef USE_LIRCSETTINGS
#include "config.h"
#else

#ifndef LIRC_PUSHFREQ
#define LIRC_PUSHFREQ 3 // 1/s
#endif
#ifndef LIRC_REPEATDELAY
#define LIRC_REPEATDELAY 350 // ms
#endif
#ifndef LIRC_REPEATFREQ
#define LIRC_REPEATFREQ 10 // 1/s
#endif
#ifndef LIRC_REPEATTIMEOUT
#define LIRC_REPEATTIMEOUT 500 // ms
#endif
#ifndef LIRC_RECONNECTDELAY
#define LIRC_RECONNECTDELAY 3000 // ms
#endif
#ifndef LIRC_PRIORITYBOOST
#define LIRC_PRIORITYBOOST 0
#endif

#endif /* LIRCSETTINGS */

cLircRemote::cLircRemote(const char *DeviceName)
:cRemote("LIRC")
,cThread("LIRC remote control")
{
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, DeviceName);
  if (Connect()) {
     Start();
     return;
     }
  f = -1;
}

cLircRemote::~cLircRemote()
{
  int fh = f;
  f = -1;
  Cancel();
  if (fh >= 0)
     close(fh);
}

bool cLircRemote::Connect(void)
{
  if ((f = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {
     if (connect(f, (struct sockaddr *)&addr, sizeof(addr)) >= 0)
        return true;
     LOG_ERROR_STR(addr.sun_path);
     close(f);
     f = -1;
     }
  else
     LOG_ERROR_STR(addr.sun_path);
  return false;
}

bool cLircRemote::Ready(void)
{
  return f >= 0;
}

void cLircRemote::Action(void)
{
  cTimeMs FirstTime;
  cTimeMs LastTime;
  char buf[LIRC_BUFFER_SIZE];
  char LastKeyName[LIRC_KEY_BUF] = "";
  bool repeat = false;
  int timeout = -1;

#ifdef USE_LIRCSETTINGS
  if (Setup.LircPriorityBoost)
     SetPriority(GetPriority() - Setup.LircPriorityBoost);
#else
  if (LIRC_PRIORITYBOOST)
     SetPriority(GetPriority() - LIRC_PRIORITYBOOST);
#endif /* LIRCSETTINGS */
  while (Running() && f >= 0) {
        bool ready = cFile::FileReady(f, timeout);
        int ret = -1;
        if (ready) {
           // read one line of the line oriented lirc protocol
           for (ret = 0; ret < (int)sizeof(buf); ret++) {
               int ch = readchar(f);
               if (ch < 0) {
                  ret = -1;
                  break;
                  }
               if (ch == '\n') {
                  buf[ret++] = '\0';
                  break;
                  }
               buf[ret] = ch;
               }
           }

        if (ready && ret <= 0 ) {
#ifdef USE_LIRCSETTINGS
           esyslog("ERROR: lircd connection broken, trying to reconnect every %.1f seconds", float(Setup.LircReconnectDelay) / 1000);
#else
           esyslog("ERROR: lircd connection broken, trying to reconnect every %.1f seconds", float(LIRC_RECONNECTDELAY) / 1000);
#endif /* LIRCSETTINGS */
           close(f);
           f = -1;
           while (Running() && f < 0) {
#ifdef USE_LIRCSETTINGS
                 cCondWait::SleepMs(Setup.LircReconnectDelay);
#else
                 cCondWait::SleepMs(LIRC_RECONNECTDELAY);
#endif /* LIRCSETTINGS */
                 if (Connect()) {
                    isyslog("reconnected to lircd");
                    break;
                    }
                 }
           }

        if (ready && ret > 21) {
           int count;
           char KeyName[LIRC_KEY_BUF];
           if (sscanf(buf, "%*x %x %29s", &count, KeyName) != 2) { // '29' in '%29s' is LIRC_KEY_BUF-1!
              esyslog("ERROR: unparseable lirc command: %s", buf);
              continue;
              }
           if (count == 0) {
#ifdef USE_LIRCSETTINGS
              if (strcmp(KeyName, LastKeyName) == 0 && FirstTime.Elapsed() < (1000 / (unsigned int)Setup.LircPushFreq))
#else
              if (strcmp(KeyName, LastKeyName) == 0 && FirstTime.Elapsed() < (1000 / LIRC_PUSHFREQ))
#endif /* LIRCSETTINGS */
                 continue; // skip keys coming in too fast
              if (repeat)
                 Put(LastKeyName, false, true);
              strcpy(LastKeyName, KeyName);
              repeat = false;
              FirstTime.Set();
              timeout = -1;
              }
           else {
#ifdef USE_LIRCSETTINGS
              if (LastTime.Elapsed() < (1000 / (unsigned int)Setup.LircRepeatFreq))
#else
              if (LastTime.Elapsed() < (1000 / LIRC_REPEATFREQ))
#endif /* LIRCSETTINGS */
                 continue; // repeat function kicks in after a short delay (after last key instead of first key)
#ifdef USE_LIRCSETTINGS
              if (FirstTime.Elapsed() < (unsigned int)Setup.LircRepeatDelay)
#else
              if (FirstTime.Elapsed() < LIRC_REPEATDELAY)
#endif /* LIRCSETTINGS */
                 continue; // skip keys coming in too fast (for count != 0 as well)
              repeat = true;
#ifdef USE_LIRCSETTINGS
              timeout = Setup.LircRepeatDelay;
#else
              timeout = LIRC_REPEATDELAY;
#endif /* LIRCSETTINGS */
              }
           LastTime.Set();
           Put(KeyName, repeat);
           }
        else if (repeat) { // the last one was a repeat, so let's generate a release
#ifdef USE_LIRCSETTINGS
           if (LastTime.Elapsed() >= (unsigned int)Setup.LircRepeatTimeout) {
#else
           if (LastTime.Elapsed() >= LIRC_REPEATTIMEOUT) {
#endif /* LIRCSETTINGS */
              Put(LastKeyName, false, true);
              repeat = false;
              *LastKeyName = 0;
              timeout = -1;
              }
           }
        }
}
