/* procuucico.c -- Process .Log/uucico/
 *
 * This file is part of TUA.
 * 
 *   Copyright (C) 1991,96  Lele Gaifax
 *
 *   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.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include "tua_4_hdb.h"

#if HDB_UUCP

#include <math.h>		/* for rint() */

#if defined(DIRENT) || defined(_POSIX_VERSION)
#include <dirent.h>
#define NLENGTH(dirent) (strlen((dirent)->d_name))
#else /* not (DIRENT or _POSIX_VERSION) */
#define dirent direct
#define NLENGTH(dirent) ((dirent)->d_namlen)
#ifdef USG
#ifdef SYSNDIR
#include <sys/ndir.h>
#else /* not SYSNDIR */
#include <ndir.h>
#endif /* not SYSNDIR */
#else /* not USG */
#include <sys/dir.h>		/* Assume SYSDIR, or not if you want. */
#endif /* not USG */
#endif /* not (DIRENT or _POSIX_VERSION) */

#define strequ(str1, str2)  (strcmp((str1), (str2)) == 0)

static void
DEFUN (UpdateProcess, (pr),
       process_status_t * pr)
{
  system_rec_t * sr = pr->System;

  if (sr && pr->Status != TUUS_NONE)
    {
      if (pr->StartTime != 0.0 && pr->StartTime < pr->EndTime)
	{
	  double jdiff = pr->EndTime - pr->StartTime;
	  double diff;
	  
	  diff = rint (jdiff * 24.0 * 60.0 * 60.0);
	  
	  sr->TimeConnect += diff;
	  sr->TimePayedBy[pr->WhoIsPaying] += diff;
	}

      sr->Calls++;
      if (pr->Status == TUUS_CALLING)
	{
	  pr->System->CallsFAIL++;
	}
      else if (pr->Status != TUUS_ERROR)
	{
	  sr->CallsOK++;
	  
	  if (pr->WhoIsPaying == LOCAL_SYSTEM)
	    sr->CallsOut++;
	  else
	    sr->CallsIn++;
	}
      else
	{
	  if (pr->LoginPassed)
	    sr->CallsSTOPPED++;
	  else
	    sr->CallsFAIL++;
	}
    }
  
  pr->Killed = TRUE;
}


#define NUMELEM(array) (sizeof(array) / sizeof(array[0]))
typedef enum mesg_ids
{
  I_DONT_KNOW = -1,
  MSG_CAN_NOT_CALL,
  MSG_CONN_FAILED,
  MSG_FAILED,
  MSG_INPUT_FAILURE,
  MSG_LOGIN_FAILED,
  MSG_OK,
  MSG_SUCCEEDED,
  MSG_CAUGHT,
  MSG_REQUEST,
  MSG_REMOTE_REQUESTED,
  MSG_LOCKED,
  MSG_BAD_READ,
  MSG_HANDSHAKE_FAILED,
  MSG_NO_CALL,
  MSG_LOST_LINE,
  MSG_TAYLOR_HANDSHAKE_SUCCESSFUL,
  MSG_TAYLOR_CALLING_SYSTEM,
  MSG_TAYLOR_CALL_COMPLETE,
  MSG_TAYLOR_ERROR,
  MSG_TAYLOR_SENDING,
  MSG_TAYLOR_RECEIVING
} mesg_id_t;

