/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

/*
 * Implementation of the gator circular buffer package for its scrollable
 * text object.
 *
 *------------------------------------------------------------------------*/

#include <afsconfig.h>
#include <afs/param.h>

#include <roken.h>

#include "gtxtextcb.h"		/*Module interface */

static int gator_textcb_debug;	/*Is debugging output turned on? */

/*------------------------------------------------------------------------
 * gator_textcb_Init
 *
 * Description:
 *	Initialize the text circular buffer package.
 *
 * Arguments:
 *	a_debug : Should debugging output be turned on?
 *
 * Returns:
 *	Zero if successful,
 *	Error value otherwise.
 *
 * Environment:
 *	MUST BE THE FIRST ROUTINE CALLED FROM THIS PACKAGE.
 *
 * Side Effects:
 *	Just remembers if debugging output should be generated.
 *------------------------------------------------------------------------*/

int
gator_textcb_Init(int a_debug)
{				/*gator_textcb_Init */

    static int initd;		/*Have we been called already? */
    static char rn[] = "gator_textcb_Init";	/*Routine name */

    if (initd) {
	fprintf(stderr,
		"[%s] Initialization routine called multiple times!!\n", rn);
	return (0);
    } else
	initd = 1;

    gator_textcb_debug = a_debug;
    return (0);

}				/*gator_textcb_Init */

/*------------------------------------------------------------------------
 * gator_textcb_Create
 *
 * Description:
 *	Create a new circular buffer.
 *
 * Arguments:
 *	int a_maxEntriesStored : How many entries should it have?
 *	int a_maxCharsPerEntry : Max chars in each entry.
 *
 * Returns:
 *	Ptr to the fully-initialized circular buffer hdr if successful,
 *	Null pointer otherwise.
 *
 * Environment:
 *	Makes sure the lock structure is properly initialized.
 *
 * Side Effects:
 *	As advertised; space is allocated for the circ buff.
 *------------------------------------------------------------------------*/

