/*
 * sf-prs.cc: Part of GNU CSSC.
 * 
 * 
 *    Copyright (C) 1997,1998,1999,2001 Free Software Foundation, Inc. 
 * 
 *    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, USA.
 * 
 * CSSC was originally Based on MySC, by Ross Ridge, which was 
 * placed in the Public Domain.
 *
 *
 * Members of the class sccs_file for printing selected parts of an
 * SCCS file.
 *
 */

#include "cssc.h"
#include "sccsfile.h"
#include "seqstate.h"
#include "delta.h"
#include "delta-iterator.h"
#include "linebuf.h"

#ifdef CONFIG_SCCS_IDS
static const char rcs_id[] = "CSSC $Id: sf-prs.cc,v 1.35 2002/04/05 23:18:08 james_youngman Exp $";
#endif

inline bool
sccs_file::get(FILE *out, mystring name, seq_no seq)
{
  sid_list no_includes, no_excludes;
  sccs_date no_cutoff;

  delta d;
  
  struct subst_parms parms(out, NULL, d, 0,
                           sccs_date());  // XXX: was sccs_date(NULL) (bad!)
  class seq_state state(highest_delta_seqno());
  
  if (prepare_seqstate(state, seq,
                       no_includes, no_excludes, no_cutoff))
  {
      return get(name, state, parms, true);
  }
  else 
  {
      return false;
  }
}

/* Prints a list of sequence numbers on the same line. */

static void
print_seq_list(FILE *out, mylist<seq_no> const &list) {
        int i;
        int len = list.length();

        /* prs does actually print the sequences in reverse order! */
        if (len > 0) {
            for(i = len-1; i >= 0; --i) {
                fprintf(out, "%u", list[i]);
                if (i > 0)
                    fprintf(out, " ");
            }
        }
}


/* Prints a list of strings, one per line. */

static void
print_string_list(FILE *out, mylist<mystring> const &list) {
        int i;
        int len = list.length();

        for(i = 0; i < len; i++) {
                fprintf(out, "%s\n", list[i].c_str());
        }
}


/* Prints a boolean flag with its name.   Simply, if the 
 * flag is unset, its name is not printed.
 */
static void
print_flag2(FILE *out, const char *s, int it)
{
  if (it)
    fprintf(out, "%s\n", s);
}


/* Prints a flag whose type has a print(FILE *) member with it's name. */

template <class TYPE>
void
print_flag2(FILE *out, const char *s, TYPE it) {
        if (it.valid()) {
                fprintf(out, "%s\t", s);
                it.print(out);
                putc('\n', out);
        }
}

// /* Prints a string flag with its name.
//  */
// static void
// print_flag2(FILE *out, const char *s, mystring it)
// {
//   if (!it.empty())
//     fprintf(out, "%s: %s\n", s, it.c_str());
// }


static inline void
print_flag2(FILE *out, const char *name, mystring *s)
{
  if (s)
    fprintf(out, "%s\t%s\n", name, s->c_str());
}

static inline void
print_flag2(FILE *out, const char *name, const char *s)
{
  if (s)
    fprintf(out, "%s\t%s\n", name, s);
}

static inline void
print_flag2(FILE *out, const char *name, char *s)
{
  if (s)
    fprintf(out, "%s\t%s\n", name, s);
}

/* Prints all the flags of an SCCS file. */


void
sccs_file::print_flags(FILE *out) const
{
  print_flag2(out, (const char *) "branch", flags.branch);
  print_flag2(out, (const char *) "ceiling", flags.ceiling);
  print_flag2(out, (const char *) "default SID", flags.default_sid);
  if (flags.encoded) fputs("encoded\n", out);
  print_flag2(out, (const char *) "floor", flags.floor);
  print_flag2(out, (const char *) "id keywd err/warn",
              flags.no_id_keywords_is_fatal);
  print_flag2(out, (const char *) "joint edit", flags.joint_edit);

  const char *locked = "locked releases";
  if (flags.all_locked)
    print_flag2(out, locked, "a");
  else
    print_flag2(out, locked, flags.locked);
  
  print_flag2(out, (const char *) "module",
              (flags.module ? flags.module->c_str()
               : (const char*)0) );
  print_flag2(out, (const char *) "null delta", flags.null_deltas);
  print_flag2(out, (const char *) "csect name", flags.user_def);
  print_flag2(out, (const char *) "type", flags.type);
  print_flag2(out, (const char *) "validate MRs",
              (flags.mr_checker ? flags.mr_checker->c_str()
               : (const char*) 0));
}


/* Prints "yes" or "no" according to the value of a boolean flag. */

inline static void
print_yesno(FILE *out, int flag) {
        if (flag) {
                fputs("yes", out);
        } else {
                fputs("no", out);
        }
}

/* Prints the value of a mystring flag. */
inline static void
print_flag(FILE *out, const mystring *s)
{
  if (s) 
    fputs("none", out);
  else
    fputs(s->c_str(), out);
}

