/* Miscellaneous tools common to all types of queries.
   Copyright (C) 1994, 1995 Free Software Foundation, Inc.
   Contributed by Brendan Kehoe (brendan@cygnus.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, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */

#include "config.h"
#include "gnats.h"
#include "query.h"

/* How we should print out the format of the PR.  */
unsigned int query_format = FORMAT_REG;

/* What list we're interested in seeing.  */
unsigned int list_format = 0;

/* Whether or not to search the text fields, and the regexp to
   search for.  */
char *text_search = NULL;

/* Whether or not to search the date fields, and the regexp to
   search for.  */
char *date_search = NULL;
 
/* Whether or not to search the start-date field, and the regexp to
   search for.  */
char *startdate_search = NULL;
 
/* Whether or not to search the end-date field, and the regexp to
   search for.  */
char *enddate_search = NULL;

/* Whether or not to search the multitext fields, and the regexp to
   search for.  */
char *m_text_search = NULL;

/* Whether or not we're searching more than one (or every) PR.  */
int searching = 0;

/* If 1, don't talk about closed PRs.  */
/* FIXME: This doesn't make sense when we have configurable state names.  */
int skip_closed = 0;

/* If 1, print a string like "/gnats/GNATS/g++/145:0:" for the
   emacs next-error function.  */
int print_path = 0;

/* If 1, don't emit any errors from get_pr.  */
int quiet = 0;

/* Look for PRs before this date.  */
time_t before = (time_t)0;

/* Look for PRs after this date.  */
time_t after = (time_t)0;

/* If specified, search for the originator of this PR.  */
char *originator = NULL;

/* If specified, search for the class of this PR.  */
char *class = NULL;

#ifdef GNATS_RELEASE_BASED
/* If specified, search for the quarter of this PR.  */
char *quarter = NULL;

/* If specified, search for the keywords of this PR.  */
char *keywords = NULL;
#endif

/* Where the results of query-pr should go.  */
FILE *outfile = stdout;

char *
disbar (str)
     char *str;
{
  char *q;
  
  while ((q = strchr (str, '|')))
    *q = '!';

  return str;
}

int
sql_types (p, type)
     char *p;
     Sql_Types type;
{
  switch (type)
    {
    case Severity:
      if (tolower(*p) == 'c')
	return 1;
      else if (tolower(*p) == 's')
	return 2;
      else if (tolower(*p) == 'n')
	return 3;
      break;
    case Priority:
      if (tolower(*p) == 'h')
	return 1;
      else if (tolower(*p) == 'm')
	return 2;
      else if (tolower(*p) == 'l')
	return 3;
      break;
    case State:
      if (tolower(*p) == 'o')
	return 1;
      else if (tolower(*p) == 'a')
	return 2;
      else if (tolower(*p) == 's')
	return 3;
      else if (tolower(*p) == 'f')
	return 4;
      else if (tolower(*p) == 'c')
	return 5;
      break;
    case Class:
      if (tolower(p[1]) == 'w') /* sw-bug */
	return 1;
      else if (tolower(p[1]) == 'c') /* scheduled */
        return 8;
      else if (tolower(p[1]) == 'o') /* doc-bug */
	return 2;
      else if (tolower(*p) == 's') /* support */
	return 3;
      else if (tolower(*p) == 'c') /* change-request */
	return 4;
      else if (tolower(*p) == 'm') /* mistaken */
	return 5;
      else if (tolower(*p) == 'd') /* duplicate */
	return 6;
      else if (tolower(*p) == 'u') /* unscheduled */
        return 7;
      break;
    }

  return 0;
}

char *
sql_time (s)
     char *s;
{
  time_t t;
  struct tm *ar_time;
  char *buf = (char *) xmalloc (16);

  t = get_date (s, NULL);
  ar_time = (struct tm *) localtime (&t);
  strftime (buf, 16, "%y-%m-%d %H:%M", ar_time);

  return buf;
}

char *
make_path (c, n)
     char *c, *n;
{
  char *path = (char *) xmalloc (PATH_MAX);

  sprintf (path, "%s/%s/%s", gnats_root, c, n);
  return path;
}

