/* 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.
*/
/*
 * read in rlog output for rcs2atfs
 *
 * Author: Juergen Nickelsen (Juergen.Nickelsen@cs.tu-berlin.de)
 *
 * $Header: readin.c[3.0] Tue Jun 29 16:38:11 1993 andy@cs.tu-berlin.de frozen $
 */

#include "rcs2atfs.h"
#include "functions.h"
#include "sttk.h"

/* read rlogin output into a struct RCSFILE */

struct RCSFILE *readin_rcsfile(fname)
char *fname ;
{
    char   *rlog_command ;	/* command to be executed in the pipe */
    FILE   *rlog_pipe ;		/* pipe for rlog command */
    char   input_line[LINELENGTH + 1] ;	/* line for reading from pipe */
    bool   file_separator_seen ; /* rlog output completely read? */
    read_state_t read_state ;	/* state of reader */
    struct RCSFILE *rcsfile ;	/* pointer to struct being filled */
    bool   use_old_line ;	/* don't read a new line, use old one */
    struct SYMNAME *symname_list ; /* lst of symbolic names */

    /* open pipe to rlog command */

    rlog_command = addstr(RLOG_COMMAND, fname) ;
    if ((rlog_pipe = popen(rlog_command, "r")) == NULL) {
	fatal(POPEN_RLOG, rlog_command) ;
    }
    free(rlog_command) ;
    rlog_command = NULL ;
    
    /* initialize variables for the reader */

    symname_list = NULL ;	/* no symbolic names yet */
    rcsfile = new_rcsfile() ;	/* malloc and initialize */
    file_separator_seen = false ; /* end of rlog output not yet seen */
    read_state = header ;	/* reader is in the header */
    use_old_line = false ;	/* read a new line */


    /* while loop terminates, when file separator has been seen
     * or no more input is available. If we have to use the line
     * previously read, we don't read a new one.
     */

    while (! file_separator_seen &&
	   (use_old_line || fgets(input_line, LINELENGTH, rlog_pipe))) {

	/* if a new line has been read, we remove a trailing newline
	 * character. If there is none, we skip the rest of the line.
	 */
	if (! use_old_line) {
	    /* remove newline */
	    char *newline = strchr(input_line, '\n') ;
	    if (newline != NULL) {
		*newline = '\0' ;
	    } else {
		/* or throw away the rest of the line */
		input_line[LINELENGTH] = '\0' ;
		while (fgetc(rlog_pipe) != '\n') ;
	    }
	} else {
	    /* must be false for the next run */
	    use_old_line = false ;
	}
	
	/*  */
	switch (read_state) {

	    /* reader is in header of rlog output */
	  case header:

	    /* checking for one-line fields */

	    if (!strncmp(input_line, K_rcsfile, strlen(K_rcsfile))) {
		rcsfile->rcs_file =
		    check_strdup(input_line + strlen(K_rcsfile) + 1) ;
	    } else if (!strncmp(input_line, K_wrkfile, strlen(K_wrkfile))) {
		char *wfile = check_strdup(input_line + strlen(K_wrkfile) + 1);
		/* Make sure NO path is left in rcsfile->working_file.
		 * That's better than sometimes and sometimes not and I
		 * don't know when.
		 */
		rcsfile->working_file = basename(wfile) ;
		free(wfile) ;
	    } else if (!strncmp(input_line, K_comlead, strlen(K_comlead))) {
		char *tmp = check_strdup(input_line + strlen(K_comlead) + 1) ;
		
		*strrchr(tmp, '\"') = '\0' ;
		rcsfile->comment_leader = sh_quote(tmp) ;
		free(tmp) ;
	    } else

	    /* fields with more than one line require a status change. */

	    if (!strncmp(input_line, K_descr, strlen(K_descr))) {
		/* read description */
		read_state = description ;
	    } else if (!strncmp(input_line, K_locks, strlen(K_locks))) {
		read_state = locks ;
		/* grumble about locks */
	    } else if (!strncmp(input_line, K_alist, strlen(K_alist))) {
		/* ignore access list */
		read_state = skip ;
	    } else if (!strncmp(input_line, K_symnames, strlen(K_symnames))) {
		/* read symbolic names */
		read_state = symnames ;
	    }
	    break ;

	  case skip:
	    /* skip over lines beginning with whitespace */

	    if (! isspace(input_line[0])) {
		/* switch back to "header" if line does not begin with
		   whitespace and keep line in buffer.
		 */
		read_state = header ;
		use_old_line = true ;
	    }
	    break ;

	  case locks:
	    /* look at locks */
	    
	    /* if line doesn't begin with whitespace, it is another field */
	    if (! isspace(input_line[0])) {
		read_state = header ;
		use_old_line = true ;
		break ;
	    }

	    rcsfile->locks = true ;
	    warning(LOCKED_VERSIONS, rcsfile->rcs_file) ;
	    break ;

	  case symnames:
	    /* read symbolic names */
	{
	    char *nbegin = input_line, *colon ;
	    struct SYMNAME *symname ;
	    int length ;

	    /* if line doesn't begin with whitespace, it is another field */
	    if (! isspace(input_line[0])) {
		read_state = header ;
		use_old_line = true ;
		break ;
	    }

	    /* extract symbolic name and revision number */
	    while (isspace(*++nbegin)) ;
	    colon = strchr(nbegin, ':') ;
	    length = colon - nbegin ;
	    if (colon == NULL) {
		error(NO_COLON,
			rcsfile->rcs_file) ;
		free_rcsfile(rcsfile) ;
		pclose(rlog_pipe) ;
		return NULL ;
	    }
	    symname = new_symname() ;
	    symname->symname = check_malloc(length + 1) ;
	    strncpy0(symname->symname, nbegin, length) ;
	    symname->revision = check_strdup(colon + 2) ;
	    symname->next = symname_list ;
	    symname_list = symname ;
	}
	    break ;

	  case description:
	    /* read description of RCS file */

	    /* if line is separator, it doesn't belong to the description */
	    if (! strcmp(input_line, K_revsep)) {
		read_state = revision1 ;
		break ;
	    }

	    /* allocate space for next line of description */
	    if (rcsfile->description == NULL) {
		rcsfile->description =
		    check_calloc(strlen(input_line) + 2, 1) ;
		/* needs to be zeroed out initially */
	    } else {
		rcsfile->description =
		    check_realloc(rcsfile->description,
				  strlen(rcsfile->description) +
				  strlen(input_line) + 2) ;
	    }

	    /* copy contents of line */
	    strcat(rcsfile->description, input_line) ;
	    strcat(rcsfile->description, "\n") ;
	    break ;

	  case revision1:
	    /* first line of a revision */

	{
	    char *scan, *scan2 ;
	    struct RCSREV *rev ;

	    /* consistency check */
	    if (strncmp(input_line, K_revision, strlen(K_revision))) {
		error(NO_REVISION,
			rcsfile->rcs_file) ;
		free_rcsfile(rcsfile) ;
		pclose(rlog_pipe) ;
		return NULL ;
	    }

	    /* copy contents into allocated RCSREV struct, insert
	     * struct into list of revisions.
	     */
	    rev = new_rcsrev() ;
	    scan = input_line + strlen(K_revision) ;
	    scan2 = scan ; while (*scan2 != '\0' && !isspace(*++scan2)) ;
	    rev->number = check_malloc(scan2 - scan + 2) ;
	    rev->number[0] = 'r' ;
	    strncpy0(rev->number + 1, scan, scan2 - scan) ;
	    rev->next = rcsfile->revisions ;
	    rcsfile->revisions = rev ;
	    rcsfile->no_of_revs++ ;
	    read_state = revision2 ;
	}
	    break ;

	  case revision2:
	    /* second line of revision */
	{
	    char *scan, *scan2, *datestring ;
	    struct RCSREV *rev = rcsfile->revisions ;
	    
	    /* consistency check */
	    if (strncmp(input_line, K_date, strlen(K_date))) {
		error(NO_DATE,
			rcsfile->rcs_file) ;
		free_rcsfile(rcsfile) ;
		pclose(rlog_pipe) ;
		return NULL ;
	    }

	    /* extract and convert date */
	    scan = strchr(input_line, ';') ;
	    scan2 = input_line + strlen(K_date) ;
	    datestring = strndup(scan2, scan - scan2) ;
	    rev->date = stMktime(datestring) ;
	    free(datestring) ;

	    while (isspace(*++scan)) ;

	    /* consistency check */
	    if (strncmp(scan, K_author, strlen(K_author))) {
		error(NO_AUTHOR,
			rcsfile->rcs_file) ;
		free_rcsfile(rcsfile) ;
		pclose(rlog_pipe) ;
		return NULL ;
	    }

	    /* extract author field */
	    scan += strlen(K_author) ;
	    scan2 = strchr(scan, ';') ;
	    rev->author = check_malloc(scan2 - scan + 1) ;
	    strncpy0(rev->author, scan, scan2 - scan) ;
	    scan = scan2 + 1 ;
	    while (isspace(*++scan)) ;

	    /* consistency check */
	    if (strncmp(scan, K_state, strlen(K_state))) {
		error(NO_STATUS,
			rcsfile->rcs_file) ;
		free_rcsfile(rcsfile) ;
		pclose(rlog_pipe) ;
		return NULL ;
	    }

	    /* extract state */
	    scan +=strlen(K_state) ;
	    scan2 = strchr(scan, ';') ;
	    rev->state = check_malloc(scan2 - scan + 1) ;
	    strncpy0(rev->state, scan, scan2 - scan) ;

	    read_state = log_msg ;
	}
	    break ;

	  case log_msg:
	    /* log message of revision */

	{
	    struct RCSREV *rev = rcsfile->revisions ;
	    char *quoted_line ;	/* input line after quoting \ and $ */
	    
	    /* if line is revision separator or file separator,
	     * log message is over */
	    if (!strcmp(input_line, K_revsep)) {
		read_state = revision1 ;
		break ;
	    }
	    if (!strcmp(input_line, K_filesep)) {
		read_state = header ;
		file_separator_seen = true ;
		break ;
	    }

	    /* perhaps it is a "branches" line */
	    if (!strncmp(input_line, K_branches, strlen(K_branches))) {
		if (! rcsfile->branches) {
				/* we haven't warned yet */
		    warning(BRANCHES, rcsfile->rcs_file) ;
		}
		rcsfile->branches = true ;
		break ;
	    }

	    /* quote \ and $ in input_line */
	    quoted_line = sh_quote(input_line) ;

	    /* allocate space for log message */
	    if (rev->log_message == NULL) {
		/* I use calloc(3) bcause allocated space
		 * must have a '\0' at the beginning for strcat(3) */
		rev->log_message = check_calloc(strlen(quoted_line) + 2, 1) ;
	    } else {
		rev->log_message = check_realloc(rev->log_message,
						 strlen(rev->log_message) +
						 strlen(quoted_line) + 2) ;
	    }

	    /* copy current line of log message */
	    strcat(rev->log_message, quoted_line) ;
	    strcat(rev->log_message, "\n") ;
	}
	    break ;

	  default:
	    /* this must never happen */
	    fatal(MIND_LOST, MUSTNOT) ;
	}
    }

    if (pclose(rlog_pipe) != 0) {
	error(RLOG_FAILED, fname) ;
	free_rcsfile(rcsfile) ;
	return NULL ;
    }	

    if (! check_rcsfile(rcsfile)) {
	error(CHECK_FAILED, fname) ;
	free_rcsfile(rcsfile) ;
	return NULL ;
    }

    sort_revisions(rcsfile) ;
    apply_symnames(rcsfile, symname_list) ;

    return rcsfile ;
}

