/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/************************************************************************/
/*                  Copyright 1997-1999 Element 14 Ltd                  */
/*                                                                      */
/*  This material is the confidential trade secret and proprietary      */
/*  information of Element 14 Ltd.  It may not be reproduced, used      */
/*  sold, or transferred to any third party without the prior written   */
/*  consent of Element 14 Ltd.  All rights reserved.                    */
/*                                                                      */
/************************************************************************/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>

#include "sys/types.h"
#include "sys/socket.h"
#include "sys/time.h"
#include "netdb.h"
#include "netinet/in.h"
#include "socklib.h"
#define REMOTE_DEBUG
#include "remote.h"

#include "VersionNum"

static const char REMOTE_DEBUG_VERSION[] = Module_FullVersion;

#define REMOTE_DEBUG_MAX_LINE (10*1024)

static int remote_debug_poll=1;
static char *debug_line = NULL;

/*
 * compare two strings caselessly
 */
static int
remote_debug_caseless_strcmp(char *a, char *b)
{
  int d;

  while (*a || *b)
  {
    d = tolower( *(a++) ) - tolower( *(b++) );
    if (d) return d;
  }
  return 0;
}

/*
 * check the input port for any data, if there is, check it for a newline
 * and if terminated correctly, extract the line.
 */
int
debug_poll(debug_session *sess)
{
  static char sbuff[REMOTE_DEBUG_MAX_LINE];
  fd_set read_set;
  struct timeval tv;
  char	 *argv[20];
  int    argc = 0;
  char   *cp;
  int    more;

  if (!sess)
    return (0);

  remote_debug_poll=0; /* don't poll during debug_printf */

  do
  {
    more = 0;
    FD_ZERO(&read_set);
    FD_SET(sess->sock,&read_set);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    if (select(FD_SETSIZE,&read_set,NULL,NULL,&tv)>0)
    {
      if (FD_ISSET(sess->sock,&read_set))
      {
        int recv_len = recv(sess->sock, sbuff, REMOTE_DEBUG_MAX_LINE-1,MSG_PEEK);
        if (recv_len>0)
        {
          if ((cp=memchr(sbuff,'\n', recv_len))!=NULL)
          {
            if (recv(sess->sock, sbuff, (cp-sbuff+1),0)>0)
            {
              sbuff[cp-sbuff]='\0'; /* remove the new line char */
              /*
               * break down string
               */
              cp = sbuff;
              while (*cp != '\0')
              {
                while (*cp == ' ')
                  cp++;
                if (*cp == '\0')
                  break;
                argv[argc++] = cp;
                if (argc>=20)
                  break;
                while (*cp != '\0' && *cp != ' ')
                  cp++;
                if (*cp == 0)
                  break;
                *cp++ = '\0';
              }
              *cp++ = '\0';
              /*
               * what do we want to do
               */
              if (argc == 0 || argv[0][0]=='#' || argv[0][0] == 0)  /* ignore comments */
                ;
              else if (remote_debug_caseless_strcmp(argv[0],"PRIORITY")==0)
              {
                if (argc>1)
                {
                  sess->priority = atoi(argv[1]);
                  if (sess->priority>7)
                    sess->priority = 7;
                  debug_printf(sess,"(6) debug priority changed to %d\n",
                  			     sess->priority);
                }
              }
              else if (sess->cmd_handler) /* callback cmd handler if registered */
              {
		  char name[32];
		  int rc;
		  strncpy(name, argv[0], sizeof(name));
		  name[sizeof(name)-1] = 0;
		  rc = sess->cmd_handler(argc, argv, sess->cmd_handle);
		  debug_printf(sess,"(6) cmd '%s' returns %d\n", name, rc);
              }
              else if (remote_debug_caseless_strcmp(argv[0], ":ack")==0)
              {
                  /* Discard these silently */
              }
              else
              {
                  debug_printf(sess,"(6) unknown command '%s'\n",argv[0]);
              }

              more = 1; /* try for more input */
            }
          }
        }
      }
    }
  } while (more);

  remote_debug_poll=1; /* return to polling during debug_printf */
  return (0);
}

/*
 * send stuff to the debug session
 */
void
debug_printf(debug_session *sess, char *format, ...)
{
  va_list list;

  va_start (list, format);
  vsprintf(debug_line,format, list);
  va_end(list);

  debug_print_line(sess, debug_line);
}

void
debug_vprintf(debug_session *sess, const char *format, va_list list)
{
  vsprintf(debug_line,format, list);

  debug_print_line(sess, debug_line);
}

void
debug_dvprintf(debug_session *sess, int priority, const char *file, int line, const char *format, va_list list)
{
  int n = 0;

  if (priority != 0)
      n += sprintf(debug_line, "(%d)", priority);

  if (file)
      n += sprintf(debug_line+n, " %s:%d: ", file, line);

  vsprintf(debug_line+n, format, list);

  debug_print_line(sess, debug_line);
}


