/*
 * extb plugin to VDR (C++)
 *
 * (C) 2004 Andreas Brachold <vdr04-at-deltab-dot-de>
 * based on Copyright (C) 1998 Christoph Bartelmus (lirc@bartelmus.de)
 *  rc -  application for sending IR-codes via lirc
 * 
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */


#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/un.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "lirc.h"
 
#include <vdr/tools.h>

#define PACKET_SIZE 256
/* three seconds */
#define TIMEOUT 3



cLircCmd::cLircCmd(char* szCmd,unsigned int nDelayMS)
{
    m_szCmd = szCmd;
    m_pTime = NULL;
    if(nDelayMS) {
        struct timeval t;
        if (gettimeofday(&t, NULL) == 0) {
            m_pTime = (struct timeval*)malloc(sizeof(struct timeval));
            if(m_pTime) {
                m_pTime->tv_sec = t.tv_sec;
                m_pTime->tv_usec = t.tv_usec + (nDelayMS * 1000);
                if(m_pTime->tv_usec < t.tv_usec)
                    ++m_pTime->tv_sec;
            }
        }
    }
}

cLirc::cLirc()
: m_szDevice(NULL)
, m_fd(-1)
, m_bShutdown(false)
, m_nQueued(0)
{
  Start();
}

cLirc::~cLirc()
{
  m_bShutdown = true;
  m_condVar.Broadcast();
  Cancel(3);

  close();
  if(m_szDevice) {
	    free(m_szDevice);
	    m_szDevice = NULL;
	}
}

volatile bool cLirc::m_bTimeout = false;

void cLirc::sigalrm(int sig)
{
  m_bTimeout = true;
}

const char *cLirc::read_string()
{
  static char buffer[PACKET_SIZE + 1] = "";
  char *end;
  static int ptr = 0;
  ssize_t ret;

  if(-1 == m_fd) {
    reopen();
  }

  if(ptr > 0) {
	    memmove(buffer, buffer + ptr, strlen(buffer + ptr) + 1);
	    ptr = strlen(buffer);
	    end = strchr(buffer, '\n');
  } else {
	    end = NULL;
	}
  alarm(TIMEOUT);
  while(end == NULL)
  {
    if(PACKET_SIZE <= ptr) {
		    moredsyslog("extb: lirc_read_string(%d): bad packet", m_fd );
		    ptr = 0;
		    return (NULL);
		}
    
    ret = read(m_fd, buffer + ptr, PACKET_SIZE - ptr);

	  if(ret <= 0 || m_bTimeout) {
		  if(m_bTimeout) {
			    moredsyslog("extb: lirc_read_string(%d): timeout", m_fd);
		  } else {
			    alarm(0);
			}
		  ptr = 0;
		  return (NULL);
    }
	  buffer[ptr + ret] = 0;
	  ptr = strlen(buffer);
	  end = strchr(buffer, '\n');
  }
  alarm(0);
  m_bTimeout = false;

  end[0] = 0;
  ptr = strlen(buffer) + 1;

  moredsyslog("extb: lirc_read_string(%d): result='%s'", m_fd, buffer);

  return (buffer);
}

enum packet_state {
    P_BEGIN,
    P_MESSAGE,
    P_STATUS,
    P_DATA,
    P_N,
    P_DATA_N,
    P_END
};