struct gator_textcb_hdr *
gator_textcb_Create(int a_maxEntriesStored, int a_maxCharsPerEntry)
{				/*gator_textcb_Create */

    static char rn[] = "gator_textcb_Create";	/*Routine name */
    char *newBuff;		/*Ptr to new text buffer */
    struct gator_textcb_entry *newEntries;	/*Ptr to new text entries */
    struct gator_textcb_hdr *newHdr;	/*Ptr to new text hdr */
    int bytesToAllocate;	/*Num bytes to allocate */
    int numBuffBytes;		/*Num bytes in text buffer */
    int curr_ent_num;		/*Current entry number */
    struct gator_textcb_entry *curr_ent;	/*Ptr to current entry */
    char *curr_buff;		/*Ptr to current buff pos */
    int curr_inv;		/*Current inversion idx */
    char *blankLine;		/*Ptr to blank line */
    int i;			/*Loop variable */

    /*
     * Start off by allocating the text buffer itself.  Don't forget we
     * need to allocate one more character per line, to make sure we can
     * always null-terminate them.  We also need to allocate the blank
     * line buffer.
     */
    numBuffBytes = bytesToAllocate =
	a_maxEntriesStored * (a_maxCharsPerEntry + 1);
    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Allocating %d bytes for the text buffer\n", rn,
		bytesToAllocate);
    newBuff = (char *)malloc(bytesToAllocate);
    if (newBuff == NULL) {
	fprintf(stderr,
		"[%s] Can't allocate %d bytes for text buffer; errno is %d\n",
		rn, bytesToAllocate, errno);
	return ((struct gator_textcb_hdr *)0);
    } else if (gator_textcb_debug)
	fprintf(stderr, "[%s] Text buffer allocated at %p\n", rn, newBuff);
    blankLine = (char *)malloc(a_maxCharsPerEntry + 1);
    if (blankLine == NULL) {
	fprintf(stderr,
		"[%s] Can't allocate %d bytes for blank line buffer; errno is %d\n",
		rn, a_maxCharsPerEntry + 1, errno);
	free(newBuff);
	return ((struct gator_textcb_hdr *)0);
    }

    /*
     * Next, allocate the entry array.
     */
    bytesToAllocate = a_maxEntriesStored * sizeof(struct gator_textcb_entry);
    if (gator_textcb_debug)
	fprintf(stderr,
		"[%s] Allocating %d bytes for the %d text entry array items\n",
		rn, bytesToAllocate, a_maxEntriesStored);
    newEntries = (struct gator_textcb_entry *)malloc(bytesToAllocate);
    if (newEntries == (struct gator_textcb_entry *)0) {
	fprintf(stderr,
		"[%s] Can't allocate %d bytes for the %d-member text entry array; errno is %d\n",
		rn, bytesToAllocate, a_maxEntriesStored, errno);
	free(newBuff);
	free(blankLine);
	return ((struct gator_textcb_hdr *)0);
    } else if (gator_textcb_debug)
	fprintf(stderr, "[%s] Text buffer entry array allocated at %p\n",
		rn, newEntries);

    /*
     * Finish off by allocating the text circular buffer header.
     */
    bytesToAllocate = sizeof(struct gator_textcb_hdr);
    if (gator_textcb_debug)
	fprintf(stderr,
		"[%s] Allocating %d bytes for the text circular buffer header\n",
		rn, bytesToAllocate);
    newHdr = (struct gator_textcb_hdr *)malloc(bytesToAllocate);
    if (newHdr == (struct gator_textcb_hdr *)0) {
	fprintf(stderr,
		"[%s] Can't allocate %d bytes for text circular buffer header; errno is %d\n",
		rn, bytesToAllocate, errno);
	free(newBuff);
	free(blankLine);
	free(newEntries);
	return ((struct gator_textcb_hdr *)0);
    } else if (gator_textcb_debug)
	fprintf(stderr,
		"[%s] Text circular buffer header allocated at %p\n", rn,
		newHdr);

    /*
     * Now, just initialize all the pieces and plug them in.
     */
    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Zeroing %d bytes in text buffer at %p\n", rn,
		numBuffBytes, newBuff);
    memset(newBuff, 0, numBuffBytes);

    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Initializing blank line buffer at %p\n", rn,
		blankLine);
    for (i = 0; i < a_maxCharsPerEntry; i++)
	*(blankLine + i) = ' ';
    *(blankLine + a_maxCharsPerEntry) = '\0';

    /*
     * Initialize each buffer entry.
     */
    for (curr_ent_num = 0, curr_buff = newBuff, curr_ent = newEntries;
	 curr_ent_num < a_maxEntriesStored;
	 curr_ent++, curr_ent_num++, curr_buff += (a_maxCharsPerEntry + 1)) {
	if (gator_textcb_debug)
	    fprintf(stderr,
		    "[%s] Initializing buffer entry %d; its text buffer address is %p\n",
		    rn, curr_ent_num, curr_buff);
	curr_ent->ID = 0;
	curr_ent->highlight = 0;
	curr_ent->numInversions = 0;
	curr_ent->charsUsed = 0;
	curr_ent->textp = curr_buff;
	memcpy(curr_ent->textp, blankLine, a_maxCharsPerEntry + 1);
	for (curr_inv = 0; curr_inv < GATOR_TEXTCB_MAXINVERSIONS; curr_inv++)
	    curr_ent->inversion[curr_inv] = 0;

    }				/*Init each buffer entry */

    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Filling in circ buff header at %p\n", rn,
		newHdr);
    Lock_Init(&(newHdr->cbLock));
    newHdr->maxEntriesStored = a_maxEntriesStored;
    newHdr->maxCharsPerEntry = a_maxCharsPerEntry;
    newHdr->currEnt = 0;
    newHdr->currEntIdx = 0;
    newHdr->oldestEnt = 0;
    newHdr->oldestEntIdx = 0;
    newHdr->entry = newEntries;
    newHdr->blankLine = blankLine;

    /*
     * Finally, return the location of the new header.
     */
    return (newHdr);

}				/*gator_textcb_Create */

/*------------------------------------------------------------------------
 * bumpCB
 *
 * Description:
 *	Move down to the next circular buffer entry.
 *
 * Arguments:
 *	struct gator_textcb_hdr *a_cbhdr : Circ buff header to bump.
 *
 * Returns:
 *	Ptr to the newest current entry.
 *
 * Environment:
 *	Nothing interesting.
 *
 * Side Effects:
 *	As advertised.
 *------------------------------------------------------------------------*/

static struct gator_textcb_entry *
bumpEntry(struct gator_textcb_hdr *a_cbhdr)

