/*	TEditCore.cpp
 *
 *		This handles the edit core routines; this knows how to insert
 *	and delete data from a handle, but doesn't actually display the
 *	information. Something higher level does that
 */

/*  XT - An example text editor application built with the YAAF library
 *
 *	PUBLIC DOMAIN: The source kit of 'xt' has been placed into the
 *	public domain by the author, William Edward Woody, on the hope
 *	that the source kit will provide a useful example of using the
 *	YAAF Library.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include <string.h>
#include "TEditCore.h"

/************************************************************************/
/*																		*/
/*	Class Declaration													*/
/*																		*/
/************************************************************************/

/*	TEditCore::TEditCore
 *
 *		Construct me
 */

TEditCore::TEditCore()
{
	fStart = 0;
	fEnd = 0;
	fText = NULL;
}

/*	TEditCore::~TEditCore
 *
 *		Delete me. Don't affect the text handle; that belongs elsewhere
 */

TEditCore::~TEditCore()
{
}

/************************************************************************/
/*																		*/
/*	Handle access														*/
/*																		*/
/************************************************************************/

/*	TEditCore::UpdateHandle
 *
 *		Update the handle; this completely recalculates the line
 *	starts handle. Normally I don't recalculate all of the line starts,
 *	but only the ones that change. In this case, however, I assume
 *	an outside force changed the text handle.
 */

void TEditCore::UpdateHandle()
{
	char *ptr;
	unsigned long i,len,ct;
	unsigned long *n;
	
	XGHandleLock textlock(*fText);
	ptr = (char *)**fText;

	ct = 0;
	len = fText->GetSize();
	for (i = 0; i < len; i++) if (ptr[i] == '\n') ct++;

	fStarts.SetSize(ct * sizeof(long));
	
	XGHandleLock startlock(fStarts);
	ct = 0;
	n = (unsigned long *)*fStarts;
	for (i = 0; i < len; i++) if (ptr[i] == '\n') n[ct++] = i;
}

/************************************************************************/
/*																		*/
/*	Line Access															*/
/*																		*/
/************************************************************************/

/*	TEditCore::GetNumLines
 *
 *		How many lines are there?
 */

unsigned long TEditCore::GetNumLines()
{
	return 1 + fStarts.GetSize() / sizeof(long);
}

/*	TEditCore::GetLineOffset
 *
 *		Get the start of the specified line
 */

unsigned long TEditCore::GetLineOffset(unsigned long line)
{
	unsigned long *n;
	
	if (line == 0) return 0;
	n = (unsigned long *)*fStarts;
	return 1 + n[line-1];
}

/*	TEditCore::GetLineLength
 *
 *		Get the length of the specified line
 */

unsigned long TEditCore::GetLineLength(unsigned long line)
{
	unsigned long nline = line + 1;
	
	/*
	 *	Find the position of the start of the next line.
	 *	If we're at the max number of lines, get the length
	 *	of the text field instead
	 */
	
	if (nline >= GetNumLines()) nline = fText->GetSize();
	else nline = GetLineOffset(nline) - 1;
	
	/*
	 *	And subtract the start of me
	 */
	
	return nline - GetLineOffset(line);
}

/************************************************************************/
/*																		*/
/*	Hit detect support													*/
/*																		*/
/************************************************************************/

/*	TEditCore::CharToLine
 *
 *		Convert from the character position to the line this character
 *	is located on.
 */

unsigned long TEditCore::CharToLine(unsigned long charpos)
{
	unsigned long len;
	unsigned long min,max,mid;
	unsigned long *n;
	unsigned long s,e;
	
	/*
	 *	Do a binary search
	 */
	
	n = (unsigned long *)*fStarts;
	min = 0;
	len = GetNumLines();
	max = len - 1;
	
	while (max > min+1) {
		mid = (min + max)/2;
		s = GetLineOffset(mid);
		e = GetLineOffset(mid+1);
		
		if (charpos < s) max = mid;
		else if (charpos >= e) min = mid;
		else return mid;
	}
	
	/*
	 *	When we get here, it's somewhere from min to max
	 */
	
	for (mid = min; mid < max; mid++) {
		e = GetLineOffset(mid+1);
		if (charpos < e) return mid;
	}
	return max;
}

/************************************************************************/
/*																		*/
/*	Selection support													*/
/*																		*/
/************************************************************************/

/*	TEditCore::GetSelect
 *
 *		Get the selection range
 */

void TEditCore::GetSelect(unsigned long *s, unsigned long *e)
{
	*s = fStart;
	*e = fEnd;
}

/*	TEditCore::SetSelect
 *
 *		Set the selection state
 */

void TEditCore::SetSelect(unsigned long s, unsigned long e)
{
	unsigned long tmp;
	
	if (s > e) {
		tmp = s;
		s = e;
		e = tmp;
	}
	
	if (s < 0) s = 0;
	if (e < 0) e = 0;
	if (s > fText->GetSize()) s = fText->GetSize();
	if (e > fText->GetSize()) e = fText->GetSize();
	
	fStart = s;
	fEnd = e;
}