void
debug_print_line(debug_session *sess, const char *line)
{
  remote_debug_print_line (0u, sess, line, strlen (line));
}


void
remote_debug_print_line (unsigned int flags, debug_session *sess, const char *line, size_t len)
{
  unsigned char priority = 7;

  if (!sess)
    return;

  if (remote_debug_poll)
    debug_poll(sess);

  /* Not raw */
  if ((flags & 1) == 0)
  {
    /* Get opening bracket */
    if (line[0]=='(')
    {
      /* Find closing bracket */
      if (strchr (line,')') != NULL)
        priority = atoi (line+1);
    }
    /* If we're masking this line out, just return */
    if (priority > sess->priority)
      return;
    /* Send the info string to the socket */
    send (sess->sock, sess->info, strlen (sess->info), 0);
  }

  /* Send the actual debug data */
  send (sess->sock, line, len, 0);

  /* Not raw */
  if ((flags & 1) == 0)
  {
    /* And if we're not in raw mode, output a \n if the debug data doesn't end with one */
    if (line[len-1] != '\n')
      send (sess->sock, "\n", strlen ("\n"), 0);
  }
}


void
remote_debug_register_cmd_handler(debug_session *sess, remote_debug_cmd_handler fn, void *handle)
{
  if (sess)
  {
    sess->cmd_handler = fn;
    sess->cmd_handle = handle;
  }
}

/*
 * Reads environment variable Inet$DebugHost in the form valhalla:1448 to
 * obtain IP address and port number. Opens a socket to this host and returns
 * a structure to be used for subsequent debug calls. Debug session should be
 * closed using the remote_debug_session_close() function
 */
void
remote_debug_open(char *info, debug_session **db_sess)
{
  int rc;
  int debug_socket=-1;
  struct sockaddr_in sockaddr;
  struct hostent *hostent;
  debug_session *sess=NULL;
  char host[40];
  char varname[100];
  char *port;

  /* malloc to avoid problems in SVC mode */
  if (debug_line == NULL)
      debug_line = malloc(REMOTE_DEBUG_MAX_LINE);

  memset(&sockaddr, 0, sizeof(sockaddr));

  /*
   * where do we want to connect to
   */

  sprintf (varname, "Inet$DebugHost_%s", info);

  if (getenv (varname) == NULL)
  {
    if (getenv("Inet$DebugHost")==NULL)
      goto exit_gracefully;
    strncpy(host,getenv("Inet$DebugHost"),sizeof(host));
  }
  else
    strncpy(host,getenv(varname),sizeof(host));

  host[sizeof(host)-1] = '\0';

  /*
   * find port
   */
  if ((port=strchr(host,':'))==NULL)
    goto exit_gracefully;

  *(port++) = 0; /* terminate host name and leave pointer to port */
  sockaddr.sin_port = htons(atoi(port));
  if (sockaddr.sin_port==0)
    goto exit_gracefully;

  /*
   * allocate a socket
   */
  do
  {
    debug_socket = socket(AF_INET, SOCK_STREAM, 0);
  } while (debug_socket == -1);

  hostent = gethostbyname(host);
  if (hostent == NULL)
    goto exit_gracefully;

  sockaddr.sin_family = AF_INET;
#ifndef COMPAT_INET4
  sockaddr.sin_len = sizeof(sockaddr);
#endif
  sockaddr.sin_addr.s_addr = *(u_long*)hostent->h_addr;

  rc = connect(debug_socket, (struct sockaddr *)&sockaddr, sizeof(sockaddr) );
  if (rc<0)
    goto exit_gracefully;

  /*
   * allocate a debug session
   */
  if ((sess=malloc(sizeof(debug_session)))==NULL)
    goto exit_gracefully;

  sess->sock = debug_socket;
  sess->cmd_handler = NULL;
  sess->cmd_handle = NULL;

  if ((sess->info=malloc(strlen(info)+sizeof(": ")))==NULL)
    goto exit_gracefully;

  strcpy(sess->info, info);
  strcat(sess->info, ": ");

  sess->priority = 7;

  /*
   * send initial opening message
   */
  debug_printf(sess,"(6) Open Version %s", REMOTE_DEBUG_VERSION);

  *db_sess = sess;

  return;

exit_gracefully:
  if (debug_socket >= 0)
    socketclose(debug_socket);
  debug_socket = -1;

  if (sess)
  {
    if (sess->info)
      free(sess->info);
    free(sess);
  }
  *db_sess = NULL;
}

/*
 * close previously opened debug session
 */
void
remote_debug_close(debug_session *sess)
{
  if (sess)
  {
    debug_printf(sess,"(6) Close");
    if (sess->sock >= 0)
      socketclose(sess->sock);

    if (sess->info)
      free(sess->info);
    free(sess);
  }

  if (debug_line)
  {
     free(debug_line);
     debug_line = NULL;
  }

  return;
}


const char *remote_debug_version (void)
{
  return REMOTE_DEBUG_VERSION;
}