int
numeric (p)
     char *p;
{
  int i;
  int l = strlen(p);
  for (i=0; i<l; i++)
    {
      if (!isdigit (p[i]))
	return 0;
    }
  return 1;
}

static char case_fold[256];

/* Return 0 if matched, 1 otherwise. */
int
regcmp (pat, match)
     char *pat, *match;
{
  struct re_pattern_buffer buf;
  union {
    const char *c;
    int i;
  } r;

  memset ((void *) &buf, 0, sizeof (buf));

  if (case_fold[1] == 0)
    {
      int i;
      for (i=0; i<256; i++)
	case_fold[i] = tolower(i);
    }
  buf.translate = case_fold;
#ifdef USE_RX
  buf.syntax_parens = (char *)1;
#endif
  
  r.c = re_compile_pattern (pat, strlen (pat), &buf);
  if (r.c)
    {
      fprintf (stderr, "%s: re_compile_pattern: %s\n", program_name, r.c);
      exit (2);
    }
  r.i = re_match (&buf, match, strlen (match), 0, 0);
  buf.translate = NULL;
  regfree (&buf);
  switch (r.i)
    {
    case -2:
      fprintf (stderr, "%s: warning: re_match died on %s\n",
	       program_name, pr[NUMBER].value);
      /*FALLTHRU*/
    case -1:
      return 1;
    default:
      return 0;
    }
}
      
/* Return 0 if found, 1 otherwise. */
int
regfind (pat, match)
     char *pat, *match;
{
  struct re_pattern_buffer buf;
  union {
    const char *c;
    int i;
  } r;

  memset ((void *) &buf, 0, sizeof (buf));

  if (case_fold[1] == 0)
    {
      int i;
      for (i=0; i<256; i++)
	case_fold[i] = tolower(i);
    }
  buf.translate = case_fold;
  buf.fastmap = xmalloc (256);
#ifdef USE_RX
  buf.syntax_parens = (char *)1;
#endif
  
  r.c = re_compile_pattern (pat, strlen (pat), &buf);
  if (r.c)
    {
      fprintf (stderr, "%s: re_compile_pattern: %s\n", program_name, r.c);
      exit (2);
    }
  r.i = strlen (match);
  r.i = re_search (&buf, match, r.i, 0, r.i, 0);
  buf.translate = NULL;
  regfree (&buf);
  switch (r.i)
    {
    case -2:
      fprintf (stderr, "%s: warning: re_search died on %s\n",
	       program_name, pr[NUMBER].value);
      /*FALLTHRU*/
    case -1:
      return 1;
    default:
      return 0;
    }
}

char *
get_category (p)
     char *p;
{
  FILE *fp;
  char *path, *category = NULL;

  if (index_chain)
    {
      Index *j;
      for (j = index_chain ; j ; j = j->next)
	if (strcmp (p, j->number) == 0)
	  {
	    category = j->category;
	    break;
	  }
    }
  else
    {
      fp = open_index ();
      if (fp == (FILE *)NULL)
	return NULL;
      category = find_pr_category (fp, p);
      close_index (fp);
    }

  if (category == NULL)
    return NULL;

  path = (char *) xmalloc (PATH_MAX);
  sprintf (path, "%s/%s/%s", gnats_root, category, p);

  return path;
}

int
date_compare (tst, sdate, type)
     time_t tst;
     char *sdate;
     int type;
{
  time_t val = atol (sdate);

  /* 0 means the date is unset, so nothing should match it */
  if (! val)
    return 1;

  if (type == 0)
    return val > tst; /* ! val < tst */
  else if (type == 1)
    return val < tst; /* ! val > tst */
  else
    abort (); /* Should never happen.  */
}

