/* @(#)text.c 1.6 95/09/16 */

/*
 * Copyright (c) 1994, 1995 by Wayne C. Gramlich.  All rights reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * for any purpose is hereby granted without fee provided that the above
 * copyright notice and this permission are retained.  The author makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

/* LINTLIBRARY */

#include <assert.h>
#include <string.h>
#include <stdlib.h>

#include "error_extern.h"
#include "chr_extern.h"
#include "memory_extern.h"
#include "text_extern.h"
#include "str_extern.h"

struct text_struct {
    unsigned size;	/* Text size in characters */
    Chr *data;		/* Text data */
};

/*
 * text_chr_fetch(text, index)
 *	This routine will return the {index}'th character from {text}.
 *	An assertion failure occurs if {index} is too large.
 */
Chr
text_chr_fetch(
    Text text,
    unsigned index)
{
    Chr chr;

    assert(index < text->size);
    chr = text->data[index];
    return chr;
}

/*
 * text_chr_search(text, chr)
 *	This routine will serach for {chr} in {text}.  If {chr} is found,
 *	the offset into {text} is returned; otherwise -1 is returned.
 */
int
text_chr_search(
    Text text,
    Chr chr)
{
    Chr *data;

    data = (Chr *)memchr((char *)text->data, (int)chr, text->size);
    if (data == (Chr *)0) {
	return -1;
    }
    return data - text->data;
}

/*
 * text_create(str, size)
 *	This routine will create and return a text object containing
 *	a pointer to the {size} characters starting at {data}.
 */
Text
text_create(
    Chr *data,
    unsigned size)
{
    Text text;

    text = (Text)memory_allocate(sizeof(*text));
    text->data = data;
    text->size = size;
    return text;
}

/*
 * text_copy_deep(text)
 *	This routine will return a copy of {text} including a copy
 *	of the underlying text.
 */
Text
text_copy_deep(
    Text text)
{
    Chr *data;
    Text new_text;
    unsigned size;

    size = text->size;
    data = str_allocate(size);
    (void)memcpy(data, text->data, size);
    data[size] = '\0';
    new_text = text_create(data, size);
    return new_text;
}

/*
 * text_copy_shallow(text)
 *	This routine will return a copy of {text} without making
 *	a copy of the underlying characters.
 */
Text
text_copy_shallow(
    Text text)
{
    Text new_text;

    new_text = text_create(text->data, text->size);
    return new_text;
}

/*
 * text_equal(text1, text2)
 *	This routine will return 1 if the contents of {Text1} are
 *	exactly equivalent to {Text2} and0 otherwise.
 */
int
text_equal(
    Text text1,
    Text text2)
{
    unsigned size;

    size = text1->size;
    if ((size != text2->size) ||
      (memcmp((char *)text1->data, (char *)text2->data, size) != 0)) {
	return 0;
    }
    return 1;
}

/*
 * text_free_deep(Text)
 *	This routine will release the storage associted with {Text}
 *	including its string data.
 */
void
text_free_deep(
    Text text)
{
    free(text->data);
    text_free_shallow(text);
}

/*
 * text_free_deep(Text)
 *	This routine will release the storage associted with {Text}
 *	excluding its string data.
 */
void
text_free_shallow(
    Text text)
{
    free(text);
}

/*
 * text_get_word(text, stop_chr)
 *	This routine will a word from {text} until {stop_chr} or
 *	until the end of {text} is encountered.
 */
Str
text_get_word(
    Text text,
    Chr stop_chr)
{
    Chr chr;
    Chr *data;
    unsigned index;
    Chr *ptr;
    unsigned size;
    Str word;

    /* Prescan for the stop character: */
    index = 0;
    data = text->data;
    size = text->size;
    for (index = 0; index < size; index++) {
	if (data[index] == stop_chr) {
	    break;
	}
    }
    if (size == index) {
	text->size = 0;
	text->data = data + size;
    } else {
	text->size = size - index - 1;
	text->data = data + index + 1;
    }
    size = index;

    word = str_allocate(size);
    ptr = word;
    size = index;
    for (index = 0; index < size; index++) {
	chr = data[index];
	switch (chr) {
	  case '+':	/* Pluses are converted into spaces: */
	    chr = ' ';
	    break;
	  case '%':	/* Percent characters are followed by 2 hex digits: */
	    {
		unsigned hex_chr1;
		unsigned hex_chr2;

		hex_chr1 = data[++index];
		hex_chr2 = data[++index];
		chr = (Chr)((chr_to_hex_digit(hex_chr1) << 4) |
		  chr_to_hex_digit(hex_chr2));
		break;
	    }
	}
	*ptr++ = chr;
	*ptr = '\0';	/* Keep null-terminated: */
    }
    return word;
}

/*
 * text_lop_name_shallow(text)
 *	This routine will create and return a {Text} object containing
 *	the name starting at {text}.  {text} is left pointing at the
 *	remaining characters.
 */
Text
text_lop_name_shallow(
    Text text)
{
    unsigned index;

    index = 0;
    while (chr_is_name(text_chr_fetch(text, index))) {
	index++;
    }
    return text_lop_shallow(text, index);
}

/*
 * text_lop_shallow(text, amount)
 *	This routine will adjust {text} so that the {amount} characters
 *	at the beginning are no longer accessed.
 */
Text
text_lop_shallow(
    Text text,
    unsigned amount)
{
    Text new_text;
    Chr *data;
    unsigned size;

    data = text->data;
    size = text->size;
    assert (amount <= size);
    new_text = text_create(data, amount);
    text->size = size - amount;
    text->data = data + amount;
    return new_text;
}