int cLirc::send_packet(const char *packet)
{
  int done, todo;
  const char *string, *data;
  char *endptr;
  enum packet_state state;
  int status;
  unsigned long n;
  unsigned long data_n = 0;

  if(-1 == m_fd) {
	    reopen();
	}

  moredsyslog("extb: cLirc::send_packet(fd=%d): %s", m_fd, packet);

  todo = strlen(packet);
  data = packet;
  while(todo > 0) {
    done = write(m_fd, (void *)data, todo);
	  if(done < 0) {
		  moredsyslog("extb: cLirc::send_packet(fd=%d): could not send packet: %s",
			  m_fd, strerror(errno));
		  return (-1);
		}
    data += done;
    todo -= done;
	}

  /* get response */
  status = 0;
  state = P_BEGIN;
  n = 0;
  while(1) {
    string = read_string();
    moredsyslog("extb: cLirc::send_packet(fd=%d): read= %s", m_fd, string);
    
    if(string == NULL)
      return (-1);
    
    switch (state) {
    case P_BEGIN:
      if(strcasecmp(string, "BEGIN") != 0) {
			    continue;
			}
      state = P_MESSAGE;
      break;
		case P_MESSAGE:
      if(strncasecmp(string, packet, strlen(string)) != 0 ||
         strlen(string) + 1 != strlen(packet)) {
			    state = P_BEGIN;
			    continue;
			}
      state = P_STATUS;
      break;
		case P_STATUS:
      if(strcasecmp(string, "SUCCESS") == 0) {
			    status = 0;
		  } else if(strcasecmp(string, "END") == 0) {
        status = 0;
        return (status);
      } else if(strcasecmp(string, "ERROR") == 0) {
        moredsyslog("extb: cLirc::send_packet(fd=%d): command failed: %s",
        m_fd, packet);
        status = -1;
      } else {
			    goto bad_packet;
			}
      state = P_DATA;
      break;
		case P_DATA:
      if(strcasecmp(string, "END") == 0) {
			    return (status);
      } else if(strcasecmp(string, "DATA") == 0) {
			    state = P_N;
			    break;
			}
      goto bad_packet;
		case P_N:
      errno = 0;
      data_n = strtoul(string, &endptr, 0);
      if(!*string || *endptr) {
			    goto bad_packet;
			}
		  if(data_n == 0) {
        state = P_END;
      } else {
			    state = P_DATA_N;
			}
      break;
		case P_DATA_N:
      moredsyslog("extb: cLirc::send_packet(fd=%d): next=%s", m_fd, string);
      n++;
      if(n == data_n)
			state = P_END;
      break;
		case P_END:
      if(strcasecmp(string, "END") == 0) {
			    return (status);
			}
      goto bad_packet;
      break;
		}
	}
bad_packet:
    moredsyslog("extb: cLirc::send_packet(fd=%d): bad return packet", m_fd);
    return (-1);
}