/* The name of the following two functions should have been
 * "compare_revisions_by_number" and "compare_revisions_by_date", but
 * I guess there still some compilers that treat only the first some
 * (10? 8? 6?) characters of an identifier as significant. (WHY?!) 
 * These two are needed for qsort()ing an array of revision pointers.
 */
int number_compare_revisions(r1, r2)
struct RCSREV **r1, **r2 ;
{
    int r1maj, r1min, r2maj, r2min ; /* major and minor revision numbers
				      * of revisions r1 and r2 */

    if (sscanf((*r1)->number, "r%d.%d", &r1maj, &r1min) != 2 ||
	sscanf((*r2)->number, "r%d.%d", &r2maj, &r2min) != 2) {
	fatal(REV_CORRUPT, MUSTNOT) ;
    }

    if (r1maj < r2maj) {
	return -1 ;
    } else if (r1maj > r2maj) {
	return 1 ;
    } else {			/* so the majors must be equal */
	if (r1min < r2min) {
	    return -1 ;
	} else if (r1min > r2min) {
	    return 1 ;
	} else {		/* the minors, too */
	    return 0 ;
	}
    }
}

int date_compare_revisions(r1, r2)
struct RCSREV **r1, **r2 ;
{
    if ((*r1)->date < (*r2)->date) {
	return -1 ;
    } else if ((*r1)->date > (*r2)->date) {
	return 1 ;
    } else {
	return 0 ;
    }
}

