/*
 * csplit: context split a file.
 *
 * Written 1-May-1999 by orc@pell.chi.il.us (david parsons) and released
 * into the public domain.
 */

static const char rcsid[] = "Mastodon $Id: s.csplit.c 1.4 00/05/21 16:33:42-00:00 orc $";
 
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <regex.h>
#include <unistd.h>
#include <stdlib.h>
#include <basis/options.h>
#include <basis/rlstring.h>

static struct lifo {
    int *heap;			/* length of cached strings */
    int size_heap;		/* number of strings we're caching */
    int next_heap;		/* next insert point (if 0, push oldest out) */
} lifo = { 0, 0, 0 };


typedef struct {
    int repeat;
    int ignore;
    enum { RE, LINENO, GAP } type;
    union {
	struct { regex_t *us_regex;  int us_offset; } re;
	int u_lineno;
	int u_gap;
    } u;
#define	regex	u.re.us_regex
#define offset	u.re.us_offset
#define lineno	u.u_lineno
#define gap	u.u_gap
} Arg;

static Arg dummy = { 0, 0, 0, 0 };

static char *prefix = "xx";	/* -f prefix */
static int   digits = 2;	/* -n width */
static int   leave_intact = 0;	/* -k */
static int   quiet = 0;		/* -s */
static int   file_number = 0;	/* our sequence number */

static char *current_file;		/* file being currently written */

static struct x_option options[] = {
    { 'f', 'f', "prefix", "FILE", "Name the created file FILE00 .. FILEnn" },
    { 'k', 'k', "keep",  0,       "On error, leave created files instead of\n"
				  "deleting them" },
    { 'n', 'n', "sequence-width",
			  "WIDTH","Use NUMBER decimal digits to form filenames" },
    { 's', 'q', "quiet",  0,      "" },
    { 's', 's', "silent", 0,      "Work quietly" },
    { 'v',  0 , "version", 0,     "Say the program version, then exit" },
    { 'h', '?', "help", 0,        "Display this help message" },
} ;
#define NR_OPTIONS (sizeof options / sizeof options[0])

char *__progname;

/*
 * usage() spits out a usage message, then exits
 */
static void
usage(int it_is_an_error)
{
    fprintf(stderr, "\nusage: %s [options]\n\n", __progname);
    showopts(stderr, NR_OPTIONS, options);

    exit (it_is_an_error ? EXIT_FAILURE : EXIT_SUCCESS);
} /* usage */


static Arg *context = 0;
static int ncontext = 0;

/*
 * add_context() adds a context to our array of Args
 */
static void
add_context(char *arg)
{
    char *p;
    int neg;
    
    switch (*arg) {
    case '/':
    case '%':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
	if ((context = realloc(context, (10+ncontext) * sizeof context[0])) == 0) {
	    perror("realloc");
	    exit(EXIT_FAILURE);
	}
	memset(&context[ncontext], 0, sizeof context[ncontext]);
	context[ncontext].repeat = 1;

	if (*arg == '/' || *arg == '%') {
	    context[ncontext].type = RE;

	    /* first strip trailing offsets off the pattern (and massage
	     * the pattern into shape for regcomp()iling
	     */
	    if ((p = strrchr(arg, *arg)) != 0 && p != arg) {
		*p++ = 0;
		if (*p == '-') {
		    neg = 1;
		    ++p;
		}
		else {
		    if (*p == '+')
			p++;
		    neg = 0;
		}
		if (*p) {
		    context[ncontext].offset = atoi(p);
		    if (neg)
			context[ncontext].offset = -(context[ncontext].offset);
		}
	    }
	    if (*arg == '%')
		context[ncontext].ignore = 1;
	    context[ncontext].regex = malloc( sizeof(regex_t) );

	    if (context[ncontext].regex == 0) {
		perror("realloc");
		exit(EXIT_FAILURE);
	    }
	    if (regcomp(context[ncontext].regex, 1+arg, REG_EXTENDED) != 0) {
		fprintf(stderr, "%s: unrecognised regular expression (%s)\n",
				__progname, 1+arg);
		exit(EXIT_FAILURE);
	    }
	}
	else {
	    context[ncontext].type = LINENO;
	    context[ncontext].lineno = atoi(arg);
	}
	ncontext++;
	break;

    case '{':
	if (ncontext == 0) {
	    fprintf(stderr, "%s: first context may not be a repeat operation\n");
	    exit(EXIT_FAILURE);
	}
	else if (context[ncontext-1].type == LINENO) {
	    if ((context = realloc(context, (10+ncontext) * sizeof context[0])) == 0) {
		perror("realloc");
		exit(EXIT_FAILURE);
	    }
	    memset(&context[ncontext], 0, sizeof context[ncontext]);
	    context[ncontext].type = GAP;
	    context[ncontext].gap = context[ncontext-1].lineno;
	    context[ncontext].repeat = atoi(1+arg);
	    ncontext++;
	}
	else
	    context[ncontext-1].repeat = atoi(1+arg);
	break;

    default:
	fprintf(stderr, "%s: unrecognised context (%s)\n", __progname);
	fprintf(stderr, "\twe recognise:\n"
			"\t/pattern/[offset]\n"
			"\t%pattern%[offset]\n"
			"\tline_no\n"
			"\t{num}\n");
	exit(EXIT_FAILURE);
    }
} /* add_context */


/*
 * bail() flees if something went wrong splitting this file
 */
static void
bail()
{
    int x;

    if (!leave_intact) 
	for (x=file_number-1; x >= 0; --x) {
	    sprintf(current_file, "%s%0*d", prefix, digits, x);
	    unlink(current_file);
	}
    exit(EXIT_FAILURE);
} /* bail */


/*
 * open_output() opens an output file. 
 */
