#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/draw.h>
#include <9pm/thread.h>
#include <9pm/cursor.h>
#include <9pm/mouse.h>
#include <9pm/keyboard.h>
#include <9pm/frame.h>
#include <9pm/fcall.h>
#include <9pm/plumb.h>
#include "dat.h"
#include "fns.h"

/*
 * Structure of Undo list:
 * 	The Undo structure follows any associated data, so the list
 *	can be read backwards: read the structure, then read whatever
 *	data is associated (insert string, file name) and precedes it.
 *	The structure includes the previous value of the modify bit
 *	and a sequence number; successive Undo structures with the
 *	same sequence number represent simultaneous changes.
 */

typedef struct Undo Undo;
struct Undo
{
	short	type;		/* Delete, Insert, Filename */
	short	mod;	/* modify bit */
	uint		seq;		/* sequence number */
	uint		p0;		/* location of change (unused in f) */
	uint		n;		/* # runes in string or file name */
};

enum
{
	Undosize = sizeof(Undo)/sizeof(Rune),
};

File*
fileaddtext(File *f, Text *t)
{
	if(f == nil){
		f = emalloc(sizeof(File));
		f->unread = TRUE;
	}
	f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
	f->text[f->ntext++] = t;
	f->curtext = t;
	return f;
}

void
filedeltext(File *f, Text *t)
{
	int i;

	for(i=0; i<f->ntext; i++)
		if(f->text[i] == t)
			goto Found;
	error("can't find text in filedeltext");

    Found:
	f->ntext--;
	if(f->ntext == 0){
		fileclose(f);
		return;
	}
	memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
	if(f->curtext == t)
		f->curtext = f->text[0];
}

void
fileinsert(File *f, uint p0, Rune *s, uint ns)
{
	if(p0 > f->b.nc)
		error("internal error: fileinsert");
	if(f->seq > 0)
		fileuninsert(f, &f->delta, p0, ns);
	bufinsert(&f->b, p0, s, ns);
	if(ns)
		f->mod = TRUE;
}

void
fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
{
	Undo u;

	/* undo an insertion by deleting */
	u.type = Delete;
	u.mod = f->mod;
	u.seq = f->seq;
	u.p0 = p0;
	u.n = ns;
	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
}

void
filedelete(File *f, uint p0, uint p1)
{
	if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc))
		error("internal error: filedelete");
	if(f->seq > 0)
		fileundelete(f, &f->delta, p0, p1);
	bufdelete(&f->b, p0, p1);
	if(p1 > p0)
		f->mod = TRUE;
}

void
fileundelete(File *f, Buffer *delta, uint p0, uint p1)
{
	Undo u;
	Rune *buf;
	uint i, n;

	/* undo a deletion by inserting */
	u.type = Insert;
	u.mod = f->mod;
	u.seq = f->seq;
	u.p0 = p0;
	u.n = p1-p0;
	buf = fbufalloc();
	for(i=p0; i<p1; i+=n){
		n = p1 - i;
		if(n > RBUFSIZE)
			n = RBUFSIZE;
		bufread(&f->b, i, buf, n);
		bufinsert(delta, delta->nc, buf, n);
	}
	fbuffree(buf);
	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);

}

void
filesetname(File *f, Rune *name, int n)
{
	if(f->seq > 0)
		fileunsetname(f, &f->delta);
	free(f->name);
	f->name = runemalloc(n);
	runemove(f->name, name, n);
	f->nname = n;
	f->unread = TRUE;
}

void
fileunsetname(File *f, Buffer *delta)
{
	Undo u;

	/* undo a file name change by restoring old name */
	u.type = Filename;
	u.mod = f->mod;
	u.seq = f->seq;
	u.p0 = 0;	/* unused */
	u.n = f->nname;
	if(f->nname)
		bufinsert(delta, delta->nc, f->name, f->nname);
	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
}

uint
fileload(File *f, uint p0, int fd, int *nulls)
{
	if(f->seq > 0)
		error("undo in file.load unimplemented");
	return bufload(&f->b, p0, fd, nulls);
}

/* return sequence number of pending redo */
uint
fileredoseq(File *f)
{
	Undo u;
	Buffer *delta;

	delta = &f->epsilon;
	if(delta->nc == 0)
		return ~0;
	bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize);
	return u.seq;
}

void
fileundo(File *f, int isundo, uint *q0p, uint *q1p)
{
	Undo u;
	Rune *buf;
	uint i, j, n, up;
	uint stop;
	Buffer *delta, *epsilon;

	if(isundo){
		/* undo; reverse delta onto epsilon, seq decreases */
		delta = &f->delta;
		epsilon = &f->epsilon;
		stop = f->seq;
	}else{
		/* redo; reverse epsilon onto delta, seq increases */
		delta = &f->epsilon;
		epsilon = &f->delta;
		stop = 0;	/* don't know yet */
	}

	buf = fbufalloc();
	while(delta->nc > 0){
		up = delta->nc-Undosize;
		bufread(delta, up, (Rune*)&u, Undosize);
		if(isundo){
			if(u.seq < stop){
				f->seq = u.seq;
				goto Return;
			}
		}else{
			if(stop == 0)
				stop = u.seq;
			if(u.seq > stop)
				goto Return;
		}
		switch(u.type){
		default:
			fprint(2, "undo: 0x%ux\n", u.type);
			abort();
			break;

		case Delete:
			f->seq = u.seq;
			fileundelete(f, epsilon, u.p0, u.p0+u.n);
			f->mod = u.mod;
			bufdelete(&f->b, u.p0, u.p0+u.n);
			for(j=0; j<f->ntext; j++)
				textdelete(f->text[j], u.p0, u.p0+u.n, FALSE);
			*q0p = u.p0;
			*q1p = u.p0;
			break;

		case Insert:
			f->seq = u.seq;
			fileuninsert(f, epsilon, u.p0, u.n);
			f->mod = u.mod;
			up -= u.n;
			for(i=0; i<u.n; i+=n){
				n = u.n - i;
				if(n > RBUFSIZE)
					n = RBUFSIZE;
				bufread(delta, up+i, buf, n);
				bufinsert(&f->b, u.p0+i, buf, n);
				for(j=0; j<f->ntext; j++)
					textinsert(f->text[j], u.p0+i, buf, n, FALSE);
			}
			*q0p = u.p0;
			*q1p = u.p0+u.n;
			break;

		case Filename:
			f->seq = u.seq;
			fileunsetname(f, epsilon);
			f->mod = u.mod;
			up -= u.n;
			free(f->name);
			if(u.n == 0)
				f->name = nil;
			else
				f->name = runemalloc(u.n);
			bufread(delta, up, f->name, u.n);
			f->nname = u.n;
			break;
		}
		bufdelete(delta, up, delta->nc);
	}
	if(isundo)
		f->seq = 0;
    Return:
	fbuffree(buf);
}

void
filereset(File *f)
{
	bufreset(&f->delta);
	bufreset(&f->epsilon);
	f->seq = 0;
}

void
fileclose(File *f)
{
	free(f->name);
	f->nname = 0;
	f->name = nil;
	free(f->text);
	f->ntext = 0;
	f->text = nil;
	bufclose(&f->b);
	bufclose(&f->delta);
	bufclose(&f->epsilon);
	elogclose(f);
	free(f);
}

void
filemark(File *f)
{
	if(f->epsilon.nc)
		bufdelete(&f->epsilon, 0, f->epsilon.nc);
	f->seq = seq;
}
