/* Functions for manipulating a PR.
   Copyright (C) 1993 Free Software Foundation, Inc.
   Contributed by Tim Wicinski (wicinski@barn.com).

This file is part of GNU GNATS.

GNU GNATS 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, or (at your option)
any later version.

GNU GNATS 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 GNU GNATS; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "config.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif

#include "globals.h"
#include "gnats.h"
#include "pr.h"

PR_entry pr[NUM_PR_ITEMS];

static PR_Name field_name		PARAMS((char *));

/* Read the file pointed to by FP, and look for matching field names, trying
   to attach values to those names.  */

void 
read_pr (fp, prune)
     FILE *fp;
     int prune;
{
  char token[STR_MAX];
  char line[STR_MAX];
  int i, oldi = -1;
  char *l;
  bool multi_text = FALSE;
  char *buffer = (char*) xmalloc (BUFSIZ);
  char *unformatted = (char*) xmalloc (BUFSIZ);
  char *b, *u, *p;
  int buffer_size = BUFSIZ, unformatted_size = BUFSIZ;
  int unformatted_len = 0, buffer_len = 0;
  int len;

  if (fp == (FILE *) NULL)
    return;

  memset (token, 0, STR_MAX);
  memset (line, 0, STR_MAX);

  b = buffer; u = unformatted;
  while ((len = read_string (line, fp)) != -1)
    {
      i = -1;

      if (len > 1)
	{
	  *token = '\0';

	  l = get_token (line, token);
	  if (token[0] == PR_START_FIELD && token[1])
	    i = (int) field_name (token);
	}

      /* Heuristic for finding included PR fields */
      if (i >= 0 && multi_text && (i == oldi || pr[i].value != NULL))
	i = -1;

      /* If we can't find the name and we are not in multi_text line mode, 
         then it goes into UNFORMATTED */
      if (i != -1)
	{
	  /* prune should be used when only reading w/no hint 
	     of writing PR's back out (ie, query-pr).  */
	  if (prune && pr[i].datatype == MultiText && i != ORGANIZATION)
	    break;

	  if (pr[i].datatype == MultiText)
	    {
	      multi_text = TRUE;
	      len = strlen (l);
	      if (oldi != -1)
		{
		  buffer[buffer_len++] = '\0';
		  pr[oldi].value = (char *) xmalloc (buffer_len);
		  memcpy (pr[oldi].value, buffer, buffer_len);
		}
	      b = buffer;
	      memcpy (b, l, len);
	      buffer_len = len;
	      b += len;
	      oldi = i;
	    }
          else 
            {
	      SKIP_WHITE_SPACE (l);

	      /* Fields that aren't MultiText shouldn't have a newline
		 at the end, since the field's value is important.  */
	      if (line[len - 1] == '\n')
		line[--len] = '\0';

	      if (is_space[ (unsigned char) line[len - 1] ])
		for (p = &line[0] + len - 1; p != &line[0]; p--)
		  {
		    if (is_space[(unsigned char) *p])
		      *p = '\0';
		    else
		      break;
		  }

	      pr[i].value = (char *) strdup (l);
	      multi_text = FALSE;
            }
	}
      else if (multi_text) 
        {
	  if ((len + buffer_len) >= buffer_size)
	    {
	      buffer_size += BUFSIZ;
	      buffer = (char *) xrealloc (buffer, buffer_size);
	      b = buffer + buffer_len;
	    }
	  memcpy (b, line, len);
	  buffer_len += len;
	  b += len;
        }
      else
	{
	  /* It must be unformatted.  This is done separately since an
	     unformatted field can show up anywhere.  */
	  multi_text = FALSE;

	  if ((len + unformatted_len) >= unformatted_size)
	    {
	      unformatted_size += BUFSIZ;
	      unformatted = (char *) xrealloc (unformatted,
					       unformatted_size);
	      u = unformatted + unformatted_len;
	    }

	  memcpy (u, line, len);
	  unformatted_len += len;
	  u += len;
	}
    }

  /* we don't really care about these buffers with prune set.  */
  if (prune)
    return;

  /* OLDI could've never changed if we didn't read anything from the file.  */
  /*   BUT - it could be an entirely hosed PR too... */
  if (unformatted_len == 0 && oldi == -1)
    return;
     
  /* Quick check to see if the last case was dealt with.  It usually isn't,
     especially since the final cases are normally MultiText.  */
  if (oldi != -1 && pr[oldi].value == NULL) 
    {
      buffer[buffer_len] = '\0';
      pr[oldi].value = buffer;      
    }

  /* Check to see if the unformatted field was used.  */
  if (unformatted_len != 0)
    {
      unformatted[unformatted_len] = '\0';
      if (pr[UNFORMATTED].value)
	{
	  len = strlen (pr[UNFORMATTED].value);
	  if ((len + unformatted_len + 1) > unformatted_size)
	    {
	      unformatted_size = len + unformatted_len + 1;
	      unformatted = (char *) xrealloc (unformatted,
					       unformatted_size);
	      u = unformatted + unformatted_len;
	    }

	  strcat (u, pr[UNFORMATTED].value);
	  free (pr[UNFORMATTED].value);
	}

      pr[UNFORMATTED].value = unformatted;
    }
}