int
pr_matches (s, i)
     Index *s;
     Index *i;
{
  if (skip_closed && strcasecmp (i->state, "closed") == 0) return 0;
  if (!s || !searching) return 1;
  return (!s->category || (regcmp (s->category, i->category) == 0))
    && (!s->submitter || (regcmp (s->submitter, i->submitter) == 0))
    && (!s->responsible || (regcmp (s->responsible, i->responsible) == 0))
    && (!s->state || (regcmp (s->state, i->state) == 0))
    && (!s->confidential || (regcmp (s->confidential, i->confidential) == 0))
#ifdef GNATS_RELEASE_BASED
    && (!before || (date_compare (before, i->date_required, 0) == 0))
    && (!after || (date_compare (after, i->date_required, 1) == 0))
#endif
    && (!s->severity || (regcmp (s->severity, i->severity) == 0))
    && (!s->priority || (regcmp (s->priority, i->priority) == 0));
}


int
check_date ()
{
  PR_Name i;
 
  if (date_search)
    {
      for (i = NUMBER; i < NUM_PR_ITEMS; i++)
        {
          if (pr[i].datatype != Date)
            continue;
          
          if (pr[i].value && regfind (date_search, pr[i].value) == 0)
            return 1;
        }
      return 0;
    }
}
 
 
int
check_startdate ()
{
  PR_Name i;
 
  if (startdate_search)
    {
      for (i = NUMBER; i < NUM_PR_ITEMS; i++)
        {
          if (pr[i].datatype != StartDate)
            continue;
          
          if (pr[i].value && regfind (startdate_search, pr[i].value) == 0)
            return 1;
        }
      return 0;
    }
}
 
 
int
check_enddate ()
{
  PR_Name i;
 
  if (enddate_search)
    {
      for (i = NUMBER; i < NUM_PR_ITEMS; i++)
        {
          if (pr[i].datatype != EndDate)
            continue;
          
          if (pr[i].value && regfind (enddate_search, pr[i].value) == 0)
            return 1;
        }
      return 0;
    }
}


int
check_text ()
{
  PR_Name i;

  if (text_search)
    {
      for (i = NUMBER; i < NUM_PR_ITEMS; i++)
	{
	  if (pr[i].datatype != Text)
	    continue;
	  
	  if (pr[i].value && regfind (text_search, pr[i].value) == 0)
	    goto part2;
	}
      return 0;
    }
 part2:
  if (m_text_search)
    {
      for (i = NUMBER; i < NUM_PR_ITEMS; i++)
	{
	  if (pr[i].datatype != MultiText)
	    continue;
	  
	  if (pr[i].value && regfind (m_text_search, pr[i].value) == 0)
	    return 1;
	}
      return 0;
    }
  return 1;
}

/* Get the PR at PATH.  If EXISTP is non-nil, only say if it existed
   or not.  If it's nil, read in the PR.  If SILENT is non-nil, we
   don't emit an error message.  */
int
get_pr (path, p, silent)
     char *path, *p;
     int silent;
{
  FILE *fp = fopen (path, "r");
  if (fp == (FILE *)NULL)
    {
      if (! silent)
	fprintf (stderr, "%s: couldn't read PR %s\n", program_name, p);
      return 0;
    }

  if (read_header (fp) < 0)
    {
      if (! silent)
	fprintf (stderr, "%s: error reading PR %s\n", program_name, p);
      return 0;
    }

  read_pr (fp, (query_format & ~FORMAT_FULL) && !m_text_search);

  fclose (fp);

  return 1;
}

/* Possibly print out a PR, checking if we want to look at unindexed things
   first.  */
int
do_pr_internal (path, prnum)
     char *path, *prnum;
{
  if (originator || startdate_search || enddate_search || date_search || text_search || m_text_search
#ifdef GNATS_RELEASE_BASED
      || keywords || quarter
#endif
      || class)
    {
      if (! get_pr (path, prnum, quiet))
	return 0;

      if ((originator && (pr[ORIGINATOR].value == NULL
			  || regfind (originator, pr[ORIGINATOR].value) != 0))
	  || (class && (pr[CLASS].value == NULL
			|| regfind (class, pr[CLASS].value) != 0))
#ifdef GNATS_RELEASE_BASED
	  || (quarter && (pr[QUARTER].value == NULL
			|| regfind (quarter, pr[QUARTER].value) != 0))
	  || (keywords && (pr[KEYWORDS].value == NULL
			|| regfind (keywords, pr[KEYWORDS].value) != 0))
#endif
	  || !check_text() || !check_date() || !check_startdate() || !check_enddate())
	return 0;
      return 1;
    }
  else
    return get_pr (path, prnum, quiet);
}

