/*
 * Copyright Department C Inc. 2023.  All rights reserved.
 *
 * Rotuines to "packetize" AT commands to RYLR998 to avoid timeout +ERR=1
 * do: cc rylr998.c -o rylr998 -l pthread
 * R H Lamb 2023
 * Note: E220-900T22D that uses Semtech LLCC68 is 1/2 the price,better specs
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <pthread.h>

static int lparse(char *line,char *argv[],int maxargs,char delc)
{
  char *cp;
  int argc,qflag;

  if((cp = strchr(line,'\r')) != (char *)0) *cp = '\0';
  if((cp = strchr(line,'\n')) != (char *)0) *cp = '\0';

  for(argc=0;argc<maxargs;argc++) argv[argc] = (char *)0;

  for(argc=0;argc<maxargs;) {
    qflag = 0;
    while(*line == ' ' || *line == '\t') line++;
    if(*line == '\0') break;
    if(*line == '"') { line++; qflag = 1; }
    argv[argc++] = line;
    if(qflag) {
      if((line = strchr(line,'"')) == (char *)0) return -1;
      *line++ = '\0';
    } else {
      for(cp=line;*cp;cp++) {
        if(/**cp == ' ' || *cp == '\t' ||*/ *cp == delc) break;
      }
      if(*cp) *cp++ = '\0';
      line = cp;
    }
  }
  return argc;
}
static int rdump(unsigned char *ptr,int n)
{
  int i,j1,j2; char buf[80]; static char htoas[]="0123456789ABCDEF";
  j1 = j2 = 0;
  for(i=0;i<n;i++,j1+=3,j2++) {
    if((i&0xf) == 0) {
      if(i) { buf[j2]='\0'; printf("%s|\n",buf); }
      j1=0; j2=51; memset(buf,' ',80); buf[50]='|';
    }
    buf[j1] = htoas[(ptr[i]&0xf0) >> 4]; buf[j1+1] = htoas[ptr[i]&0x0f];
    if(ptr[i] >= 0x20 && ptr[i] < 0x80) buf[j2]=ptr[i]; else buf[j2]='.';
  }
  buf[j2]='\0'; printf("%s|\n",buf);
  return 0;
}

static uint8_t serlistenerrun=0;
static int serialfd=0;
static int serlistener(void *pm)
{
  int i,n;
  struct termios serial_oldtio,serial_newtio;
  char *dev;
  uint8_t c,buf[1024];

  dev = (char *)pm;
  
 reopen:
  if((serialfd=open(dev,(O_RDWR|O_NOCTTY))) < 0) {
    printf("Cant open %s\n",dev);
    return -1;
  }
  if(tcgetattr(serialfd,&serial_oldtio)) {
    printf("tcgetattr failed for %s\n",dev);
    close(serialfd);
    return -1;
  }
  memset(&serial_newtio,0,sizeof(serial_newtio));
  //serial_newtio.c_cflag = B115200|CS8|CLOCAL|CREAD|PARENB;
  serial_newtio.c_cflag = B115200|CS8|CLOCAL|CREAD;
  serial_newtio.c_iflag = IGNPAR;
  serial_newtio.c_oflag = 0;
  serial_newtio.c_lflag = 0; /* non-cannonical, no echo */
  serial_newtio.c_cc[VTIME] = 200;   /* 10 = 1 sec timeout */
  serial_newtio.c_cc[VMIN] = 0; /* return from read on any char rcvd */

  tcflush(serialfd,TCIFLUSH);
  if(tcsetattr(serialfd,TCSANOW,&serial_newtio)) {
    printf("tcsetattr failed for %s\n",dev);
    close(serialfd);
    return -1;
  }    
  n = 0;
  while(serlistenerrun) {

    i = read(serialfd,&c,1);
    if(i > 0) {
      if(c == '\r' || n >= sizeof(buf)) {
	rdump(buf,n);
	n = 0;
      }
      if(c == '\n') continue;
      buf[n++] = c;
    } else if(i == 0) {

      continue;
      
      printf("error: closing serial\n");
      close(serialfd);
      sleep(1);
      goto reopen;
    } else {
      printf("read error %s\n",strerror(errno));
      close(serialfd);
      sleep(1);
      goto reopen;
    }

    
  }

  printf("Done. Closing %s\n",dev);
  close(serialfd);
  return 0;
}

static int atcmd(int argc,char *argv[]) {
  char *p=argv[0]; // argv[1]
  write(serialfd,p,strlen(p));
  write(serialfd,"\r\n",2);
  return 0;
}
struct command {
  char *name;
  int (*function)();
};
struct command cmds[]; 
#define COMMAND_MAX_ARGS 20
static int cparse(struct command cmds[],char *line)
{
  struct command *cm;
  char *argv[COMMAND_MAX_ARGS];
  int argc,i;

  argc = lparse(line,argv,COMMAND_MAX_ARGS,' ');
  if(argv[0] == (char *)0) return 0;
  if(argv[0][0] == '#') return 0;
  for(cm=cmds;cm->name;cm++) {
    if(strncmp(argv[0],cm->name,strlen(argv[0])) == 0)
      break;
  }
  if(cm->name == (char *)0) {
    return atcmd(argc,argv);
    printf("Unknown Command\n");
    return -1;
  }
  i = (*cm->function)(argc,argv);
  return i;
}
static int go() { return 0; }
static int doexit() { return 3; }
static int help() {
  struct command *cm;
  for(cm=cmds;cm->name;cm++) printf(" %s\n",cm->name);
  return 0;
}
struct command cmds[] = {
  "",go, // first
  "exit",doexit,
  "?",help,
  (char *)0,NULL,
};

int main(int argc,char *argv[])
{
  pthread_t ths[2];
  int s,epollfd;
  struct epoll_event ev;
#define MAX_EVENTS 10
  struct epoll_event events[MAX_EVENTS];
#define DATA_BUFSIZE 1024
  uint8_t buf[DATA_BUFSIZE];
  char *dev;

  dev = "/dev/ttyUSB0";
  if(argc > 1) dev = argv[1];
  
  if((epollfd=epoll_create(10)) < 0) { // "10" is ignored but > 0
    printf("error: CONSOLE epoll_create %s\n",strerror(errno));
    goto endit;
  }
  s = 0; // 0=in 1=out 2=err CONSOLE
  ev.events = EPOLLIN;
  if(epoll_ctl(epollfd,EPOLL_CTL_ADD,s,&ev) == -1) {
    printf("%s: console epoll_ctl fail %s\n",__func__,strerror(errno));
    goto endit;
  }
  serlistenerrun = 1;
  if(pthread_create(&ths[0],NULL,(void *)serlistener,dev) != 0) {
    printf("error: Could not create serial listener thread: %s\n",strerror(errno));
    goto endit;
  }  
  while(1) {
    int nfds,ie;
    if((nfds=epoll_wait(epollfd,events,MAX_EVENTS,1000)) < 0) {
      printf("error: epoll_pwait %s\n",strerror(errno));
      goto endit;
    }
    if(nfds == 0) {// timeout
      //periodic_process(); // housekeeping
      continue;
    }
    for(ie=0;ie<nfds;ie++) {
	
      if(fgets(buf,sizeof(buf),stdin) > 0) {
	if(cparse(cmds,buf) == 3) {
	  printf("Good Bye\n");
	  serlistenerrun = 0;
	  goto endit;
	} else {
	  printf(">");
	}
	fflush(stdout);	  
      } else {
	printf("Abort\n");
	goto endit;
      }
      
    }
  }
 endit:
  sleep(1);
  printf("%s: Ending...\n",__func__);  
  return 0;
}