/*
 * text_lop_white_space_shallow(text, index)
 *	This routine will remove create an return a {Text} object that
 *	contains all characters from the beginning of {text} through
 *	the {index}'th character including any following white space
 *	characters.  {text} is updated to point the remaining characters.
 */
Text
text_lop_white_space_shallow(
    Text text,
    unsigned index)
{
    while (chr_is_white_space(text_chr_fetch(text, index))) {
	index++;
    }
    return text_lop_shallow(text, index);
}

/*
 * text_read(in_file)
 *	This routine will read the characters from {in_file} and return
 *	the resulting {Text} object.
 */
Text
text_read(
    FILE *in_file)
{
    unsigned amount_read;
    Pointer buffer;
    unsigned limit;
    unsigned size;
    Text text;
    
    /* Read the file until dry: */
    size = 0;
    limit =sizeof(int);
    buffer = memory_allocate(limit);
    for (;;) {
	amount_read = fread((Chr *)buffer + size, 1, limit - size, in_file);
	if ((amount_read == 0) && feof(in_file)) {
	    break;
	}
	size += amount_read;
	if (size == limit) {
	    limit <<= 1;
	    buffer = memory_reallocate(buffer, limit);
	}
    }

    /* Trim and the buffer, create the {Text} object and return: */
    buffer = memory_reallocate(buffer, size + 1);
    text = text_create((Chr *)buffer, size);
    text->data[size] = '\0';
    return text;
}

/*
 * text_read_exact(in_file, count, errors)
 *	This routine will read exactly {count} characters from {in_file}
 *	and return the resulting {Text} object.  If {count} characters
 *	are not successfully read in
 */
Text
text_read_exact(
    FILE *in_file,
    unsigned count,
    Errors errors)
{
    unsigned amount_read;
    Str data;
    Text text;

    data = str_allocate(count);
    amount_read = fread(data, 1, count, in_file);
    if (amount_read != count) {
	errors_append(errors,
	  str_printf("Only read %d characters of form (not %d)!",
	  amount_read, count));
    }
    text = text_create(data, amount_read);
    return text;
}

/*
 * text_restore(in_file);
 *	This routine will write read and return a {Text} object from
 *	{in_file} that was written with {text_save()}.
 */
Text
text_restore(
    FILE *in_file)
{
    Str str;
    Text text;

    str = str_restore(in_file);
    if (str == (Str)0) {
	return (Text)0;
    }
    /* FIXME: This is pretty sloppy! */
    text = text_create(str, str_size(str));
    return text;
}

/*
 * text_save(text, out_file)
 *	This routine will write {text} to {out_file} in such a way that
 *	it can be reread by {text_restore()}.
 */
void
text_save(
    Text text,
    FILE *out_file)
{
    /* FIXME: This is sloppy for now: */
    if (text == (Text)0) {
	(void)fprintf(out_file, "0");
    } else {
	unsigned size;

	size = text->size;
	(void)fprintf(out_file, "\"");
	assert(fwrite((void *)text->data, 1, size, out_file) == size);
	(void)fprintf(out_file, "\"");
    }
}

/*
 * text_size(text)
 *	This routine will return the number of characters in {text}.
 */
unsigned
text_size(
    Text text)
{
    return text->size;
}

/*
 * text_str_copy(text)
 *	This routine will return a copy {text} as a string.
 */
Str
text_str_copy(
    Text text)
{
    unsigned size;
    Str str;

    size = text_size(text);
    str = str_allocate(size);
    str[size] = '\0';
    memory_copy(str, text->data, size);
    return str;
}

/*
 * text_str_upper_case(text)
 *	This routine will return a upper case version of {text} as a string.
 */
Str
text_str_upper_case(
    Text text)
{
    unsigned index;
    unsigned size;
    Str str;

    size = text_size(text);
    str = str_allocate(size);
    str[size] = '\0';
    memory_copy(str, text->data, size);
    for (index = 0; index < size; index++) {
	str[index] = chr_to_upper(str[index]);
    }
    return str;
}

/*
 * text_weak_equal(text1, text2)
 *	Will return 1 if either {text1} and {text2} are both (Text)0 or
 *	if they are both non-null and the texting contents are equal;
 *	otherwise, 0 is returned.
 */
int
text_weak_equal(
    Text text1,
    Text text2)
{
    if ((text1 == (Text)0) && (text2 == (Text)0)) {
	return 1;
    } else if ((text1 == (Text)0) || (text2 == (Text)0)) {
	return 0;
    }
    return text_equal(text1, text2);
}

/*
 * text_write(text, out_file)
 *	This routine will write {text} to {out_file}.
 */
void
text_write(
    Text text,
    FILE *out_file)
{
    unsigned size;
    unsigned amount_written;

    size = text->size;
    amount_written = fwrite(text->data, 1, size, out_file);
    assert(amount_written == size);
}

/*
 * text_write_range(text, out_file, start_offset, length)
 *	This routine will write the {length} characters from {text}
 *	starting at {start_offset} to {out_file}.
 */
void
text_write_range(
    Text text,
    FILE *out_file,
    unsigned start_offset,
    unsigned length)
{
    unsigned size;
    unsigned amount_written;

    size = text->size;
    assert(start_offset + length <= size);
    amount_written = fwrite(text->data + start_offset, 1, length, out_file);
    assert(amount_written == length);
}