/************************************************************************/
/*																		*/
/*	Keyboard manipulation support										*/
/*																		*/
/************************************************************************/

/*	TEditCore::InsertChar
 *
 *		Insert a single character.
 */

void TEditCore::InsertChar(char c)
{
	/*
	 *	Delete whatever was selected
	 */
	
	if (fStart != fEnd) Delete(fStart,fEnd-fStart);
	
	/*
	 *	Insert the character
	 */
	
	Insert(fStart,1,&c);
	fStart++;
	fEnd = fStart;
}

/*	TEditCore::DeleteChar
 *
 *		Delete a character. If there is a range selected, delete the
 *	range instead
 */

void TEditCore::DeleteChar()
{
	if (fStart != fEnd) {
		Delete(fStart,fEnd-fStart);
		fEnd = fStart;
	} else if (fStart > 0) {
		fStart--;
		Delete(fStart,1);
		fEnd = fStart;
	}
}

/************************************************************************/
/*																		*/
/*	Cut/Copy/Paste/Clear												*/
/*																		*/
/************************************************************************/

/*	TEditCore::CutText
 *
 *		Copy and clear
 */

void TEditCore::CutText(XGHandle &text)
{
	CopyText(text);
	ClearText();
}

/*	TEditCore::CopyText
 *
 *		Copy text
 */

void TEditCore::CopyText(XGHandle &text)
{
	text.SetSize(fEnd-fStart);
	memcpy((char *)*text,(char *)(**fText)+fStart,fEnd-fStart);
}

/*	TEditCore::PasteText
 *
 *		Paste text in the selection range
 */

void TEditCore::PasteText(XGHandle &text)
{
	if (fEnd != fStart) Delete(fStart,fEnd-fStart);
	
	XGHandleLock lock(text);
	Insert(fStart,text.GetSize(),(char *)*text);
	fEnd = fStart + text.GetSize();
	fStart = fEnd;
}

/*	TEditCore::ClearText
 *
 *		Clear the block of text
 */

void TEditCore::ClearText(void)
{
	if (fEnd != fStart) {
		Delete(fStart,fEnd-fStart);
		fEnd = fStart;
	}
}

/************************************************************************/
/*																		*/
/*	Insert/Delete core													*/
/*																		*/
/************************************************************************/

/*	TEditCore::Delete
 *
 *		Delete a block of text. This also adjusts the line starts
 *	array appropriately
 */

void TEditCore::Delete(unsigned long start, unsigned long len)
{
	unsigned long end;
	unsigned long llen;
	unsigned long i,p;
	unsigned long *n;
	
	if (len <= 0) return;
	
	/*
	 *	Adjust the line starts
	 */
	
	n = (unsigned long *)*fStarts;
	llen = fStarts.GetSize() / sizeof(long);
	
	/*
	 *	Find where the start of this thing is
	 */
	
	end = start + len;
	for (i = 0; i < llen; i++) {
		if (n[i] >= start) break;
	}
	
	/*
	 *	Find what to skip
	 */
	
	p = i;
	for (; i < llen; i++) {
		if (n[i] >= end) break;
	}
	
	/*
	 *	Shift the stuff down and resize the starts
	 */
	
	for (; i < llen; i++) {
		n[p++] = n[i] - len;
	}
	fStarts.SetSize(p * sizeof(long));
	
	/*
	 *	Now delete the data
	 */
	
	fText->Delete(start,len);
}

/*	TEditCore::Insert
 *
 *		Insert text block, updating the line starts at the same time
 */

void TEditCore::Insert(unsigned long start, unsigned long len, char *text)
{
	unsigned long lstart;
	unsigned long icount;
	unsigned long x;
	unsigned long pos;
	unsigned long llen;
	unsigned long *n;
	
	/*
	 *	Count the number of '\n' in the text
	 */
	
	icount = 0;
	for (x = 0; x < len; x++) if (text[x] == '\n') icount++;
	
	/*
	 *	Find the start where I have to insert data
	 */
	
	lstart = CharToLine(start);
	if (icount > 0) {
		fStarts.Insert(lstart*sizeof(long),icount*sizeof(long));
		
		n = (unsigned long *)*fStarts;
		pos = lstart;
		for (x = 0; x < len; x++) {
			if (text[x] == '\n') n[pos++] = x + start;
		}
	} else {
		n = (unsigned long *)*fStarts;
	}
	
	/*
	 *	Update the other line starts
	 */
	
	llen = fStarts.GetSize() / sizeof(long);
	for (x = lstart + icount; x < llen; x++) n[x] += len;
	
	/*
	 *	Insert text block into the text handle
	 */
	
	try {
		fText->Insert(start,len);
	}
	catch (...) {
		UpdateHandle();
		throw;						// out of memory; restore
	}
	
	memcpy((char *)**fText + start,text,len);
}
