/*
 * lircrc.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <getopt.h>
#include <vdr/plugin.h>
#include <lirc/lirc_client.h>
#include <vdr/status.h>
#include <vdr/remote.h>
#ifndef LIRCRC_USER_FILE
#define LIRCRC_USER_FILE ".lircrc"
#endif

static const char *VERSION        = "0.0.2";
static const char *DESCRIPTION    = "Adds LIRC support by using '.lircrc'";

class cThreadLircrc;

class cPluginLircrc : public cPlugin {
friend class cThreadLircrc;
private:
  // Add any member variables or functions you may need here.
  cThreadLircrc* m_pThreadLircrc;
  char*          m_pszLircrcProgArg;
  char*          m_pszLircrcFileArg;
  bool           m_bKillVDRsLircRemoteArg;
protected:
  struct lirc_config* m_pLircConfig;
public:
  cPluginLircrc(void);
  virtual ~cPluginLircrc();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return DESCRIPTION; }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
};

class cThreadLircrc : protected cThread
{
friend class cPluginLircrc;
private:
  cPluginLircrc* m_pPluginLircrc;
  virtual void Action(void);
public:
  cThreadLircrc(cPluginLircrc* pPluginLircrc);
};

class cKillVDRsLircRemote : private cStatus
{
private:
  const char* m_pszPluginName;
public:
  cKillVDRsLircRemote(const char* pszPluginName);
private:
  virtual void OsdClear(void)
  {
    KillIt();
  }
  void KillIt(void);
};

cKillVDRsLircRemote::cKillVDRsLircRemote(const char* pszPluginName)
{
  m_pszPluginName = pszPluginName;
}

void cKillVDRsLircRemote::KillIt(void)
{
  cRemote* pRemote = Remotes.Last();
  while (pRemote)
  {
    if (strcmp(pRemote->Name(), "LIRC") == 0)
    {
      esyslog("%s: killing VDRs LIRC remote", m_pszPluginName);
      Remotes.Del(pRemote);
      break;
    }
    pRemote = Remotes.Prev(pRemote);
  }
  delete this;
}

cThreadLircrc::cThreadLircrc(cPluginLircrc* pPluginLircrc)
{
  m_pPluginLircrc = pPluginLircrc;
}

void cThreadLircrc::Action(void)
{
  char *pszLircCode;
  char *pszLircConfig;
  int ret;
  while(!lirc_nextcode(&pszLircCode))
  {
    if(pszLircCode == NULL)
      continue;
    while(!(ret = lirc_code2char(m_pPluginLircrc->m_pLircConfig, pszLircCode, &pszLircConfig)) && pszLircConfig)
    {
      char* pszNextKey = pszLircConfig;
      do
      {
        char* pszKey = pszNextKey;
        pszNextKey = strpbrk(pszLircConfig, " \t");
        if (pszNextKey)
          *(pszNextKey++) = '\0';
        eKeys key = cKey::FromString(pszKey);
        if (key != kNone)
          cRemote::Put(key);
      }
      while (pszNextKey);
    }
    free(pszLircCode);
    if(ret == -1) 
      break;
  }
  esyslog("%s: error - LIRC connection broken, lircd killed?", m_pPluginLircrc->Name());
}

cPluginLircrc::cPluginLircrc(void)
{
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  m_pszLircrcProgArg = NULL;
  m_pszLircrcFileArg = NULL;
  m_bKillVDRsLircRemoteArg = true;
  m_pLircConfig = NULL;
  m_pThreadLircrc = NULL;
}

cPluginLircrc::~cPluginLircrc()
{
  // Clean up after yourself!
  if (m_pLircConfig)
  {
    delete m_pThreadLircrc;
    lirc_freeconfig(m_pLircConfig);
    lirc_deinit();
  }
}

const char *cPluginLircrc::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return "  -p <name>, --prog=<name>   name of 'prog' in .lircrc (default is 'VDR')\n"
         "  -k,        --kill          do not kill VDRs LIRC Remote (default is to kill it)\n"
         "  [<config_file>]            name and path of the config file (default is $HOME/.lircrc)\n";
}

bool cPluginLircrc::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  bool bMoreOptions = true;
  opterr = 0;
  do
  {
    static struct option aOptions[] =
    {
      { "prog", required_argument, 0, 'p' },
      { "kill", no_argument,       0, 'k' },
      { NULL }
    };
    switch(getopt_long(argc, argv, ":n:k", aOptions, NULL))
    {
    case 'p':
      m_pszLircrcProgArg = optarg;
      break;
    case 'k':
      m_bKillVDRsLircRemoteArg = false;
      break;
    case '?':
      esyslog("%s: error - unkown option %s [arg #%d] - ignored", Name(), argv[optind-1], optind-1);
      break;
    case ':':
      esyslog("%s: error - missing argument for option %s [arg #%d] - ignored", Name(), argv[optind-1], optind-1);
      break;
    default:
      bMoreOptions = false;
      break;
    }
  }
  while(bMoreOptions);
  if (optind < argc)
    m_pszLircrcFileArg = argv[optind++];
  if (optind < argc)
    esyslog("%s: error - extra arguments '%s'... [arg #%d...] - ignored", Name(), argv[optind], optind);
  return true;
}

bool cPluginLircrc::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  if (!m_pszLircrcProgArg)
    m_pszLircrcProgArg = "VDR";
  if (lirc_init(m_pszLircrcProgArg, false) == -1)
    esyslog("%s: fatal error - can't init LIRC (%s)", Name(), m_pszLircrcProgArg);
  else
  {
    char* pszLircrcFile = NULL;
    if (!m_pszLircrcFileArg)
    {
      char* pszHome = getenv("HOME");
      if (!pszHome)
        pszHome="/";
      pszLircrcFile = (char*)malloc(strlen(pszHome)+1+strlen(LIRCRC_USER_FILE)+1);
      if (!pszLircrcFile)
        esyslog("%s: fatal error - out of memory", Name());
      else
      {
        strcpy(pszLircrcFile, pszHome);
        if ((strlen(pszHome) > 0) && (pszLircrcFile[strlen(pszLircrcFile)-1] != '/'))
          strcat(pszLircrcFile,"/");
        strcat(pszLircrcFile, LIRCRC_USER_FILE);
        m_pszLircrcFileArg = pszLircrcFile;
      }
    }
    if (   m_pszLircrcFileArg
        && lirc_readconfig(m_pszLircrcFileArg, &m_pLircConfig, NULL))
      esyslog("%s: fatal error - can't read '%s'", Name(), m_pszLircrcFileArg);
    if (!m_pLircConfig)
      lirc_deinit();
    if (pszLircrcFile)
      free(pszLircrcFile);
  }
  return true;
}

bool cPluginLircrc::Start(void)
{
  // Start any background activities the plugin shall perform.
  if (m_pLircConfig)
  {
    if (!(m_pThreadLircrc = new cThreadLircrc(this)))
      esyslog("%s: fatal error - out of memory", Name());
    else if (!m_pThreadLircrc->Start())
      esyslog("%s: fatal error - can't start thread", Name());
    else
    {
      esyslog("%s: background thread started for '%s' of '%s'", Name(), m_pszLircrcProgArg, m_pszLircrcFileArg);
      if (m_bKillVDRsLircRemoteArg)
        new cKillVDRsLircRemote(Name());
    }
  }
  return true;
}

VDRPLUGINCREATOR(cPluginLircrc); // Don't touch this!