/* Write either a single PR field, or the entire PR into the file passed
   in via FP.  */
void 
write_pr (fp, string)
     FILE *fp;
     PR_Name string;
{
  register int i;

  if (string != NUM_PR_ITEMS)
    {
      char *fmt = "%-16s %s\n";
      if (pr[string].datatype == MultiText)
	fmt = "%s\n%s\n";
      if (pr[string].value == NULL)
	fprintf (fp, "%s\n", pr[string].name);
      else
	fprintf (fp, fmt, pr[string].name, pr[string].value);
    }
  else
    {
      for (i = 0; i < NUM_PR_ITEMS; i++)
	if (pr[i].value != NULL)
	  {
	    /* For multi-text lines, always send a newline after the
		field name, and don't emit one after the value, since
		it will have the newline we need.  */
	    if (pr[i].datatype == MultiText)
	      fprintf (fp, "%s\n%s", pr[i].name, pr[i].value);
	    else
	      fprintf (fp, "%-16s %s\n", pr[i].name, pr[i].value);
	  }
        else
  	  fprintf (fp, "%s\n", pr[i].name);
    }
}

/* Init all the fields in the PR.  */
void 
init_pr ()
{
  PR_Name i;

  for (i = NUMBER; i < NUM_PR_ITEMS; i++)
    xfree (pr[i].value);
  
  memset (&pr[0], 0, sizeof (PR_entry) * NUM_PR_ITEMS);

  pr[ORIGINATOR].name = ORIGINATOR_STRING;
  pr[ORIGINATOR].datatype = Text;

  pr[ORGANIZATION].name = ORGANIZATION_STRING;
  pr[ORGANIZATION].datatype = MultiText;

  pr[SUBMITTER].name = SUBMITTER_STRING;
  pr[SUBMITTER].datatype = Text;
  pr[SUBMITTER].default_value = def_subm;

  pr[ARRIVAL_DATE].name = ARRIVAL_DATE_STRING;
  pr[ARRIVAL_DATE].default_value = '\0';
  pr[ARRIVAL_DATE].datatype = Text;

  pr[SYNOPSIS].name = SYNOPSIS_STRING;
  pr[SYNOPSIS].datatype = Text;

  pr[CONFIDENTIAL].name = CONFIDENTIAL_STRING;
  pr[CONFIDENTIAL].enum_values = "no | yes";
  pr[CONFIDENTIAL].default_value = "yes";
  pr[CONFIDENTIAL].datatype = Enum;

  pr[SEVERITY].name = SEVERITY_STRING;
  pr[SEVERITY].enum_values = "non-critical | serious | critical";
  pr[SEVERITY].default_value = "serious";
  pr[SEVERITY].datatype = Enum;

  pr[PRIORITY].name = PRIORITY_STRING;
  pr[PRIORITY].enum_values = "low | medium | high";
  pr[PRIORITY].default_value = "medium";
  pr[PRIORITY].datatype = Enum;

  pr[CATEGORY].name = CATEGORY_STRING;
  pr[CATEGORY].default_value = PENDING;
  pr[CATEGORY].datatype = Text;	/* really enum, matching category file */

  pr[RELEASE].name = RELEASE_STRING;
  pr[RELEASE].datatype = Text;

  pr[ENVIRONMENT].name = ENVIRONMENT_STRING;
  pr[ENVIRONMENT].datatype = MultiText;

  pr[DESCRIPTION].name = DESCRIPTION_STRING;
  pr[DESCRIPTION].datatype = MultiText;

  pr[HOW_TO_REPEAT].name = HOW_TO_REPEAT_STRING;
  pr[HOW_TO_REPEAT].datatype = MultiText;

  pr[NUMBER].name = NUMBER_STRING;
  pr[NUMBER].datatype = Integral;
  pr[NUMBER].default_value = "-1";

  pr[RESPONSIBLE].name = RESPONSIBLE_STRING;
  pr[RESPONSIBLE].datatype = Text;

  pr[AUDIT_TRAIL].name = AUDIT_TRAIL_STRING;
  pr[AUDIT_TRAIL].datatype = MultiText;
  pr[AUDIT_TRAIL].default_value = "\nState-Changed-From-To:\n\
State-Changed-By:\n\
State-Changed-When:\n\
State-Changed-Why:";

  pr[STATE].name = STATE_STRING;
  pr[STATE].enum_values = "open | analyzed | feedback | suspended | closed";
  pr[STATE].default_value = "open";
  pr[STATE].datatype = Enum;

  pr[CLASS].name = CLASS_STRING;
  pr[CLASS].enum_values
    = "support | sw-bug | doc-bug | change-request | mistaken | duplicate ";
  pr[CLASS].default_value = "sw-bug";
  pr[CLASS].datatype = Enum;

  pr[FIX].name = FIX_STRING;
  pr[FIX].datatype = MultiText;
  pr[FIX].default_value = "\nUnknown";

  pr[UNFORMATTED].name = UNFORMATTED_STRING;
  pr[UNFORMATTED].datatype = MultiText;
}