static FILE *
open_output(Arg *context)
{
    FILE *tmp;

    if (context->ignore)	/* Ye Bit Bucket */
	return 0;

    sprintf(current_file, "%s%0*d", prefix, digits, file_number++);

    if ((tmp = fopen(current_file, "w")) == 0) {
	perror(current_file);
	bail();
    }
    return tmp;
} /* open_output */


/*
 * push() shoves a line length onto the heap
 */
static void
push(int size)
{
    int x;

    if (lifo.next_heap == lifo.size_heap) {
	for (x=0; x < lifo.next_heap-1; x++)
	    lifo.heap[x] = lifo.heap[x+1];
	lifo.next_heap--;
    }
    lifo.heap[lifo.next_heap++] = size;
} /* push */


/*
 * csplit(), in mortal flesh
 */
void
main(int argc, char **argv)
{
    int opt;
    FILE *input;
    FILE *output;
    int x;
    int c;
    long filesize;
    int size;
    int linenr;
    rlString line;

    __progname = basename(argv[0]);

    x_opterr = 1;
    while ((opt = x_getopt(argc, argv, NR_OPTIONS, options)) != EOF)
	switch (opt) {
	case 'f':
		prefix = x_optarg;
		break;
	case 'k':
		leave_intact = 1;
		break;
	case 'n':
		digits = atoi(x_optarg);
		break;
	case 's':
		quiet = 1;
		break;
	case 'v':
		puts("$Revision: 1.4 $");
		exit(EXIT_SUCCESS);
	case 'h':
	default:
		usage(opt != 'h');
	}
	
    if (argc == x_optind)
	usage(1);

    /* preallocate the current_file area, just to be paranoid */
    if ( (current_file = malloc(strlen(prefix) + 20)) == 0) {
	perror("malloc");
	exit(EXIT_FAILURE);
    }

    lifo.heap = malloc(1);
    context = malloc(1);

    if (strcmp(argv[x_optind], "-") == 0)
	input = stdin;
    else if ((input = fopen(argv[x_optind], "r")) == 0) {
	perror(argv[x_optind]);
	exit(EXIT_FAILURE);
    }
    for (x = x_optind+1; x < argc; x++)
	add_context(argv[x]);

    line.data = malloc(1);
    line.alloc = 0;
    size = 0;
    linenr = 1;

    for (x=0; x < ncontext; ) {
	output = open_output(&context[x]);
	filesize = 0;

	/*
	 * resize the lifo for negative offsets
	 */
	if (context[x].offset < 0) {
	    lifo.heap = realloc(lifo.heap, (-context[x].offset) * sizeof line);
	    lifo.size_heap = -context[x].offset;
	}
	else
	    lifo.size_heap = 0;
	
	lifo.next_heap = 0;

/* process a file section
 */
	switch (context[x].type) {
	case RE:
	    while ( rlgets(&line, input) > 0) {
		linenr++;
		if (regexec(context[x].regex, line.data, 0, 0, 0) == 0)
		    break;
		push(size);
		filesize += line.size;
		if (output)
		    fwrite(line.data, 1, line.size, output);
	    }
	    if (size == 0) {
		fprintf(stderr, "%s: EOF looking for context\n", __progname);
		bail();
	    }
	    else {
		/* push back the marker line */
		fseek(input, -line.size, SEEK_CUR);
		--linenr;
	    }

	    /* back down to a negative offset */
	    if (context[x].offset < 0) {
		int ct;
		for (ct = lifo.next_heap-1; ct >= 0; --ct) {
		    fseek(input, -(lifo.heap[ct]), SEEK_CUR);
		    if (output)
			fseek(output, -(lifo.heap[ct]), SEEK_CUR);
		    filesize -= lifo.heap[ct];
		    --linenr;
		}
		if (output) {
		    fflush(output);
		    ftruncate(fileno(output), ftell(output));
		}
		size = 0;
	    }
	    else if (context[x].offset > 0) {
		/* step forward to a positive offset */
		int ct;

		for (ct=0; ct < context[x].offset; ct++) {
		    if (rlgets(&line, input) == 0) {
			if (output) {
			    fclose(output);
			    if (!quiet)
				printf("%ld\n", filesize);
			}
			exit(EXIT_SUCCESS);
		    }
		    ++linenr;
		    if (output)
			fwrite(line.data, 1, line.size, output);
		    filesize += line.size;
		}
	    }
	    break;

	case GAP:
	    for (c=0; c < context[x].gap; c++) {
		if ( rlgets(&line, input) > 0) {
		    linenr++;
		    filesize += line.size;
		    if (output)
			fwrite(line.data, 1, line.size, output);
		}
		else
		    break;
	    }
	    break;

	case LINENO:
	    if (linenr >= context[x].lineno) {
		fprintf(stderr, "%s: line number %d out of range\n",
				__progname, context[x].lineno);
		bail();
	    }
	    while (linenr < context[x].lineno) {
		rlgets(&line, input);
		linenr++;
		if (line.size == 0) {
		    fprintf(stderr, "%s: EOF looking for line %d\n",
				    __progname, context[x].lineno);
		    bail();
		}
		filesize += line.size;
		if (output)
		    fwrite(line.data, 1, line.size, output);
	    }
	    break;
	}
	if (output) {
	    fclose(output);
	    if (!quiet)
		printf("%ld\n", filesize);
	}
	if (--context[x].repeat == 0)
	    x++;
    }
    
    if (!feof(input)) {
	filesize = 0;
	output = open_output(&dummy);

	while ((c = getc(input)) != EOF) {
	    filesize++;
	    putc(c, output);
	}
	if (!quiet)
	    printf("%ld\n", filesize);
	fclose(output);
    }
    exit(EXIT_SUCCESS);
} /* csplit */