bool cLirc::open(const char *szDevice)
{
  if(m_szDevice != szDevice) //avoid reopen
  {
      if(m_szDevice)
	    free(m_szDevice);
      m_szDevice = strdup(szDevice);
  }

  struct sockaddr_un addr;
  
  struct sigaction act;
  act.sa_handler = sigalrm;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;		/* we need EINTR */
  sigaction(SIGALRM, &act, NULL);
  
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, m_szDevice, sizeof(addr.sun_path));
  m_fd = socket(AF_UNIX, SOCK_STREAM, 0);
  if(-1 == m_fd)
  {
    esyslog("extb: cLirc::open, could not open socket: %s", strerror(errno));
    return (false);
  }

  if(connect(m_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
  {
    esyslog("extb: cLirc::open, could not connect to socket: %s", strerror(errno));
    return (false);
  }

  dsyslog("extb: cLirc::open (fd=%d)", m_fd);


  return (true);
}

void cLirc::close()
{
  if(-1 != m_fd) {
	    dsyslog("extb: cLirc::close (fd=%d)", m_fd);
	    ::close(m_fd);
	    m_fd = -1;
	}
}

//////////////////////////////////////////////////////////////////////////////
/** cLirc::sendmore(const char *s)
Sende multiple lirc commands
@param - const char *s - commands seperated by space
@return - none
*/
void cLirc::sendmore(const char *s)
{
  unsigned int m = 0;
  const char* szDelim = ",; ";

  if(s){
    char* sbuf = strdup(s);
    if( sbuf ) {
      char* rbuf = strdup(sbuf);
      char* tmp = rbuf;

      if( rbuf ) {
        m = 0;
        char* next = strtok_r(sbuf, szDelim, &tmp);
        while(next && m < 64) {
          send(next,0);
          ++m;
          // check for next token
          next = strtok_r(0, szDelim, &tmp);
        }
        free(rbuf);
      }
      free(sbuf);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
/** void cLirc::send(const char *s)
Sende single lirc command
@param - const char *s - command
@return - none
*/
void cLirc::send(const char *s,unsigned int nDelayMS,const char *r)
{
  if(!s)
      return;
  
  for(;*s!=0 && *s == ' ';++s); //Skip leading space
  
  if(*s==0 || strlen(s) == 0) 
    return;

  //fprintf(stderr,"cLirc::send(%s)\n",s);
  
  char* szCmd = NULL;
  char* szRemove = NULL;

  asprintf(&szCmd, "send_once EXTB_TX %s\n", s);

  if(r)
  {
      for(;*r!=0 && *r == ' ';++r); //Skip leading space
      if(*r!=0 || strlen(r) != 0) 
      {  
         asprintf(&szRemove, "send_once EXTB_TX %s\n", r);
      }
  }  


  if(szCmd) {

    cMutexLock lock(&m_Mutex);
    if(szRemove)
    {
        m_queue.remove(szCmd);
        m_queue.remove(szRemove);
        free(szRemove);
    }
    if(!m_queue.add(szCmd,nDelayMS))
      free(szCmd);   //Queue full or Thread is dead     
    // Adjust Queued size
    m_nQueued = m_queue.size();
    m_condVar.Broadcast();  //Wake Thread up
  }
}
//////////////////////////////////////////////////////////////////////////////
/** void cLirc::Action(void)
The Worker as Thread, call from baseclass throuth Start()
@param - none
@return - none
*/
void cLirc::Action(void)
{
  dsyslog("extb: cLirc thread started (pid=%d)", getpid());

  while (!m_bShutdown) {
  
    char* szCmd = NULL;
    unsigned int msSleep = 60000;
    
    { // Protect the queue ++
      cMutexLock lock(&m_Mutex);
      cLircQueue::iterator i = m_queue.begin();
      if(m_queue.end() != i) {
        bool bExec = false;
        if(i->m_pTime) {
            struct timeval t;
            if (gettimeofday(&t, NULL) == 0) {
                int msDelta = 
                         ((i->m_pTime->tv_sec - t.tv_sec) * 1000)
                       + ((i->m_pTime->tv_usec - t.tv_usec) / 1000);
                if(msDelta <= 0)
                {
					bExec = true;
                    free(i->m_pTime);
				}
                else
                {
                    msSleep = msDelta;
                }
			}else  {
				bExec = true;
                free(i->m_pTime);
			}
        } else {
            bExec = true;
        }
            
        if(bExec)
        {
            szCmd = i->m_szCmd;
            m_queue.erase(i);
        }
      }
    } // ++
  
    // Send command
    if(szCmd) { 
       //fprintf(stderr,"extb: exec : %s", szCmd);fflush( stderr );
       for(unsigned int nRetry=1;!m_bShutdown && nRetry<=3 && (-1 == send_packet(szCmd));++nRetry) {
        esyslog("extb: cLirc::send(fd=%d) connection lost, %d.resend %s",m_fd,nRetry,szCmd);
      close();
      }
      free(szCmd);
    }
    {// Protect the queue ++
      cMutexLock lock(&m_Mutex);
      // readjust queued size to realsize
      m_nQueued = m_queue.size();
      // Go sleep if nothing todo, for one minute
      if((m_nQueued == 0 || msSleep != 60000) && !m_bShutdown) { 
        m_condVar.TimedWait(m_Mutex, msSleep); 
      }
    }
  }
  dsyslog("extb: cLirc thread closed (pid=%d)", getpid());
}

////////////////////////////////////////////////////////////////////////////////
// Wait until queue is empty
void cLirc::flush()
{
    for(unsigned int n=0;n < 10;++n) {
      { // Protect the queue ++
        cMutexLock lock(&m_Mutex); 
        if(m_nQueued == 0)
          break;
        else
          m_condVar.Broadcast();  //Wake Thread up
      } // ++
      usleep(50);
    }
}
