/*
 * ISDN sniffer plugin for VDR
 *
 * Copyright (C) 2003, 2004 Andreas Kool <akool@gmx.de>
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id$
 */

#include <vdr/plugin.h>
#include <vdr/status.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#if defined(__cplusplus)
extern "C" {
#endif
#include "socket.h"  // this is isdnlog's "socket.h"!
#if defined(__cplusplus)
}
#endif
#include "i18n.h"
#include "player-isdnlog.h"


#define MAX_HOST_NAME   64


static const char *VERSION        = "0.0.3";
static const char *DESCRIPTION    = "ISDN monitor";
static const char *MAINMENUENTRY  = "Repeat last message";
static const char *VOICEFILE  	  = "/tmp/isdnlog.au";


static char server[MAX_HOST_NAME];
static int  port;
static int  mute;
static int  talk;

static socket_queue *isocket;
static int sock;
static CALL *info;
static bool active;
char   lastmsg[256];
static int recordings;
static int replays;


static void convert()
{
  auto   int    pid, status;
  auto   char   cmd[512];
  extern char **environ;


  sprintf(cmd, "echo \"%s\" | /video/mbrola/preproc /video/mbrola/Rules.lst /video/mbrola/Hadifix.abk | /video/mbrola/txt2pho -f | /video/mbrola/mbrola-linux-i386 /video/mbrola/de3 - -.au > %s",
    lastmsg, VOICEFILE);

  pid = fork();

  if (pid == -1) {
    printf("ERROR: fork failed: (%d) %s", errno, strerror(errno));
    return;
  }

  if (!pid) { // child
    execle("/bin/sh", "sh", "-c", cmd, 0, environ);
    printf("ERROR: exec failed for %s: (%d) %s", cmd, errno, strerror(errno));
    exit(127);
  }
  else
    wait(&status);
}


static void action()
{

  if (replays)
    printf("isdnlog: %s - suppressed, replaying is active!\n", lastmsg);
  else {
    printf("isdnlog: %s\n", lastmsg);

    if (mute && !talk)
      cDevice::PrimaryDevice()->ToggleMute();

    if (talk) {
      convert();
    
      cSndPlayerControl *control = new cSndPlayerControl(VOICEFILE);

      cControl::Launch(control);
    }
  
    if (mute && !talk)
      cDevice::PrimaryDevice()->ToggleMute();
  }
}

void disconnect(void)
{
  if (isocket != NULL) {
    close(isocket->descriptor);
    del_socket(&isocket, 0);
  }
}