/* Check all the enumerated typed fields that are declared, and check
   to make sure they're all good values.  Reset any null or invalid
   entries.  */
struct bad_enum *
check_enum_types (check)
     int check;
{
  int i = -1;
  char *p;
  bool found = FALSE;
  char *token = (char *) xmalloc (1024); /* FIXME */
  struct bad_enum *bad_enums = NULL, *bad_enums_end = NULL;

  while (++i < (int) NUM_PR_ITEMS)
    {
      found = FALSE;
      if (pr[i].datatype != (PR_Type) 0 && pr[i].datatype == Enum)
	{
	  if (pr[i].value == NULL || *pr[i].value == '\0')
	    {
	      if (pr[i].default_value != NULL)
		pr[i].value = pr[i].default_value;
	      else
		break;
	    }

	  p = pr[i].enum_values;
	  while (*p != '\0')
	    {
	      char *t;
	      int j;
	      p = get_next_field (p, token, '|');
	      t = strchr (pr[i].value, ' ');

	      if (t)
		j = strncasecmp (pr[i].value, token, t - pr[i].value);
	      else
		j = strcasecmp (pr[i].value, token);

	      if (j == 0)
		{
		  found = TRUE;
		  break;
		}
	    }

	  if (found == FALSE)
	    {
	      if (check)
		{
		  char *msg = (char *) xmalloc (100 + strlen (pr[i].value)
						+ strlen (pr[i].name)
						+ strlen (pr[i].default_value));
		  if (bad_enums == NULL)
		    bad_enums = bad_enums_end
		    = (struct bad_enum *) xmalloc (sizeof (struct bad_enum));
		  else
		    {
		      bad_enums_end->next
			= (struct bad_enum *) xmalloc (sizeof (struct bad_enum));
		      bad_enums_end = bad_enums_end->next;
		    }

		  if (check == 1)
		    sprintf (msg,
			     "\tNote: There was a bad value `%s' for the field `%s'.\n\tIt was set to the default value of `%s'.\n",
			     pr[i].value, pr[i].name, pr[i].default_value);
		  else if (check == 2)
		    sprintf (msg, "%s %s\n", pr[i].name, pr[i].value);

		  bad_enums_end->msg = msg;
		  bad_enums_end->next = (struct bad_enum *) NULL;
		}
	      pr[i].value = pr[i].default_value;
	    }
	}
    }

  return bad_enums;
}

/* Look to see if STRING is a known GNATS string.  */
static PR_Name 
field_name (string)
     char *string;
{
  PR_Name i;
  int len;

  if (*string == '\0')
    return (PR_Name) -1;

  len = strlen(string);

  for (i = (PR_Name) 0; i < NUM_PR_ITEMS; i++)
    if ((pr[i].name != NULL && string != NULL) &&
	(strncmp (pr[i].name, string, len) == 0))
      return i;

  /* We check this, for backwards compatability with the old
     way of doing things (referring to them as customers, instead
     of submitters).  */
  if (strncmp (ALTERNATE_SUBMITTER, string, len) == 0)
    return SUBMITTER;

  return (PR_Name) -1;
}

/* These two routines are used for external procedures. This way, other files
   don't need to see the pr structure.  */
char *
field_value (name)
     PR_Name name;
{
  if (name < (PR_Name) 0 || name >= NUM_PR_ITEMS)
    return NULL;

  if (pr[name].value == NULL)
    {
      if (pr[name].default_value != NULL)
	pr[name].value = strdup (pr[name].default_value);
      else
	pr[name].value = strdup ("");
    }

  return pr[name].value;
}

/* Set the value of the PR entry NAME to STRING.  Returns TRUE if the
   operation was successful, FALSE if not (e.g., if STRING was a bad
   value for an enumerated field).  */
bool 
set_field (name, string)
     PR_Name name;
     char *string;
{
  char *p;
  char *token;
  bool found = FALSE;

  if (name < (PR_Name) 0 || name >= NUM_PR_ITEMS || string == NULL)
    return found;

  if (pr[name].datatype != Enum)
    {
      pr[name].value = (char *) strdup (string);
      found = TRUE;
    }
  else
    {
      token = (char *) xmalloc(STR_MAX);
      p = pr[name].enum_values;

      while (*p != '\0')
	{
	  p = get_next_field (p, token, '|');
	  if (strcmp (string, token) == 0)
	    {
	      found = TRUE;
	      break;
	    }
	}

      if (found)
	{
	  xfree (pr[name].value);
	  pr[name].value = (char *) strdup (string);
	}
     }

  return found;
}
