#include <sys/socket.h>   // for recv...
#include <stdlib.h>       // for exit...
#include <sys/file.h>
#include <unistd.h>
#include <termios.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/select.h>

#include "util.h"
#include "debug.h"
#include "dce.h"
#include "ip.h"
#include "ip232.h"

void *ip232_thread(void *arg) {
  dce_config *cfg = (dce_config *)arg;
  int rc;
  unsigned char buf[256];

  fd_set readfs;
  int max_fd = 0;

  LOG_ENTER();
  for (;;) {
    FD_ZERO(&readfs);
    FD_SET(cfg->dp[1][0], &readfs);
    max_fd = cfg->dp[1][0];
    FD_SET(cfg->sSocket, &readfs);
    max_fd = MAX(max_fd, cfg->sSocket);
    LOG(LOG_ALL, "Waiting for incoming ip232 connections");
    rc = select(max_fd + 1, &readfs, NULL, NULL, NULL);

    if (rc < 0) {
      // handle error
    } else {
      if (FD_ISSET(cfg->dp[1][0], &readfs)) {  // pipe
        rc = readPipe(cfg->dp[1][0], buf, sizeof(buf) - 1);
        LOG(LOG_DEBUG, "ip232 thread notified");
      }
      if (FD_ISSET(cfg->sSocket, &readfs)) {  // ip connection
        LOG(LOG_DEBUG, "Incoming ip232 connection");
        rc = ip_accept(cfg->sSocket);
        if(cfg->is_connected) {
          LOG(LOG_DEBUG, "Already have ip232 connection, rejecting new");
          // already have a connection... accept and close
          if(rc > -1) {
            close(rc);
          }
        } else {
          if(rc > -1) {
            cfg->ofd = rc;
            cfg->ifd = rc;
            cfg->is_connected = TRUE;
            cfg->ip232_dtr = FALSE;
            cfg->ip232_dcd = FALSE;
          }
        }
      }
    }
  }
  LOG_EXIT();
}

int ip232_init_conn(dce_config *cfg) {
  int rc = -1;

  LOG_ENTER();
  LOG(LOG_INFO, "Opening ip232 device");
  if(cfg->tty[0] == '-') { // use STDIN/STDOUT
    cfg->ofd = STDOUT_FILENO;
    cfg->ifd = STDIN_FILENO;
    cfg->is_connected = TRUE;
    cfg->ip232_dtr = FALSE;
    cfg->ip232_dcd = FALSE;
  } else {
    rc = ip_init_server_conn(cfg->tty, 25232);

    if (rc < 0) {
      ELOG(LOG_FATAL, "Could not initialize ip232 server socket");
      exit(-1);
    }
    if(-1 == pipe(cfg->dp[0])) {
      ELOG(LOG_FATAL, "ip232 thread incoming IPC pipe could not be created");
      exit(-1);
    }

    if(-1 == pipe(cfg->dp[1])) {
      ELOG(LOG_FATAL, "ip232 thread outgoing IPC pipe could not be created");
      exit(-1);
    }

    cfg->sSocket = rc;
    cfg->is_connected = FALSE;
    spawn_thread(ip232_thread, (void *)cfg, "IP232");
  }
  LOG(LOG_INFO, "ip232 device configured");
  LOG_EXIT();
  return 0;
}


int ip232_set_flow_control(dce_config *cfg, int status) {
  return 0;
}

int ip232_get_control_lines(dce_config *cfg) {

  return ((cfg->is_connected ? DCE_CL_LE : 0)
          | ((cfg->is_connected && cfg->ip232_dtr) ? DCE_CL_DTR : 0)
         );
}

int ip232_set_control_lines(dce_config *cfg, int state) {
  int dcd;
  int ri;
  unsigned char cmd[2];

  dcd = (state & DCE_CL_DCD) ? TRUE : FALSE;
  ri = (state & DCE_CL_RI) ? TRUE : FALSE;
  LOG(LOG_DEBUG, "ip232 control line state dcd:%x ri:%x", dcd, ri);
  if ((dcd != cfg->ip232_dcd) || (ri != cfg->ip232_ri)) {
    LOG(LOG_DEBUG, "reconfiguring virtual DCD/RI");
    cfg->ip232_dcd = dcd;
    cfg->ip232_ri = ri;
    if (cfg->is_connected) {
      LOG(LOG_DEBUG, "Sending data");
      cmd[0] = 255;
      cmd[1] = (dcd ? IP232_DCD : 0) | (ri ? IP232_RI : 0);
      write(cfg->ofd, cmd, sizeof(cmd));
    }
  }
  return 0;
}

int ip232_write(dce_config *cfg, unsigned char* data, int len) {
  int retval;
  int i = 0;
  int double_iac = FALSE;
  unsigned char text[1024];
  int text_len = 0;

  log_trace(TRACE_MODEM_OUT, data, len);
  retval = len;
  if (cfg->is_connected) {
    while(i < len) {
      if (double_iac) {
        text[text_len++] = 255;
        double_iac = FALSE;
        i++;
      } else {
        if(255 == data[i]) {
          text[text_len++] = 255;
          double_iac = TRUE;
        } else {
          text[text_len++] = data[i++];
        }
      }

      if(text_len == sizeof(text)) {
        retval = write(cfg->ofd, text, text_len);
        text_len = 0;
      }
    }
    if(text_len) {
      retval = write(cfg->ofd, text, text_len);
    }
  }
  return retval;
}

int ip232_read(dce_config *cfg, unsigned char *data, int len) {
  int res;
  //int rc;
  unsigned char buf[256];
  int i = 0;
  unsigned char ch;
  int text_len = 0;

  LOG_ENTER();
  if (len > sizeof(buf)) {
    LOG(LOG_FATAL, "ip232_read: len > sizeof(buf)");
    exit(-1);
  }

  if (cfg->is_connected) {
    res = recv(cfg->ifd, buf, len, 0);
    if (0 >= res) {
      LOG(LOG_INFO, "No ip232 socket data read, assume closed peer");
      ip_disconnect(cfg->ofd);
      cfg->is_connected = FALSE;
    } else {
      LOG(LOG_DEBUG, "Read %d bytes from ip232 socket", res);
      log_trace(TRACE_MODEM_IN, buf, res);

      while(i < res) {
        ch = buf[i];
        if (cfg->ip232_iac) {
          cfg->ip232_iac = FALSE;
          switch (ch) {
            case 0:
              cfg->ip232_dtr = FALSE;
              LOG(LOG_DEBUG, "Virtual DTR line down");
              break;
            case 1:
              cfg->ip232_dtr = TRUE;
              LOG(LOG_DEBUG, "Virtual DTR line up");
              break;
            case 255:
              data[text_len++] = 255;
              break;
          }
        } else {
          if (255 == ch) {
            cfg->ip232_iac = TRUE;
          } else {
            data[text_len++] = ch;
          }
        }
        i++;
      }
    }
  }
  LOG_EXIT();
  return text_len;
}
