/* Copyright (C) 1995, 1996, 1997, 1998 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: srle.c,v 1.2.6.1.2.1 2003/01/17 00:49:05 giles Exp $ */
/* RunLengthEncode filter */
#include "stdio_.h"		/* includes std.h */
#include "memory_.h"
#include "strimpl.h"
#include "srlx.h"

/* ------ RunLengthEncode ------ */

private_st_RLE_state();

/* Set defaults */
private void
s_RLE_set_defaults(stream_state * st)
{
    stream_RLE_state *const ss = (stream_RLE_state *) st;

    s_RLE_set_defaults_inline(ss);
}

/* Initialize */
private int
s_RLE_init(stream_state * st)
{
    stream_RLE_state *const ss = (stream_RLE_state *) st;

    return s_RLE_init_inline(ss);
}

/* Process a buffer */
private int
s_RLE_process(stream_state * st, stream_cursor_read * pr,
	      stream_cursor_write * pw, bool last)
{
    stream_RLE_state *const ss = (stream_RLE_state *) st;
    register const byte *p = pr->ptr;
    register byte *q = pw->ptr;
    const byte *rlimit = pr->limit;
    byte *wlimit = pw->limit;
    int status = 0;
    ulong rleft = ss->record_left;

    /*
     * We thought that the Genoa CET demands that the output from this
     * filter be not just legal, but optimal, so we went to the trouble
     * of ensuring this.  It turned out that this wasn't actually
     * necessary, but we didn't want to change the code back.
     *
     * For optimal output, we can't just break runs at buffer
     * boundaries: unless we hit a record boundary or the end of the
     * input, we have to look ahead far enough to know that we aren't
     * breaking a run prematurely.
     */

    /* Check for leftover output. */
copy:
    if (ss->copy_left) {
	uint rcount = rlimit - p;
	uint wcount = wlimit - q;
	uint count = ss->copy_left;

	if (rcount < count)
	    count = rcount;
	if (wcount < count)
	    count = wcount;
	if (rleft < count)
	    count = rleft;
	memcpy(q + 1, p + 1, count);
	pr->ptr = p += count;
	pw->ptr = q += count;
	if ((ss->record_left = rleft -= count) == 0)
	    ss->record_left = rleft = ss->record_size;
	if ((ss->copy_left -= count) != 0)
	    return (rcount == 0 ? 0 : 1);
    }
    while (p < rlimit) {
	const byte *beg = p;
	const byte *p1;
	uint count = rlimit - p;
	bool end = last;
	byte next;

	if (count > rleft)
	    count = rleft, end = true;
	if (count > 128)
	    count = 128, end = true;
	p1 = p + count - 1;
	if (count < 3) {
	    if (!end || count == 0)
		break;		/* can't look ahead far enough */
	    if (count == 1) {
		if (wlimit - q < 2) {
		    status = 1;
		    break;
		}
		*++q = 0;
	    } else {		/* count == 2 */
		if (p[1] == p[2]) {
		    if (wlimit - q < 2) {
			status = 1;
			break;
		    }
		    *++q = 255;
		} else {
		    if (wlimit - q < 3) {
			status = 1;
			break;
		    }
		    *++q = 1;
		    *++q = p[1];
		}
	    }
	    *++q = p1[1];
	    p = p1 + 1;
	} else if ((next = p[1]) == p[2] && next == p[3]) {
	    if (wlimit - q < 2) {
		status = 1;
		break;
	    }
	    /* Recognize leading repeated byte */
	    do {
		p++;
	    }
	    while (p < p1 && p[2] == next);
	    if (p == p1 && !end) {
		p = beg;	/* need to look ahead further */
		break;
	    }
	    p++;
	    *++q = (byte) (257 - (p - beg));
	    *++q = next;
	} else {
	    p1--;
	    while (p < p1 && (p[2] != p[1] || p[3] != p[1]))
		p++;
	    if (p == p1) {
		if (!end) {
		    p = beg;	/* need to look ahead further */
		    break;
		}
		p += 2;
	    }
	    count = p - beg;
	    if (wlimit - q < count + 1) {
		p = beg;
		if (q >= wlimit) {
		    status = 1;
		    break;
		}
		/* Copy some now and some later. */
		*++q = count - 1;
		ss->copy_left = count;
		goto copy;
	    }
	    *++q = count - 1;
	    memcpy(q + 1, beg + 1, count);
	    q += count;
	}
	rleft -= p - beg;
	if (rleft == 0)
	    rleft = ss->record_size;
    }
    if (last && status == 0 && ss->EndOfData) {
	if (q < wlimit)
	    *++q = 128;
	else
	    status = 1;
    }
    pr->ptr = p;
    pw->ptr = q;
    ss->record_left = rleft;
    return status;
}

/* Stream template */
const stream_template s_RLE_template = {
    &st_RLE_state, s_RLE_init, s_RLE_process, 129, 2, NULL,
    s_RLE_set_defaults, s_RLE_init
};