{				/*bumpEntry */

    static char rn[] = "bumpEntry";	/*Routine name */
    struct gator_textcb_entry *curr_ent;	/*Ptr to current entry */
    int inv;			/*Inversion number */

    /*
     * Bump the total number of writes, and don't forget to advance
     * the oldest entry, if appropriate.
     */
    if (gator_textcb_debug)
	fprintf(stderr,
		"[%s]: Bumping entry for circular buffer at %p; current values: currEnt=%d (idx %d), oldestEnt=%d (idx %d), maxEntriesStored=%d\n",
		rn, a_cbhdr, a_cbhdr->currEnt, a_cbhdr->currEntIdx,
		a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx,
		a_cbhdr->maxEntriesStored);

    a_cbhdr->currEnt++;
    if (++(a_cbhdr->currEntIdx) >= a_cbhdr->maxEntriesStored)
	a_cbhdr->currEntIdx = 0;
    curr_ent = a_cbhdr->entry + a_cbhdr->currEntIdx;

    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Zeroing entry %d (idx %d) at %p\n", rn,
		a_cbhdr->currEnt, a_cbhdr->currEntIdx, curr_ent);

    curr_ent->ID = a_cbhdr->currEnt;
    curr_ent->highlight = 0;
    curr_ent->numInversions = 0;
    curr_ent->charsUsed = 0;
    /*
     * Copy over a blank line into the one we're initializing.  We
     * copy over the trailing null, too.
     */
    memcpy(curr_ent->textp, a_cbhdr->blankLine,
	   a_cbhdr->maxCharsPerEntry + 1);
    for (inv = 0; inv < GATOR_TEXTCB_MAXINVERSIONS; inv++)
	curr_ent->inversion[inv] = 0;

    /*
     * If we've already stated circulating in the buffer, remember to
     * bump the oldest entry info too.
     */
    if (a_cbhdr->currEnt >= a_cbhdr->maxEntriesStored) {
	if (gator_textcb_debug)
	    fprintf(stderr,
		    "[%s]: Advancing oldest entry number & index; was entry %d, index %d, now ",
		    rn, a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx);
	a_cbhdr->oldestEnt++;
	if (++(a_cbhdr->oldestEntIdx) >= a_cbhdr->maxEntriesStored)
	    a_cbhdr->oldestEntIdx = 0;
	if (gator_textcb_debug)
	    fprintf(stderr, "entry %d, index %d\n", a_cbhdr->oldestEnt,
		    a_cbhdr->oldestEntIdx);
    }

    /*Bump oldest entry info */
    /*
     * Finally, return the address of the newest current entry.
     */
    return (curr_ent);

}				/*bumpEntry */

/*------------------------------------------------------------------------
 * gator_textcb_Write
 *
 * Description:
 *	Write the given string to the text circular buffer.  Line
 *	breaks are caused either by overflowing the current text
 *	line or via explicit '\n's.
 *
 * Arguments:
 *	struct gator_textcb_hdr *a_cbhdr : Ptr to circ buff hdr.
 *	char *a_textToWrite		 : Ptr to text to insert.
 *	int a_numChars			 : Number of chars to write.
 *	int a_highlight			 : Use highlighting?
 *	int a_skip;			 : Force a skip to the next line?
 *
 * Returns:
 *	Zero if successful,
 *	Error value otherwise.
 *
 * Environment:
 *	Circular buffer is consistent upon entry, namely the first and
 *	last entry pointers are legal.
 *
 * Side Effects:
 *	As advertised.
 *------------------------------------------------------------------------*/