static mesg_id_t
DEFUN (DecodeMsg, (msg),
       char *msg)
{
  register idx;

  static struct
    {
      char *mesg;
      mesg_id_t mesg_id;
    }
  HandledMsg[] =
  {
    { "BAD READ ", MSG_BAD_READ },
    { "CAN NOT CALL ", MSG_CAN_NOT_CALL },
    { "CAUGHT ", MSG_CAUGHT },
    { "CONN FAILED ", MSG_CONN_FAILED },
    { "Calling system ", MSG_TAYLOR_CALLING_SYSTEM },
    { "Call complete ", MSG_TAYLOR_CALL_COMPLETE },
    { "ERROR: ", MSG_TAYLOR_ERROR },
    { "FAILED ", MSG_FAILED },
    { "HANDSHAKE FAILED ", MSG_HANDSHAKE_FAILED },
    { "Handshake successful ", MSG_TAYLOR_HANDSHAKE_SUCCESSFUL },
    { "IN SEND/SLAVE MODE ", MSG_INPUT_FAILURE },
    { "LOGIN FAILED ", MSG_LOGIN_FAILED },
    { "LOCKED ", MSG_LOCKED },
    { "LOST LINE ", MSG_LOST_LINE },
    { "NO CALL ", MSG_NO_CALL },
    { "OK ", MSG_OK },
    { "REQUEST ", MSG_REQUEST },
    { "Receiving ", MSG_TAYLOR_RECEIVING },
    { "REMOTE REQUESTED ", MSG_REMOTE_REQUESTED },
    { "SUCCEEDED ", MSG_SUCCEEDED },
    { "Sending ", MSG_TAYLOR_SENDING }
  };

  for (idx = 0; idx < NUMELEM (HandledMsg) &&
       *msg >= *(HandledMsg[idx].mesg); idx++)
    {
      if (*msg == *(HandledMsg[idx].mesg) &&
	  strbegcmp (msg, HandledMsg[idx].mesg) == 0)
	return HandledMsg[idx].mesg_id;
    }
  return I_DONT_KNOW;
}

/*
 * This function is a little bit intricated: in my opinion it is the weak
 * point of TUA; it tries to be smart in calculating the timings of the
 * connections, and to keep track of the errors, but is seems that
 * sometimes some garbage is put in by uucico, and that fools the
 * "algorithm".
 * In a few words, it reads a line at a time from a uucico log, an tries
 * to understand what's going on, computing the time spent "speaking"
 * with the remote, until an error is found or the end of the
 * conversation is reached.
 */
static void
DEFUN (ProcUucicoEntry, (fp, sys),
       FILE * fp AND
       char *sys)

{
  system_rec_t *sr = insert_system (sys);
  struct uucico_log lentry;
  Julian_t first_line_timestamp = 0.0, last_timestamp = 0.0;

  lentry.Command = 0;
  
#ifdef DEBUG
  if (be_verbose)
    fprintf (stderr, "\n%s:\n", sys);
#endif
  
  while (GetUucicoLog (fp, &lentry) == OK)
    {
      process_status_t * pr = insert_process (lentry.ProcessId);
      char * command;

      pr->System = sr;
      pr->EndTime = lentry.TimeStamp;
      
      if (first_line_timestamp == 0.0)
        first_line_timestamp = lentry.TimeStamp;

      if (lentry.TimeStamp > last_timestamp)
	last_timestamp = lentry.TimeStamp;

      command = strtok (lentry.Command, "(");
      switch (DecodeMsg (command))
	{
	case MSG_SUCCEEDED:
	  pr->Status = TUUS_HANDSHAKE;
	  pr->StartTime = lentry.TimeStamp;
	  pr->WhoIsPaying = LOCAL_SYSTEM;
	  break;

	case MSG_OK:
	  if (strequ (strtok (NULL, ") "), "startup"))
	    {
	      sr->LastConnection = lentry.TimeStamp;
	      pr->LoginPassed = TRUE;
	      if (pr->StartTime == 0.0)
		{
		 /* It is an inbound calls (it didn't find a SUCCEEDED yet) */
		  pr->WhoIsPaying = REMOTE_SYSTEM;
		  pr->StartTime = lentry.TimeStamp;
		}
	    }
	  else
	    {
	     /* This is a line like "OK (convers..complete PORTNAME ...)" */
	      pr->Status = TUUS_OK;
	      UpdateProcess (pr);
	    }
	  break;

	case MSG_CAN_NOT_CALL:
	case MSG_CONN_FAILED:
	  pr->LoginPassed = FALSE;
	  pr->Status = TUUS_ERROR;
	  break;

	case MSG_HANDSHAKE_FAILED:
	case MSG_INPUT_FAILURE:
	  pr->LoginPassed = TRUE;
	  pr->Status = TUUS_ERROR;
	  break;

	case MSG_FAILED:
	 /*
           * It can be either "FAILED (conversation complete)" or "FAILED
           * (CAN'T READ...)"  In the latter case, it's not a fatal
           * error, and so the conversation keeps going.
           */
	  if (strequ (strtok (NULL, ")"), "conversation complete"))
	    {
	      pr->Status = TUUS_ERROR;
	      UpdateProcess (pr);
	    }
	  break;

	case MSG_LOGIN_FAILED:
	  pr->LoginPassed = FALSE;
	  pr->Status = TUUS_ERROR;
	  break;

	case MSG_CAUGHT:
	  pr->Status = TUUS_ERROR;
	  UpdateProcess (pr);
	  break;

	case MSG_REQUEST:
	case MSG_REMOTE_REQUESTED:
	  pr->Status = TUUS_SENDING;
	  break;
	    
	case MSG_LOST_LINE:
	case MSG_NO_CALL:
	case MSG_BAD_READ:
	case MSG_LOCKED:
	  break;

	case MSG_TAYLOR_HANDSHAKE_SUCCESSFUL:
	  {
	    /* Line of type:
	     * Handshake successful (login proxima port tty1A protocol 'g' packet size 64 window 3) */
	    
	    pr->LoginPassed = TRUE;
	    pr->Status = TUUS_HANDSHAKE;
	    if (pr->StartTime == 0.0)
	      {
		/* It is an inbound calls (it didn't find a Calling System yet) */
		pr->WhoIsPaying = REMOTE_SYSTEM;
		pr->StartTime = lentry.TimeStamp;
		sr->LastConnection = lentry.TimeStamp;
	      }
	  }
	  break;

	case MSG_TAYLOR_CALL_COMPLETE:
	  UpdateProcess (pr);
	  break;

	case MSG_TAYLOR_CALLING_SYSTEM:
	  pr->Status = TUUS_CALLING;
	  pr->StartTime = lentry.TimeStamp;
	  pr->WhoIsPaying = LOCAL_SYSTEM;
	  sr->LastConnection = lentry.TimeStamp;
	  break;

	case MSG_TAYLOR_ERROR:
	  if (pr->Status != TUUS_NONE)
	    pr->Status = TUUS_ERROR;
	  break;

	case MSG_TAYLOR_RECEIVING:
	case MSG_TAYLOR_SENDING:
	  pr->Status = TUUS_SENDING;
	  break;
	  
	case I_DONT_KNOW:
#ifdef DEBUG
	  debug_printf ("Warning: unknown command '%s'", command);
#endif
	  break;
	}

     /*
       * TO DO: COMPLETE THE MESSAGE LIST
       * ================================
       *
       * Through a short analysis of various uucico logs, I
       * think I taked care of all the foundamental ones, but I
       * cannot be sure, because I couldn't find a complete list of
       * them.
       */
    }

  xfree (lentry.Command);
  
  if (StatStartingTime > first_line_timestamp || StatStartingTime == 0.0)
    StatStartingTime = first_line_timestamp;

  if (StatEndingTime < last_timestamp)
    StatEndingTime = last_timestamp;
}