static int eval_message(void)
{
  register char *p, *p1;
  auto 	   int   n, size, chan;


  do {
    size = Read(isocket);

    if (size <= 0) {
      disconnect();
      printf("Connection to server broken, errno=%d, size=%d!\n", errno, size);
      return 0;
    }

    if (isocket->waitstatus == WF_ACC) {
      switch (isocket->msg) {
	case MSG_ANN_ACC : isocket->servtyp = stoi((unsigned char *)isocket->msgbuf.buf, _MSG_2B);
			   isocket->waitstatus = WF_NOTHING;
			   break;

	case MSG_VERSION : if (strcmp(PROT_VERSION, isocket->msgbuf.buf)) {
                             printf("Server and client have different version:\n\nServer has version %s\nClient has version %s\n", isocket->msgbuf.buf, PROT_VERSION);
                             disconnect();
			     return 0;
			   }
			   break;

	case MSG_ANN_REJ : isocket->msg = MSG_CLOSE;
 	        	   isocket->msgbuf.used = 0;
 	        	   Write(isocket);
                           printf("Access to the server denied.\n");
                           disconnect();
			   return 0;

	         default : printf("WF_ACC: got unknown message type %d.\n", isocket->msg);
			   break;
      }
    }
    else {
      switch (isocket->msg) {
	case MSG_CALLER      :
	case MSG_WHO_IS      :
	case MSG_SERVER      :
	case MSG_CHANGE_CHAN : break;

	case MSG_CALL_INFO   : p = isocket->msgbuf.buf;
                               n = 0;

			       memset(info, 0, sizeof(CALL));

                               while (*p && (n < 20)) {
                                 if ((p1 = strchr(p, '|')) || (p1 = strchr(p, 0))) {
                                   *p1 = 0;

                    	           switch (n) {
                                      case 0 : chan = atoi(p);
                                   	       break;

                                      case 1 : info->stat = atoi(p);
                                   	       break;

                                      case 2 : info->dialin = atoi(p);
                                   	       break;

                                      case 3 : strcpy(info->num[_ME(info)], p);
                                     	       break;

                                      case 4 : strcpy(info->alias[_ME(info)], p);
                                     	       break;

                                      case 5 : strcpy(info->num[_OTHER(info)], p);
                                     	       break;

                                      case 6 : strcpy(info->vorwahl[_OTHER(info)], p);
                                     	       break;

                                      case 7 : strcpy(info->rufnummer[_OTHER(info)], p);
                                     	       break;

                                      case 8 : strcpy(info->alias[_OTHER(info)], p);
                                     	       break;

                                      case 9 : strcpy(info->area[_OTHER(info)], p);
                                     	       break;

                                     case 19 : strcpy(info->msg, p);
                                     	       break;
                    	           }

                                   p = p1 + 1;
                                 }
                                 n++;
                               }

  			       switch (info->stat) {
    			         case STATE_CONNECT    :
    				 case STATE_BYTE       :
    				 case STATE_AOCD       :
    				 case STATE_TIME       :
    				 case STATE_CAUSE      :
                                 case STATE_HUPTIMEOUT :
    				 case STATE_HANGUP     : break;

    				 case STATE_RING       : if (info->dialin && !memcmp(info->msg, "RING", 4)) {
                                      		       	   char *other = info->alias[_OTHER(info)]; // try alias
                                                           
                                                           if (!*other)	 			    // none, use number 
    							     other = info->rufnummer[_OTHER(info)];

                                                           if (!*other)				    // none ...  
				   		      	     sprintf(lastmsg, tr("Call for %s"),
							       info->alias[_ME(info)]);
                                                           else
				   		      	     sprintf(lastmsg, tr("Call from %s, %s for %s"),
							       other,
							       info->area[_OTHER(info)],
							       info->alias[_ME(info)]);

    				      		           action();
    				 			 }
    				      		         break;

    					       default : printf("Internal error: got unknown message type in SetCallInfo: %d.\n", info->stat);
  	      	      	 		    	         break;
    			       }
			       break;

	             default : printf("Default: got unknown message type %d.\n", isocket->msg);
			       break;
      }
    }

  } while (isocket->status == NEXT_MSG);

  return 1;
}

static int connect_to_isdnlog(void)
{
  auto struct passwd *pw;


  isocket = NULL;

  if ((sock = client_connect(server, port)) >= 0) {

    if ((pw = getpwuid(getuid())) != NULL) {

      if (!add_socket(&isocket, sock)) {

        isocket->msg = MSG_ANNOUNCE;
	msgcpy(isocket, pw->pw_name, strlen(pw->pw_name) + 1);

	Write(isocket);

	isocket->waitstatus = WF_ACC;

	return 1;
      }
      else
        printf("Out of memory!\n");
    }
    else
      printf("Oops, You are nobody!?!\n");
  }
  else
    printf("Can not connect to host %s on port %d\n", server, port);

  return 0;
}

static int listen_to_isdnlog(void)
{
  register int    cc = 0;
  auto	   fd_set readmask;


  if (connect_to_isdnlog()) {

    FD_ZERO(&readmask);
    FD_SET(isocket->descriptor, &readmask);

    info = (CALL *)malloc(sizeof(CALL));
    active = 1;

    while (active) {
      if (select(isocket->descriptor + 1, &readmask, NULL, NULL, NULL))
        cc = eval_message();

        if (!cc) // connection broken
          active = false;
    }
  }

  return 0;
}


// --- cMenuSetupIsdnlog ----------------------------------------------------

class cMenuSetupIsdnlog : public cMenuSetupPage {
protected:
  virtual void Store(void);
public:
  cMenuSetupIsdnlog(void);
};

// --- cIsdnlog --------------------------------------------------------

class cIsdnlog : public cThread {
private:
  virtual void Action(void);
public:
  cIsdnlog(void);
  ~cIsdnlog(void);
};