int
gator_textcb_Write(struct gator_textcb_hdr *a_cbhdr, char *a_textToWrite,
		   int a_numChars, int a_highlight, int a_skip)
{				/*gator_textcb_Write */

    static char rn[] = "gator_textcb_Write";	/*Routine name */
    struct gator_textcb_entry *curr_ent;	/*Ptr to current text entry */
    int curr_ent_idx;		/*Index of current entry */
    int max_chars;		/*Max chars per entry */
    int chars_to_copy;		/*Num chars to copy in */
    int effective_highlight;	/*Tmp highlight value */
    char *dest;			/*Destination of char copy */

    /*
     * Make sure we haven't been passed a bogus buffer, and lock it
     * before we start.
     */
    if (a_cbhdr == (struct gator_textcb_hdr *)0) {
	fprintf(stderr,
		"[%s]: Null pointer passed in for circ buff header!!  Aborting write operation.\n",
		rn);
	return (-1);
    }
    ObtainWriteLock(&(a_cbhdr->cbLock));

    curr_ent_idx = a_cbhdr->currEntIdx;
    curr_ent = (a_cbhdr->entry) + curr_ent_idx;
    max_chars = a_cbhdr->maxCharsPerEntry;
    effective_highlight = curr_ent->highlight;
    if (curr_ent->numInversions % 2)
	effective_highlight = (effective_highlight ? 0 : 1);
    if (gator_textcb_debug)
	fprintf(stderr,
		"[%s]: Current entry: %d (at index %d, keeping %d max), effective highlight: %d, located at %p\n",
		rn, a_cbhdr->currEnt, curr_ent_idx, a_cbhdr->maxEntriesStored,
		effective_highlight, curr_ent);

    while (a_numChars > 0) {
	/*
	 * There are still characters to stuff into our circular buffer.
	 */
	if (gator_textcb_debug)
	    fprintf(stderr,
		    "[%s]: Top of write loop: %d char(s) left to write.\n",
		    rn, a_numChars);

	if (curr_ent->charsUsed >= max_chars) {
	    /*
	     * Bump the entry in the given circular buffer.
	     */
	    if (gator_textcb_debug)
		fprintf(stderr,
			"[%s]: Entry %d at index %d full, advancing to next one.\n",
			rn, a_cbhdr->currEnt, curr_ent_idx);
	    curr_ent = bumpEntry(a_cbhdr);
	    curr_ent_idx = a_cbhdr->currEntIdx;
	    if (gator_textcb_debug)
		fprintf(stderr,
			"[%s] New CB entry info: currEnt=%d (idx %d), oldestEnt=%d (idx %d), curr entry ptr is %p\n",
			rn, a_cbhdr->currEnt, a_cbhdr->currEntIdx,
			a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx, curr_ent);
	}

	/*Bump current entry */
	/*
	 * At this point, the current entry has room for at least one more
	 * character, and we have at least one more character to write.
	 * Insert as much from the user text as possible.
	 */
	chars_to_copy = max_chars - curr_ent->charsUsed;
	if (a_numChars < chars_to_copy)
	    chars_to_copy = a_numChars;
	dest = curr_ent->textp + curr_ent->charsUsed;
	if (gator_textcb_debug)
	    fprintf(stderr,
		    "[%s]: Copying %d char(s) into current entry at %p (entry buffer starts at %p)\n",
		    rn, chars_to_copy, dest, curr_ent->textp);

	/*
	 * Handle highlighting and inversions.
	 */
	if (curr_ent->charsUsed == 0) {
	    /*
	     * No chars yet, so this sets the highlight field.
	     */
	    effective_highlight = curr_ent->highlight = a_highlight;
	} else if (effective_highlight != a_highlight) {
	    /*
	     * We need a new inversion, if there's room.
	     */
	    if (gator_textcb_debug)
		fprintf(stderr,
			"[%s]: Highlight mismatch, recording inversion at char loc %d\n",
			rn, curr_ent->charsUsed);
	    if (curr_ent->numInversions < GATOR_TEXTCB_MAXINVERSIONS) {
		effective_highlight = a_highlight;
		curr_ent->inversion[curr_ent->numInversions] =
		    curr_ent->charsUsed;
		curr_ent->numInversions++;
	    } else if (gator_textcb_debug)
		fprintf(stderr, "[%s]: Out of inversions!!\n", rn);
	}

	/*Handle inversion */
	/*
	 * Move the string chunk into its place in the buffer, bump the
	 * number of chars used in the current entry.
	 */
	strncpy(dest, a_textToWrite, chars_to_copy);
	curr_ent->charsUsed += chars_to_copy;
	a_textToWrite += chars_to_copy;
	a_numChars -= chars_to_copy;

    }				/*while (a_numChars > 0) */

    /*
     * All characters have been copied in.  Handle the case where we've
     * been asked to skip to the next entry, even if there's still room
     * in the current one.
     */
    if (a_skip) {
	if (gator_textcb_debug)
	    fprintf(stderr, "[%s] Handling request to skip to next entry\n",
		    rn);
	if (curr_ent->charsUsed > 0)
	    curr_ent = bumpEntry(a_cbhdr);
	else if (gator_textcb_debug)
	    fprintf(stderr,
		    "[%s] Not skipping, we're already on a fresh entry\n",
		    rn);
    }

    /*Skip to the next entry */
    /*
     * We can now unlock the CB and return successfully.
     */
    ReleaseWriteLock(&(a_cbhdr->cbLock));
    return (0);

}				/*gator_textcb_Write */