/* sort revisions by revision number, if branches exist by date */
void sort_revisions(rcsfile)
struct RCSFILE *rcsfile ;
{
    struct RCSREV **pointers ;	/* to store a pointer to every r. in */
    struct RCSREV *rev ;	/* pointer to a revision */
    int (*compare)() ;		/* for qsort() */
    int i ;			/* counter and index into pointers[] */

    /* copy pointers to revisions into array */
    pointers = (struct RCSREV **)
	check_malloc(sizeof(struct RCSREV *) * rcsfile->no_of_revs) ;
    for (rev = rcsfile->revisions, i = 0; rev != NULL; rev = rev->next, i++) {
	if (i == rcsfile->no_of_revs) {
	    fatal(BAD_REVNO, MUSTNOT) ;
	}
	pointers[i] = rev ;
    }

    /* sort array */
    compare = rcsfile->branches ? date_compare_revisions :
				  number_compare_revisions ;
    qsort(pointers, i, sizeof(struct RCSREV *), compare) ;

    /* and hang to rcsfile structure */
    rcsfile->revisions = NULL ;
    while (--i > -1) {
	pointers[i]->next = rcsfile->revisions ;
	rcsfile->revisions = pointers[i] ;
    }
    free(pointers) ;
}

/* check if all needed fields in rcsfile are present */
bool check_rcsfile(rcsfile)
struct RCSFILE *rcsfile ;
{
    struct RCSREV *rev ;
    int a ;