cIsdnlog::cIsdnlog()
{
  Start();
}


cIsdnlog::~cIsdnlog()
{
  active = 0;
  Cancel(0);

  disconnect();
}


void cIsdnlog::Action(void)
{
  if (!listen_to_isdnlog())
    printf("isdnlog: cannot start ISDN sniffer\n");
}


// --- IsdnlogStatus ------------------------------------------------------

class cIsdnlogStatus : public cStatus {

public:
  void IsdnlogStatus();
    
    
protected:
  virtual void Recording(const cDevice *Device, const char *Name);
  virtual void Replaying(const cControl *Control, const char *Name);
};

void cIsdnlogStatus::IsdnlogStatus()
{
  recordings = 0;
  replays = 0;
}

void cIsdnlogStatus::Recording(const cDevice *Device, const char *Name)
{
  if (Name == NULL)
    recordings--; // recording stopped
  else
    recordings++; // recording started
}

void cIsdnlogStatus::Replaying(const cControl *Control, const char *Name)
{
  if (Name == NULL)
    replays--; // replaying stopped
  else
    replays++; // replaying started
}

// --- cPluginIsdnlog ----------------------------------------------------

class cPluginIsdnlog : public cPlugin {
private:
  cIsdnlog *isdnlog;
public:
  cPluginIsdnlog(void);
  virtual ~cPluginIsdnlog();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return tr(DESCRIPTION); }
  virtual bool Initialize(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return tr(MAINMENUENTRY); }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);

private:
  cIsdnlogStatus *IsdnlogStatus;
};


cPluginIsdnlog::cPluginIsdnlog(void)
{
  strcpy(server, "localhost");
  port = SERV_PORT;
  mute = 1;
  talk = 1;
  info = (CALL *)NULL;
  active = 0;
  isdnlog = 0;

  IsdnlogStatus = NULL;

  *lastmsg = 0;
  // Test
  sprintf(lastmsg, "Anruf von Hans Mustermann aus Musterhausen für Klaus Schmidinger");
}

cPluginIsdnlog::~cPluginIsdnlog()
{
  if (info)
    free(info);

  if (IsdnlogStatus)
    delete IsdnlogStatus;
}

bool cPluginIsdnlog::Initialize(void)
{
  RegisterI18n(IsdnlogPhrases);

  if (isdnlog)
    delete isdnlog;

  isdnlog = new cIsdnlog;
  IsdnlogStatus = new cIsdnlogStatus;

  return true;
}

void cPluginIsdnlog::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginIsdnlog::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.

  if (*lastmsg)
    action();

  return (cOsdObject *)NULL;
}

cMenuSetupPage *cPluginIsdnlog::SetupMenu(void)
{
  return new cMenuSetupIsdnlog;
}

bool cPluginIsdnlog::SetupParse(const char *Name, const char *Value)
{
  if      (!strcasecmp(Name, "Server")) strn0cpy(server, Value, MAX_HOST_NAME);
  else if (!strcasecmp(Name, "Port"))  	port = atoi(Value);
  else if (!strcasecmp(Name, "Mute"))   mute = atoi(Value);
  else if (!strcasecmp(Name, "Talk")) 	talk = atoi(Value);
  else
    return false;

  return true;
}


cMenuSetupIsdnlog::cMenuSetupIsdnlog(void)
{
  static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789." };

  SetSection(tr("ISDNlog"));

  Add(new cMenuEditStrItem( tr("Setup.isdnlog$Server"), server, MAX_HOST_NAME, allowed));
  Add(new cMenuEditIntItem( tr("Setup.isdnlog$Port"), 	&port, 1, 65535));
  Add(new cMenuEditBoolItem(tr("Setup.isdnlog$Mute"),   &mute, tr("no"), tr("yes")));
  Add(new cMenuEditBoolItem(tr("Setup.isdnlog$Talk"),   &talk, tr("no"), tr("yes")));
}

void cMenuSetupIsdnlog::Store(void)
{
  SetupStore("Server", server);
  SetupStore("Port",   port);
  SetupStore("Mute",   mute);
  SetupStore("Talk",   talk);
}

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