/*------------------------------------------------------------------------
 * gator_textcb_BlankLine
 *
 * Description:
 *	Write out some number of blank lines to the given circular
 *	buffer.
 *
 * Arguments:
 *	struct gator_textcb_hdr *a_cbhdr : Ptr to circ buff hdr.
 *	int *a_numBlanks		 : Num. blank lines to write.
 *
 * Returns:
 *	Zero if successful,
 *	Error value otherwise.
 *
 * Environment:
 *	Circular buffer is consistent upon entry, namely the first and
 *	last entry pointers are legal.
 *
 * Side Effects:
 *	As advertised.
 *------------------------------------------------------------------------*/

int
gator_textcb_BlankLine(struct gator_textcb_hdr *a_cbhdr,
		       int a_numBlanks)
{				/*gator_textcb_BlankLine */

    static char rn[] = "gator_textcb_BlankLine";	/*Routine name */

    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Putting out %d blank lines to the CB at %p\n",
		rn, a_numBlanks, a_cbhdr);

    if (a_cbhdr == (struct gator_textcb_hdr *)0) {
	if (gator_textcb_debug)
	    fprintf(stderr, "[%s] Null pointer passed for CB hdr!!\n", rn);
	return (-1);
    }

    while (a_numBlanks > 0) {
	/*
	 * The bumpEntry routine returns a struct gator_textcb_entry
	 * pointer, but we don't need it here, so we don't assign it.
	 */
	bumpEntry(a_cbhdr);
	a_numBlanks--;
    }

    /*
     * Return happily and successfully.
     */
    return (0);

}				/*gator_textcb_Write */

/*------------------------------------------------------------------------
 * gator_textcb_Delete
 *
 * Description:
 *	Delete the storage used by the given circular buffer, including
 *	the header itself.
 *
 * Arguments:
 *	struct gator_textcb_hdr *a_cbhdr : Ptr to the header of the
 *						circ buffer to reap.
 *
 * Returns:
 *	Zero if successful,
 *	Error value otherwise.
 *
 * Environment:
 *	We write-lock the buffer before deleting it, which is slightly
 *	silly, since it will stop existing after we're done.  At least
 *	we'll make sure nobody is actively writing to it while it's
 *	being deleted.
 *
 * Side Effects:
 *	As advertised.
 *------------------------------------------------------------------------*/

int
gator_textcb_Delete(struct gator_textcb_hdr *a_cbhdr)
{				/*gator_textcb_Delete */

    static char rn[] = "gator_textcb_Delete";	/*Routine name */

    if (gator_textcb_debug)
	fprintf(stderr, "[%s]: Deleting text circular buffer at %p\n", rn,
		a_cbhdr);
    ObtainWriteLock(&(a_cbhdr->cbLock));

    /*
     * The beginning of the text buffer itself is pointed to by the
     * first text entry.
     */
    if (gator_textcb_debug)
	fprintf(stderr,
		"[%s]: Freeing text buffer proper at %p (%d bytes)\n", rn,
		a_cbhdr->entry[0].textp,
		(a_cbhdr->maxEntriesStored * a_cbhdr->maxCharsPerEntry));
    free(a_cbhdr->entry[0].textp);
    a_cbhdr->entry[0].textp = NULL;

    if (gator_textcb_debug)
	fprintf(stderr, "[%s]: Freeing text entry array at %p (%" AFS_SIZET_FMT " bytes)\n",
		rn, a_cbhdr->entry,
		(a_cbhdr->maxEntriesStored *
		 sizeof(struct gator_textcb_entry)));
    free(a_cbhdr->entry);
    a_cbhdr->entry = (struct gator_textcb_entry *)0;
    free(a_cbhdr->blankLine);
    a_cbhdr->blankLine = NULL;

    /*
     * Release the write lock on it, then free the header itself.
     */
    ReleaseWriteLock(&(a_cbhdr->cbLock));
    if (gator_textcb_debug)
	fprintf(stderr, "[%s] Freeing cicular buffer header at %p\n", rn,
		a_cbhdr);
    free(a_cbhdr);
    return (0);

}				/*gator_textcb_Delete */