    if (rcsfile->working_file == NULL ||
	rcsfile->comment_leader == NULL ||
	rcsfile->revisions == NULL) {

	return false ;
    }

    for (rev = rcsfile->revisions; rev != NULL; rev = rev->next) {
	if (rev->number == NULL ||
	    sscanf(rev->number, "r%d.%d", &a, &a) != 2 ||
	    rev->author == NULL) {
	    return false ;
	}
    }

    return true ;
}

/* apply symbolic names to apropriate revisions */
void apply_symnames(rcsfile, symname_list)
struct RCSFILE *rcsfile ;
struct SYMNAME *symname_list ;
{
    struct RCSREV *rev ;
    struct SYMNAME *sym ;

    /* Yes, I know it is an O(n^2) algorithm, but I don't think
     * it is worth more effort.
     */

    while (symname_list != NULL) {
 	for (rev = rcsfile->revisions; rev != NULL; rev = rev->next) {
	    if (!strcmp(rev->number + 1, symname_list->revision)) {
		sym = symname_list ;
		symname_list = symname_list->next ;
		sym->next = rev->symbolic_names ;
		rev->symbolic_names = sym ;
		break ;		/* save a few nanoseconds */
	    }
	}
	if (rev == NULL) {	/* for-loop came to an end */
	    warning(BOGUS_SYMNAME,
		    symname_list->symname) ;
	    symname_list = symname_list->next ;
	}
    }
}

/*EOF*/