int
do_pr (i)
     Index *i;
{
  int val = 0;
  char *path = make_path (i->category, i->number);

  if ((val = do_pr_internal (path, i->number)))
    print_pr (path, i->number, 1);
  
  xfree (path);

  return val;
}

void
print_pr (path, p, opened)
     char *path, *p;
     int opened;
{
  /* Note: don't use P in any real way, cuz it could be "1234" or
     "category/1234".  */
  if (! opened && ! get_pr (path, p, quiet))
    return;

  start_data ();

  if (query_format & FORMAT_FULL)
    {
      write_header (outfile, NUM_HEADER_ITEMS);
      fprintf (outfile, "\n");
      write_pr (outfile, NUM_PR_ITEMS);
    }
  else if (query_format & FORMAT_SQL)
    {
      /* Trim `foo (Full Foo)' to just `foo'.  */
      char *t, *q = (char *) strchr (field_value (RESPONSIBLE), ' ');
      if (q != NULL)
	*q = '\0';

      fprintf (outfile, "%1.1d|%-8.8s|%-16.16s|%-128.128s|%-3.3s",
               sql_types (field_value (PRIORITY), Priority),
	       field_value (NUMBER), field_value (CATEGORY),
	       disbar(field_value (SYNOPSIS)),
	       field_value (CONFIDENTIAL));
      fprintf (outfile, "|%1.1d|%-16.16s|%1.1d|%1.1d|%-16.16s",
	       sql_types (field_value (SEVERITY), Severity),
       	       field_value (RESPONSIBLE),
	       sql_types (field_value (STATE), State),
	       sql_types (field_value (CLASS), Class),
	       field_value (SUBMITTER));
      t = sql_time (field_value (ARRIVAL_DATE));
      fprintf (outfile, "|%-16.16s|%-64.64s|%-64.64s|%-64.64s|%-64.64s", t,
	       disbar (field_value (ORIGINATOR)),
	       field_value(START_DATE),
               field_value(END_DATE),
	       field_value (RELEASE));
      xfree (t);
    }
  else if (query_format & FORMAT_SUMM)
    {
      char *s;

      s = (char *) strchr (pr[RESPONSIBLE].value, ' ');
      if (s)
	*s = '\0';
      fprintf (outfile,
	       "%8s %-8.8s %-8.8s %-9.9s %-9.9s %-8.8s %-10.10s %s",
	       pr[NUMBER].value, pr[RESPONSIBLE].value,
	       pr[CATEGORY].value, pr[STATE].value, pr[SEVERITY].value,
	       pr[PRIORITY].value, pr[SUBMITTER].value,
	       pr[SYNOPSIS].value);
    }
  else
    {
      /* Print this out for emacs when people do `M-x query-pr' and want
	 to be able to use the next-error function on the buffer.  */
      if (print_path)
	fprintf (outfile, "%s:0:\n", path);

      write_pr (outfile, NUMBER);
      write_pr (outfile, CATEGORY);
      write_pr (outfile, SYNOPSIS);
      write_pr (outfile, CONFIDENTIAL);
      write_pr (outfile, SEVERITY);
      write_pr (outfile, PRIORITY);
      write_pr (outfile, RESPONSIBLE);
      write_pr (outfile, STATE);
      write_pr (outfile, CLASS);
#ifdef GNATS_RELEASE_BASED
      write_pr (outfile, QUARTER);
      write_pr (outfile, KEYWORDS);
      write_pr (outfile, DATE_REQUIRED);
#endif
      write_pr (outfile, SUBMITTER);
      write_pr (outfile, ORIGINATOR);
      write_pr (outfile, START_DATE);
      write_pr (outfile, END_DATE);
      write_pr (outfile, RELEASE);
      write_pr (outfile, ARRIVAL_DATE);
      write_pr (outfile, LAST_MODIFIED);
    }
}
