/***************************************************************************
                          svdrpc.cpp  -  description
                             -------------------
    begin                : Sat Aug  5 13:32:09 MEST 2000
    copyright            : (C) 2000 by Guido
    email                : gfiala@s.netic.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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.                                   *
 *                                                                         *
 ***************************************************************************/
#include "svdrpc.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
//#include <linux/time.h>
#include <unistd.h>
#include <errno.h>

// --- cSocket ---------------------------------------------------------------

cSocket::~cSocket()
{
  Close();
}

void cSocket::Close(void)
{
  if (sock >= 0) {
     close(sock);
     sock = -1;
     }
}

bool cSocket::Open(int Port)
{
  port = Port;
  sock = -1;
  if (sock < 0) {
     // create socket:
     sock = socket(PF_INET, SOCK_STREAM, 0);
     if (sock < 0) {
        perror("socket");
        port = 0;
        return false;
        }
     // make it non-blocking:
     int oldflags = fcntl(sock, F_GETFL, 0);
     if (oldflags < 0) {
        perror("fcntl-GET");
        return false;
        }
     oldflags |= O_NONBLOCK;
     if (fcntl(sock, F_SETFL, oldflags) < 0) {
        perror("fcntl-SET");
        return false;
        }
     //ToDo: make Socket-keep-alive...
  }
  return true;
}

int cSocket::Connect(void)
{
  if (Open(port)) {
     struct sockaddr_in name;
     name.sin_family = AF_INET;
     name.sin_port = htons(port);
     name.sin_addr.s_addr = 0x0100007f;
     uint size = sizeof(name);
     errno=0;
     int result = connect(sock, (struct sockaddr *)&name, size);
     if ((result == -1) && (errno==EINPROGRESS))//yes, thats like spec!
     {
        return sock;
     }
     else
     {
        perror("connect");
     }
  }
  return -1;
}

// --- cSVDRPC ----------------------------------------------------------------

bool cSVDRPC::Connected()
{
	if(filedes==-1)
		return false;
	else
		return true;
}

cSVDRPC::~cSVDRPC()
{
  Close();
}

void cSVDRPC::Open(int Port)
{
  port=Port;
  name[0]=0;
  if (socket.Open(Port))
    filedes = socket.Connect();
  else
    filedes = -1;
  outstandingReply=1;
  ReadReply();//lese Greeting...
}

void cSVDRPC::Close(void)
{
  close(filedes);
	socket.Close();
  filedes=-1;
}

bool cSVDRPC::Send(const char *s, int length)
{
  int ret=0;
  int retrycount=0;
  retry://we try again
  if(filedes>0)
  {
      if (length < 0) length = strlen(s);      
      outstandingReply++;
      int wbytes=0;
      char dummy[]="\0";

      wbytes=write(filedes, s, length);//writes always lenght bytes in practice
      if (wbytes == length) {
         ret=(1==write(filedes,dummy,1));//dummy only, second write detects if socket is closed in practice
      }
      if (!ret) //if we could not write or did'nt get a reply
 		{
           //perror("svdrpc-write:");
  			  Close();//reopen connection in the case something went wrong:
  			  if (socket.Open(port))
               filedes = socket.Connect();
  			  else
  			    filedes = -1;
  			  outstandingReply=1;
  			  //ReadReply();//lese Greeting...
             if(retrycount++<3) goto retry;//we try again 3 times, if it does not work we have probably someone else using the port!
  			  ret=false;
   	}
  }
  else
  {
  	ret=false;
  }
  return ret;
}

bool cSVDRPC::ReadReply()
{
  int n=0,i=0,rbytes=0,SIZE=MAXCMDBUFFER-1;
  buf[0]=0;
  if (filedes >= 0)
  {
    fd_set set;
    FD_ZERO(&set);
    FD_SET(filedes, &set);
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 200;
    do
    {
      if(-1!=select(filedes+1, &set, NULL, NULL, &timeout)) {
        n = read(filedes, buf + rbytes,1);
        if(n<=0) break;//socket closed!
        rbytes+=n;
        if (rbytes == SIZE) break;
      } else {
        return true;//no reply yet!
      }
    }
    while ((buf[rbytes-1]!='\n') && (++i<MAXCMDBUFFER));
    if(rbytes>0)
    {
      buf[rbytes]=0;
		if((outstandingReply>0)&&(buf[rbytes-1]=='\n'))outstandingReply--;
      return true;//(outstandingReply==0);
    }
    else
    {
      buf[0]=0;
    }
  }
  return false;
}

bool cSVDRPC::CmdQuit()
{
  char *Option=NULL;
  asprintf(&Option,"Quit\r\n");
  bool result=Send(Option);
  ReadReply();
  delete Option;
  return result;
}

//------------------------------------------------------------------------------------------
bool cSVDRPC::CmdTKey(const char *key)
{
  char *Option=NULL;
  asprintf(&Option,"HITK %s\r\n",key);
  bool result=Send(Option);
  ReadReply();
  delete Option;
  return result;
}

const char* cSVDRPC::CmdChan()
{
	int n=0;
  while(outstandingReply>0 && Connected() && n++<100)
  {
		ReadReply();
  }
  char *Option=NULL;
  asprintf(&Option,"CHAN\r\n");
  Send(Option);
  delete Option;
  int id,chan;
  n=0;
  do
  {
		ReadReply();
  }
  while(outstandingReply>0 && n++<50);
  if(3!=sscanf(buf,"%d%d%20s",&id,&chan,name))
  { //can be just number+channel too:
    if(2!=sscanf(buf,"%d%20s",&chan,name))
    {
      name[0]=0;chan=0;
    }
  }
  return name;
}