int
DEFUN_VOID (read_uucico_log)
{
  DIR *dirfp;
  struct dirent *direntry;
  char dirname[LPNMAX];
  int ret_value = OK;

  sprintf (dirname, "%s/%s", logs_prefix_path_opt, UUCICO_DIR);

  if ((dirfp = opendir (dirname)) == (DIR *) NULL)
    {
      LOG("cannot read %s", dirname);
      ret_value = ERROR;
    }
  else
    {
#ifdef DEBUG
      if (be_verbose)
	fputs ("\nProcessing uucico logs:", stderr);
#endif
      
      while ((direntry = readdir (dirfp)) != NULL)
	{
	  FILE *CicoFp;
	  char PathName[LPNMAX];
	  char System[15];

	  if (*direntry->d_name == '.')
	    continue;

	  sprintf (System, "%.*s", (int) NLENGTH (direntry), direntry->d_name);

	  if (just_some_system_opt && (search_system (System) == (system_rec_t *) NULL))
	    continue;

	  sprintf (PathName, "%s/%s/%s", logs_prefix_path_opt, UUCICO_DIR, System);
	  if ((CicoFp = fopen (PathName, "r")) == (FILE *) NULL)
	    {
	      LOG ("cannot open %s", PathName);
	      ret_value = ERROR;
	      break;
	    }
	  debug_filename (PathName);
	  ProcUucicoEntry (CicoFp, System);
	  enquire_processes ((traverse_func_t) UpdateProcess);
	  debug_end();
	  
	  fclose (CicoFp);
	}

    }

  if (dirfp)
    closedir (dirfp);

  return ret_value;
}

#endif /* HDB_UUCP */