/* Prints the value of a mystring flag. */
inline static void
print_flag(FILE *out, mystring *s)
{
  if (s) 
    fputs(s->c_str(), out);
  else
    fputs("none", out);
}

/* Prints the value of a mystring flag. */
inline static void
print_flag(FILE *out, const mystring &s)
{
  if (s.empty()) 
    fputs("none", out);
  else
    fputs(s.c_str(), out);
}


inline static void
print_flag(FILE *out, const release_list &it)
{
  if (it.valid()) 
    it.print(out);
  else
    fputs("none", out);
}

inline static void
print_flag(FILE *out, const release &it)
{
  if (it.valid()) 
    it.print(out);
  else
    fputs("none", out);
}

inline static void
print_flag(FILE *out, const sid &it)
{
  if (it.valid()) 
    it.print(out);
  else
    fputs("none", out);
}

// /* Prints the the value of string flag. */
// template <class TYPE>
// void
// print_flag(FILE *out, TYPE it)
// {
//   if (it.valid()) 
//     it.print(out);
//   else
//     fputs("none", out);
// }

/* These macros are used to convert the one or two characters a prs
   data keyword in an unsigned value used in the switch statement 
   in the function below. */

#define KEY1(c)         ((unsigned char)(c))
#define KEY2(c1, c2)    (((unsigned char)(c1)) * 256 + (unsigned char)(c2))

/* Prints selected parts of an SCCS file and the specified entry in the
   delta table. */

void
sccs_file::print_delta(FILE *out, const char *format,
                       struct delta const &d)
{
  const char *s = format;
  
