/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * ShapeTools Version Control System
 *
 * display.c - output procedures for "vl" command
 *
 * Author: Andreas Lampen, TU-Berlin (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: display.c[9.0] Tue Jan 25 17:43:02 1994 andy@cs.tu-berlin.de frozen $
 */

#include <pwd.h>
#include <grp.h>

#include "atfs.h"
#include "atfstk.h"
#include "sttk.h"

extern int expandAttrFlag;
extern int fastFlag;
extern int fileClassFlag;
extern int fullUser;
extern int groupFlag;
extern int lockerFlag;
extern int logFlag;
extern int longStatusFlag;
extern int ownerFlag;
extern int showAllAttrsFlag;
extern char *showAttrs[];
extern int showHiddenAttrsFlag;
extern int sortLinesFlag;

extern char examineDir[];

static int lineLen, userLen, longStatus = FALSE;

EXPORT void initDisplay ()
{
  int maxUserLen;

  lineLen = stGetTermWidth (1);
  if (fullUser)
    maxUserLen = 24;
  else if (groupFlag)
    maxUserLen = 18;
  else
    maxUserLen = 10;

  userLen = lineLen - 59;
  if (userLen > maxUserLen)
    userLen = maxUserLen;
  else if (userLen < 8)
    userLen = 8;
  if (lineLen > 100)
    longStatus = TRUE;
}

/*==================================
 *  local general output routines
 *==================================*/

LOCAL void addGroup (userStr, gid)
     char *userStr;
     int  gid;
{
  struct passwd *pwent;
  struct group *group = NULL;
  char *atPtr = NULL, *grPtr;

  if (gid >= 0)
    group = getgrgid (gid);
  else {
    if (fullUser) {
      if ((atPtr = strchr (userStr, '@')))
	*atPtr = '\0';
    }
    else {
      if ((atPtr = strchr (userStr, ' ')))
	*atPtr = '\0';
    }
      
    if ((pwent = getpwnam (userStr)) != NULL)
      group = getgrgid (pwent->pw_gid);
  }

  if (group) {
    int groupNameLen = strlen (group->gr_name), maxLen;
    if (userLen >= 20)
      maxLen = 8;
    else if (userLen <= 12)
      maxLen = 4;
    else
      maxLen = 6;
    grPtr = &userStr[userLen-(maxLen+1)];
    *grPtr++ = ' ';
    strncpy (grPtr, "        ", maxLen);
    strncpy (grPtr, group->gr_name, (groupNameLen > maxLen) ? maxLen : groupNameLen);
    if (atPtr && (atPtr < grPtr-1)) {
      if (fullUser)
	*atPtr = '@';
      else
	*atPtr = ' ';
    }
  }
}


/*=====================
 * ASO output routines
 *=====================*/

EXPORT char *asoName (aso)
     Af_key *aso;
{
  static char name[NAME_MAX+1];

  if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
    strcpy (name, af_retattr (aso, AF_ATTUNIXNAME));
  else if (af_retnumattr (aso, AF_ATTSTATE) == AF_DERIVED) {
    strcpy (name, af_retattr (aso, AF_ATTBOUND));
    strcpy (&name[strlen(name)-1], "-drvd]");
  }
  else
    strcpy (name, af_retattr (aso, AF_ATTBOUND));

  if (fileClassFlag)
    strcat (name, atFileClassExt (aso));

  return (name);
}

EXPORT void displayAsoAttrs (aso, attrBuf, indent)
     Af_key *aso;
     Af_attrs *attrBuf;
     char *indent;
{
  int i=0, j, found;
  char *attrName, *attrValue;
  Af_attrs myAttrBuf;

  if (!attrBuf) {
    af_allattrs (aso, &myAttrBuf);
    attrBuf = &myAttrBuf;
  }

  while (attrBuf->af_udattrs[i]) {
    if ((attrName = atAttrName (attrBuf->af_udattrs[i])) == NULL)
      attrName = "__xXxXxX__";
    if (!showAllAttrsFlag) {
      /* check if attribute is in list of attributes to be shown */
      j = 0;
      found = FALSE;
      while (showAttrs[j]) {
	if (!strcmp (attrName, showAttrs[j])) {
	  found = TRUE;
	  break;
	}
	j++;
      }
      if (!found) {
	i++;
	continue;
      }
    }
    /* skip hidden attribute unless showHiddenAttrsFlag is set */
    if ((attrName[0] == '_') && (attrName[1] == '_') && !showHiddenAttrsFlag) {
      i++;
      continue;
    }
    /* ToDo: implement proper attribute output format */
    /* expand attribute if necessary */
    if (expandAttrFlag) {
      attrValue = atRetAttr (aso, attrName);
      printf ("%s%s=%s\n", indent, attrName, attrValue ? attrValue : "");
    }
    else
      printf ("%s%s\n", indent, attrBuf->af_udattrs[i]);
    i++;
  }
    
  if (logFlag || showAllAttrsFlag) {
    if (expandAttrFlag)
      attrValue = atRetAttr (aso, "note");
    else
      attrValue = af_rnote (aso);
    printf ("%s%s=%s\n", indent, AT_ATTLOG, attrValue ? attrValue : "");
  }
}

EXPORT void displayAsoShort (aso, path)
     Af_key *aso;
     char   *path;
{
  if (path && *path) {
    fputs (path, stdout);
    fputc ('/', stdout);
  }
  fputs (asoName (aso), stdout);
  fputc ('\n', stdout);

  if (showAllAttrsFlag || showAttrs[0] || logFlag)
    displayAsoAttrs (aso, NULL, "  ");
}

EXPORT void displayAsoLong (aso, path)
     Af_key *aso;
     char   *path;
{
  Af_user *userPtr;
  char userStr[USER_MAX+1];
  int  i;

  /* write mode -- 10 chars plus 1 char space */
  fputs (atWriteMode (aso), stdout);
  fputc (' ', stdout);

  /* write status -- 1(short) or 6(long) chars plus 1 char space */
  if (longStatusFlag)
    longStatus = TRUE;
  fputs (atWriteStatus (aso, longStatus), stdout);
  fputc (' ', stdout);

  /* write user -- default 20 chars plus 1 char space */
  if (lockerFlag)
    userPtr = af_retuserattr (aso, AF_ATTLOCKER);
  else if (ownerFlag)
    userPtr = af_retuserattr (aso, AF_ATTOWNER);
  else 
    userPtr = af_retuserattr (aso, AF_ATTAUTHOR);
  if (userPtr) {
    strncpy (userStr, userPtr->af_username, userLen);
    if (fullUser) {
      int len = strlen (userStr);
      if (len < userLen) {
	strcat (userStr, "@");
	strncat (userStr, userPtr->af_userdomain, userLen-(len+1));
      }
    }
    i = strlen (userStr);
  }
  else
    i = 0;
  /* fill up with blanks */
  for (; i<userLen; i++)
    userStr[i] = ' ';
  userStr[userLen] = '\0';

  /* add group name if necessary */
  if (groupFlag) {
    /* if the displayed ASO has a corresponding UNIX file,
       get group id from inode */
    if (lockerFlag)
      addGroup (userStr, -1);
    else if (ownerFlag)
      addGroup (userStr, aso->af_ldes->af_owngid);
    else {
      struct stat iBuf;
      if ((af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY) &&
	  (stat (af_retattr (aso, AF_ATTUNIXPATH), &iBuf) != ERROR))
	addGroup (userStr, iBuf.st_gid);
      else
	addGroup (userStr, -1);
    }
  }

  fputs (userStr, stdout);
  fputc (' ', stdout);

  /* write size (right adjusted) -- max 8 digits */
  fprintf (stdout, "%8d ", af_retnumattr (aso, AF_ATTSIZE));

  /* write date -- narrowed to 12 chars */
  if (lockerFlag)
    fputs (atWriteDate (aso, AF_ATTLTIME), stdout);
  else {
    if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
      fputs (atWriteDate (aso, AF_ATTMTIME), stdout);
    else
      fputs (atWriteDate (aso, AF_ATTSTIME), stdout);
  }
  fputc (' ', stdout);

  /* write name */
  if (path && *path) {
    fputs (path, stdout);
    fputc ('/', stdout);
  }
  fputs (asoName (aso), stdout);
  fputc ('\n', stdout);
  if (showAllAttrsFlag || showAttrs[0] || logFlag)
    displayAsoAttrs (aso, NULL, "  ");
}

EXPORT void displayAsoFormat (aso, formatString)
     Af_key *aso;
     char   *formatString;
{
  /* ToDo: handle expandAttrFlag 
   * Difficult, because it should do expansion exactly once.
   * This needs AtFStk suppport !
   * When atExpand is just set FALSE, the unmodified format string
   * is written to the output.
   */
  atExpandAttrs (aso, formatString, strlen (formatString), stdout, 0, AT_EXPAND_FILE);
}

EXPORT void displayAsoAll (aso)
     Af_key *aso;
{
  int  i, nameLen;
  char *titleName;
  Af_attrs attrBuf;

  af_allattrs (aso, &attrBuf);

  titleName = asoName (aso);
  nameLen = strlen (titleName);
  printf ("********%s", titleName);
  for (i = nameLen+4; i<=40; i++)
    fputc ('*', stdout);
  fputc ('\n', stdout);

  if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
    printf ("  Name:   %s:%s\n", attrBuf.af_host, af_retattr (aso, AF_ATTUNIXPATH));
  else
    printf ("  Name:   %s:%s\n", attrBuf.af_host, af_retattr (aso, AF_ATTBOUNDPATH));

  printf ("  Status: %s\n", atWriteStatus (aso, TRUE));
  
  printf ("  Owner:  %-24.24s", af_retattr (aso, AF_ATTOWNER));
  printf ("  Last accessed: %s\n", af_retattr (aso, AF_ATTATIME));
  
  printf ("  Author: %-24.24s", af_retattr (aso, AF_ATTAUTHOR));
  printf ("  Last changed:  %s\n", af_retattr (aso, AF_ATTCTIME));
  
  if (attrBuf.af_locker.af_username[0])
    printf ("  Locker: %-24.24s", af_retattr (aso, AF_ATTLOCKER));
  else
    printf ("  Locker: %-24.24s", "<not locked>");
  if (attrBuf.af_ltime != AF_NOTIME)
    printf ("  (Un)Locked at: %s\n", af_retattr (aso, AF_ATTLTIME));
  else
    printf ("\n");

  printf ("  Size:   %-24d", af_retnumattr (aso, AF_ATTSIZE));
  printf ("  Last modified: %s\n", af_retattr (aso, AF_ATTMTIME));
  
  printf ("  Mode:   %-24.24s", atWriteMode (aso));
  if (attrBuf.af_stime != AF_NOTIME)
    printf ("  Saved at:      %s\n", af_retattr (aso, AF_ATTSTIME));
  else
    printf ("\n");

  printf ("  User defined attributes:\n");
  displayAsoAttrs (aso, &attrBuf, "  ");
  af_freeattrbuf (&attrBuf);
  printf ("\n");
}

/*==========================
 * History output routines
 *==========================*/

int internalCall = FALSE; /* this flag tells histAddName, if global
			     variables are already initialized */

Af_key firstAso, lastAso, busyAso;
int firstExists = FALSE, lastExists = FALSE, busyExists = FALSE, cachedExist = FALSE;

LOCAL void fillGlobals (hist)
     char *hist;
{
  char name[NAME_MAX+1], type[TYPE_MAX];

  strcpy (name, af_afname (hist));
  strcpy (type, af_aftype (hist));

  if (af_getkey (examineDir, name, type, AF_FIRSTVERS, AF_FIRSTVERS, &firstAso) != -1)
    firstExists = TRUE;

  if (af_getkey (examineDir, name, type, AF_LASTVERS, AF_LASTVERS, &lastAso) != -1)
    lastExists = TRUE;

  if (af_getkey (examineDir, name, type, AF_BUSYVERS, AF_BUSYVERS, &busyAso) != -1)
    busyExists = TRUE;

  if (af_access (examineDir, name, type, AF_CLASS_DERIVED) != -1)
    cachedExist = TRUE;
}

LOCAL void destroyGlobals ()
{
  af_dropkey (&firstAso);
  af_dropkey (&lastAso);
  af_dropkey (&busyAso);
  firstExists = lastExists = busyExists = cachedExist = FALSE;
}

EXPORT char *histAddName (hist, longName)
     char *hist;
     int  longName; /* 1 - inclusive "busy" and "cached", 0 - exclusive */
{
  static char histVinfo[64];

  if (fastFlag) {
    histVinfo[0] = '\0';
    return (histVinfo);
  }

  if (!internalCall)
    fillGlobals (hist);

  histVinfo[0] = '[';
  histVinfo[1] = '\0';

  if (firstExists) {
    strcat (histVinfo, af_retattr (&firstAso, AF_ATTVERSION));

    if ((lastExists) && (lastAso.af_lpos != firstAso.af_lpos)) {
      strcat (histVinfo, "-");
      strcat (histVinfo, af_retattr (&lastAso, AF_ATTVERSION));
    }

    if (cachedExist & longName)
      strcat (histVinfo, ",cached");
    if (busyExists & longName)
      strcat (histVinfo, ",busy");
    strcat (histVinfo, "]");
  }
  else if (cachedExist) {
    histVinfo[0] = '\0';
    if (busyExists & longName)
      strcpy (histVinfo, "[cached,busy]");
    else
      strcpy (histVinfo, "[]");
  }
  else if (busyExists) {
    histVinfo[0] = '\0';
  }
  else {
    char *namePtr = strrchr (hist, '/');
    if (!namePtr)
      namePtr = hist;
    if (strcmp (namePtr, ".") && strcmp (namePtr, ".."))
      strcpy (histVinfo, "[]");
    else
      histVinfo[0] = '\0';
  }

  if (fileClassFlag) {
    if (busyExists) {
      strcat (histVinfo, atFileClassExt (&busyAso));
    }
    else if (lastExists) {
      strcat (histVinfo, atFileClassExt (&lastAso));
    }
    else if (cachedExist) {
      strcat (histVinfo, "$");
    }
  }

  if (!internalCall)
    destroyGlobals ();
  return (histVinfo);
}

EXPORT void displayHistShort (hist, path)
     char *hist, *path;
{
  if (!fastFlag)
    fillGlobals (hist);
  if (path && *path) {
    fputs (path, stdout);
    fputc ('/', stdout);
  }
  fputs (hist, stdout);
  internalCall = TRUE;
  fputs (histAddName (hist, 1), stdout);
  internalCall = FALSE;
  fputc ('\n', stdout);
  if (!fastFlag)
    destroyGlobals ();
}

EXPORT void displayHistLong (hist, path)
     char *hist, *path;
{
  Af_user *userPtr;
  char userStr[USER_MAX+1], lockerStr[USER_MAX+1];
  int  i, cached=0, saved=0, isHist = FALSE;
  uid_t ownGid = -1;

  fillGlobals (hist);

  /* determine number of saved and cached versions */
  if (lastExists) {
    afAccessAso (&lastAso, AF_ATTRS);
    saved = lastAso.af_ldes->af_nrevs;
  }
  else if (busyExists) {
    saved = 1;
  }

  if (cachedExist) {
    Af_attrs attrBuf;
    Af_set resultSet;
    af_initattrs (&attrBuf);
    strcpy (attrBuf.af_name, af_afname(hist));
    strcpy (attrBuf.af_type, af_aftype(hist));

    cached = af_cachefind (&attrBuf, &resultSet);
    af_dropset (&resultSet);
    if (cached <= 0) {
      /* huh ?, something went wrong */
      cached = 0;
    }
  }

  /* write type */
  if (busyExists) {
    mode_t busyMode = af_retnumattr (&busyAso, AF_ATTMODE);

    if (S_ISDIR(busyMode))
      fputc ('d', stdout);
    else if (S_ISCHR(busyMode))
      fputc ('c', stdout);
    else if (S_ISBLK(busyMode))
      fputc ('b', stdout);
    else if (S_ISREG(busyMode)) {
      if (cachedExist && (saved == 1))
	fputc ('o', stdout);
      else if (cachedExist && (saved > 1))
	fputc ('x', stdout);
      else {
	fputc ('h', stdout);
	isHist = TRUE;
      }
    }
#ifdef S_ISLNK
    else if (S_ISLNK(busyMode))
      fputc ('l', stdout);
#endif
#ifdef S_ISSOCK
    else if (S_ISSOCK(busyMode))
      fputc ('s', stdout);
#endif
  }
  else {
    if (cachedExist && (saved == 0))
      fputc ('o', stdout);
    else if (cachedExist && (saved > 0))
      fputc ('x', stdout);
    else {
      fputc ('h', stdout);
      isHist = TRUE;
    }
  }

  lockerStr[0] = '\0';
  /* write locked generations */
  if (isHist && lastExists) {
    int gen=0, gen1=0, gen2=0;
    Af_key tmpAso;
    Af_user *tmpUser;
    char genStr[16];

    for (i=af_retnumattr (&lastAso, AF_ATTGEN); i>=1; i--) {
      if (af_getkey (NULL, af_afname(hist), af_aftype(hist), i, AF_LASTVERS, &tmpAso) != -1) {
	tmpUser = af_testlock (&tmpAso);
	if (atUserValid (tmpUser)) {
	  if (gen == 0)
	    gen = i;
	  else if (gen1 == 0)
	    gen1 = i;
	  else if (gen2 == 0)
	    gen2 = i;
	  else {
	    af_dropkey (&tmpAso);
	    break;
	  }
	  if (!lockerStr[0]) {
	    strncpy (lockerStr, tmpUser->af_username, userLen);
	    if (fullUser) {
	      int len = strlen (lockerStr);
	      if (len < userLen) {
		strcat (lockerStr, "@");
		strncat (lockerStr, tmpUser->af_userdomain, userLen -(len+1));
	      }
	    }
	  }
	}
	af_dropkey (&tmpAso);
      }
    }

    if (gen) {
      if (gen <= 9) {
	if (gen2)
	  sprintf (genStr, "^%d..", gen);
	else if (gen1 > 9)
	  sprintf (genStr, "^%d..", gen);
	else if (gen1)
	  sprintf (genStr, "^%d,%d", gen, gen1);
	else
	  sprintf (genStr, "^%d  ", gen);
      }
      else {
	if (gen1 || gen2)
	  sprintf (genStr, "^%d.", gen);
	else
	  sprintf (genStr, "^%d ", gen);
      }
      fputs (genStr, stdout);
    }
    else 
      fputs ("    ", stdout);
  }
  else
    fputs ("    ", stdout);

  /* indicate busy version */
  if (busyExists)
    fputs (" b ", stdout);
  else
    fputs ("   ", stdout);


  /* write owner or locker -- default 20 chars plus 1 char space */
  userPtr = NULL;
  if (firstExists) {
    userPtr = af_retuserattr (&firstAso, AF_ATTOWNER);
    ownGid = firstAso.af_ldes->af_owngid;
  }
  else if (lastExists) {
    userPtr = af_retuserattr (&lastAso, AF_ATTOWNER);
    ownGid = lastAso.af_ldes->af_owngid;
  }
  else if (busyExists) {
    userPtr = af_retuserattr (&busyAso, AF_ATTOWNER);
    ownGid = busyAso.af_ldes->af_owngid;
  }

  if (lockerFlag && lockerStr[0]) {
    strncpy (userStr, lockerStr, userLen);
    i = strlen (userStr);
  }
  else if (!lockerFlag && userPtr) {
    strncpy (userStr, userPtr->af_username, userLen);
    if (fullUser) {
      int len = strlen (userStr);
      if (len < userLen) {
	strcat (userStr, "@");
	strncat (userStr, userPtr->af_userdomain, userLen-(len+1));
      }
    }
    i = strlen (userStr);
  }
  else
    i = 0;
  /* fill up with blanks */
  for (; i<userLen; i++)
    userStr[i] = ' ';
  userStr[userLen] = '\0';

  /* add group name if necessary */
  if (groupFlag) {
    if (lockerFlag && lockerStr[0])
      addGroup (userStr, -1);
    else if (!lockerFlag && userPtr)
      addGroup (userStr, ownGid);
  }

  fputs (userStr, stdout);
  fputc (' ', stdout);

  /* write number of versions (right adjusted) -- max 6 digits */
  if (cached > 9) {
    if (saved)
      fprintf (stdout, "%2d+%2dc ", saved, cached);
    else
      fprintf (stdout, "%5dc ", cached);
    }
  else if (cached)
    fprintf (stdout, "%3d+%dc ", saved, cached);
  else
    fprintf (stdout, "%6d ", saved);
    
  /* write date -- narrowed to 12 chars */
  if (busyExists)
    fputs (atWriteDate (&busyAso, AF_ATTMTIME), stdout);
  else if (lastExists)
    fputs (atWriteDate (&lastAso, AF_ATTSTIME), stdout);
  else
    fputs ("            ", stdout);
  fputc (' ', stdout);

  /* write name */
  if (path && *path) {
    fputs (path, stdout);
    fputc ('/', stdout);
  }
  fputs (hist, stdout);
  internalCall = TRUE;
  fputs (histAddName (hist, 0), stdout);
  internalCall = FALSE;
  fputc ('\n', stdout);
  destroyGlobals ();
}

EXPORT void displayHistAll (hist)
     char *hist;
{
  static int done = FALSE;

  if (!done) {
    stLog ("Sorry, '-all' Option for histories (-h) not implemented.", ST_LOG_MSGERR);
    done = TRUE;
  }
  /* ToDo: displayHistAll */
}

EXPORT void displayHistFormat (hist, formatString)
     char *hist;
     char *formatString;
{
  static int done = FALSE;

  if (!done) {
    stLog ("Sorry, '-format' Option for histories (-h) not implemented.", ST_LOG_MSGERR);
    done = TRUE;
  }
  /* ToDo: displayHistFormat */
}


/*-----------------------------------------------
 * columns
 *-----------------------------------------------*/

EXPORT void displayColumns (nameList, nameCount, maxLen)
     char **nameList;
     int  nameCount; /* number of entries in name list */
     int  maxLen; /* length of longest string in nameList */
{
  int colWidth = maxLen + 2, columns, nameLen, spaces, newLine, i, j;

  /* calculate number of columns to be displayed */
  if (!(columns = lineLen / colWidth))
    columns++;
  if (columns > nameCount)
    columns = nameCount;

  newLine = FALSE;
  if (sortLinesFlag) {
    for (i=0; i<nameCount; i++) {
      fputs (nameList[i], stdout);
      nameLen = strlen (nameList[i]);
      free (nameList[i]);

      if ((i % columns == columns-1) || ( i == nameCount-1)) {
	fputc ('\n', stdout);
	newLine = FALSE;
	continue;
      }
      newLine = TRUE;
      spaces = colWidth - strlen (nameList[i]);
      while (spaces--)
	fputc (' ', stdout);
    }
  }
  else {
    int  lines = (nameCount / columns), nameIdx;

    if (nameCount % columns)
      lines++;

    for (i=0; i<lines; i++) {
      for (j=0; j<columns; j++) {
	if ((nameIdx = (j*lines)+i) < nameCount) {
	  fputs (nameList[nameIdx], stdout);
	  nameLen = strlen(nameList[nameIdx]);
	  free(nameList[nameIdx]);
	}
	else
	  nameLen = colWidth;

	if (j < columns-1) {
	  newLine = TRUE;
	  spaces = colWidth - nameLen;
	  while (spaces--)
	    fputc (' ', stdout);
	}
	else {
	  newLine = FALSE;
	  fputc ('\n', stdout);
	}
      }
    }
  }

  if (newLine)
    fputc ('\n', stdout);
  free((char *) nameList);
}