  while (1)
    {
      char c = *s++;

      if (c == '\0')
        {
          break;        // end of format.
        }
      else if ('\\' == c)
        {
          if ('\0' != *s)
            {
              // Not at the end of the format string.
              // Backslash escape codes.  We only recognise \n and \t.
              switch (*s)
                {
                case 'n':
                  /* Turn a \n into a newline unless it is the last 
                   * bit of the format string.  In the latter case we 
                   * ignore it - see prs/format.sh test cases 4a and 4b.
                   * Those partiicular test cases were checked against 
                   * Sun Solaris 2.6.
                   */
                  if (s[1])
                    {
                      c = '\n';
                      break;
                    }
                  else
                    {
                      return;
                    }
                case 't': c = '\t'; break;
                case '\\': c = '\\'; break;
                default:        // not \n or \t -- print the whole thing.
                  putc('\\', out);
                  c = *s;
                  break;
                }
              putc(c, out);
              ++s;
            }
          else
            {
              putc('\\', out); // trailing backslash at and of format.
            }
          
          continue;
        }
      else if (c != ':' || s[0] == '\0')
        {
          putc(c, out);
          continue;
        }
      
      const char *back_to = s;
      unsigned key = 0;
      
      if (s[1] == ':')
        {
          key = KEY1(s[0]);
          s += 2;
        }
      else if (s[2] == ':')
        {
          key = KEY2(s[0], s[1]);
          s += 3;
        }
      else
        {
          putc(':', out);
          continue;
        }

      switch (key)
        {
        default:
          s = back_to;
          putc(':', out);
          continue;
                        
        case KEY2('D','t'):
          print_delta(out, ":DT: :I: :D: :T: :P: :DS: :DP:",
                      d);
          break;

        case KEY2('D','L'):
          print_delta(out, ":Li:/:Ld:/:Lu:", d);
          break;
          
        case KEY2('L','i'):
          fprintf(out, "%05lu", d.inserted);
          break;

        case KEY2('L','d'):
          fprintf(out, "%05lu", d.deleted);
          break;

        case KEY2('L','u'):
          fprintf(out, "%05lu", d.unchanged);
          break;

        case KEY2('D','T'):
          putc(d.type, out);
          break;

        case KEY1('I'):
          d.id.print(out);
          break;

        case KEY1('R'):
          d.id.printf(out, 'R');
          break;

        case KEY1('L'):
          d.id.printf(out, 'L');
          break;
          
        case KEY1('B'):
          d.id.printf(out, 'B');
          break;
          
        case KEY1('S'):
          d.id.printf(out, 'S');
          break;
          
        case KEY1('D'):
          d.date.printf(out, 'D');
          break;
          
        case KEY2('D','y'):
          d.date.printf(out, 'y');
          break;

        case KEY2('D','m'):
          d.date.printf(out, 'o');
          break;
          
        case KEY2('D','d'):
          d.date.printf(out, 'd');
          break;
          
        case KEY1('T'):
          d.date.printf(out, 'T');
          break;
          
        case KEY2('T','h'):
          d.date.printf(out, 'h');
          break;
          
        case KEY2('T','m'):
          d.date.printf(out, 'm');
          break;

        case KEY2('T','s'):
          d.date.printf(out, 's');
          break;

        case KEY1('P'):
          fputs(d.user.c_str(), out);
          break;

        case KEY2('D','S'):
          fprintf(out, "%u", d.seq);
          break;

        case KEY2('D','P'):
          fprintf(out, "%u", d.prev_seq);
          break;

        case KEY2('D', 'I'):
          if (d.included.length() > 0 ||
              d.excluded.length() > 0 ||
              d.ignored.length()  > 0   )
            {
              /* Testing witht he SOlaris version only shows one slash! */
              /* print_delta(out, ":Dn:/:Dx:/:Dg:", d); */
              print_delta(out, ":Dn:/:Dx:", d);
              break;
            }
                  
        case KEY2('D','n'):
          print_seq_list(out, d.included);
          break;

        case KEY2('D','x'):
          print_seq_list(out, d.excluded);
          break;

        case KEY2('D','g'):
          print_seq_list(out, d.ignored);
                        break;

        case KEY2('M','R'):
          print_string_list(out, d.mrs);
          break;

        case KEY1('C'):
          print_string_list(out, d.comments);
          break;

        case KEY2('U','N'):
	  if (users.length())
	    print_string_list(out, users);
	  else
	    fprintf(out, "%s\n", "none");
          break;

        case KEY2('F', 'L'):
          print_flags(out);
          break;
                        
        case KEY1('Y'):
          print_flag(out, flags.type);
          break;
                        
        case KEY2('M','F'):
          print_yesno(out, flags.mr_checker != 0);
          break;
          
        case KEY2('M','P'):
          print_flag(out, flags.mr_checker);
          break;
                        
        case KEY2('K','F'):
          print_yesno(out, flags.no_id_keywords_is_fatal);
          break;

        case KEY2('B','F'):
          print_yesno(out, flags.branch);
          break;

        case KEY1('J'):
          print_yesno(out, flags.joint_edit);
          break;
                        
        case KEY2('L','K'):
          if (flags.all_locked)
            {
              putc('a', out);
            }
          else
            {
              
              if (flags.locked.empty())
                fprintf(out, "none");
              else
                print_flag(out, flags.locked);
            }
          break;

        case KEY1('Q'):
          if (flags.user_def)
            print_flag(out, flags.user_def);
          break;

        case KEY1('M'):
          print_flag(out, get_module_name());
          break;
                        
        case KEY2('F','B'):
          print_flag(out, flags.floor);
          break;
                        
        case KEY2('C','B'):
          print_flag(out, flags.ceiling);
          break;

        case KEY2('D','s'):
          print_flag(out, flags.default_sid);
          break;

        case KEY2('N','D'):
          print_yesno(out, flags.null_deltas);
          break;

        case KEY2('F','D'):
          // The genuine article prints '(none)' if there
          // is no description.  
          // JY Sun Nov 25 01:33:46 2001; Solaris 2.6 
          // prints "none" rather than "(none)".
          if (0 == comments.length())
            fputs("none\n", out);
          else
            print_string_list(out, comments);
          break;

        case KEY2('B','D'):
          if (seek_to_body())
            {
              while (read_line() != -1)
                {
                  fputs(plinebuf->c_str(), out);
                  putc('\n', out);
                }
            }
          else
            {
              // TODO: what should we do if the seek fails?
              // do nothing.
            }
          break;

        case KEY2('G','B'):
          get(out, "-", d.seq); // TODO: check return value?
          break;

        case KEY1('W'):
          print_delta(out, ":Z::M:\t:I:", d);
          break;

        case KEY1('A'):
          print_delta(out, ":Z::Y: :M: :I::Z:", d);
          break;

        case KEY1('Z'):
          fputc('@', out);
          fputs("(#)", out);
          break;

        case KEY1('F'):
          fputs(base_part(name.sfile()).c_str(), out);
          break;

        case KEY2('P','N'):
          fputs(canonify_filename(name.c_str()).c_str(), out);
          break;
        }
    }
}


/* Prints out parts of the SCCS file.  */

bool
sccs_file::prs(FILE *out, mystring format, sid rid, sccs_date cutoff_date,
               enum when when, int all_deltas)
{
  if (!rid.valid())
    {
      rid = find_most_recent_sid(rid);
    }

  if (when != SIDONLY && !cutoff_date.valid())
    {
      const delta *pd = find_delta(rid);
      if (0 == pd)
        {
          errormsg("%s: Requested SID doesn't exist.", name.c_str());
          return false;
        }
      cutoff_date = pd->date;
    }

  const_delta_iterator iter(delta_table);
  while (iter.next(all_deltas))
    {
      switch (when)
        {
        case EARLIER:
          if (iter->date > cutoff_date)
            {
              continue;
            }
          break;

        case SIDONLY:
          if (rid != iter->id)
            {
              continue;
            }
          break;

        case LATER:
          if (iter->date < cutoff_date)
            {
              continue;
            }
          break;
        }

      print_delta(out, format.c_str(), *iter.operator->());
      putc('\n', out);
    }
  
  if (ferror(out))
    {
      errormsg("%s: Ouput file error.", name.c_str());
      return false;
    }
  return true;
}

// Explicit template instantiations.
template void print_flag2(FILE *out, const char *s, release);
template void print_flag2(FILE *out, const char *s, sid);
template void print_flag2(FILE *out, const char *s, release_list);

/* Local variables: */
/* mode: c++ */
/* End: */