/* This file handles text_block records in a Paige struct. */

#include "Paige.h"

#ifdef MAC_PLATFORM
#pragma segment pgtblocks
#endif

#include "machine.h"
#include "pgOSUtl.h"
#include "pgText.h"
#include "pgEdit.h"
#include "pgSelect.h"
#include "pgDefStl.h"
#include "defprocs.h"
#include "pgUtils.h"
#include "pgShapes.h"
#include "pgScript.h"
#include "pgTxtWid.h"
#include "pgtxr.h"

#ifdef PG_SUBSETS
#include "pgSubRef.h"

static void split_subref_list (text_block_ptr block1, text_block_ptr block2);

#endif


static void inval_selections (paige_rec_ptr pg);
static long find_breaking_char (paige_rec_ptr pg, text_block_ptr block,
		style_walk_ptr styles, long begin_offset, long byte_count,
		long desired_flags);


/* pgInitTextblock initializes a new text_block. Offset_begin is the
absolute offset beginning while the_text is the text (or NULL if none). */

PG_PASCAL (void) pgInitTextblock (paige_rec_ptr pg_rec, long offset_begin,
			text_ref the_text, text_block_ptr block, pg_boolean cache_text)
{
	point_start_ptr			starts;
	
	pgPushMemoryID(pg_rec);

	pgFillBlock(block, sizeof(text_block), 0);
	
	block->begin = block->end = offset_begin;
	
	if (the_text) {
		
		block->text = the_text;
		block->end += GetMemorySize(the_text);
	}
	else
	if (!cache_text)
		block->text = MemoryAlloc(pg_rec->globals->mem_globals, sizeof(pg_char), 0, 32);

	block->lines = MemoryAllocClear(pg_rec->globals->mem_globals,
			sizeof(point_start), 2, 4);
	
	if (block->text)
		SetMemoryPurge(block->text, TEXT_PURGE_STATUS, FALSE);

	SetMemoryPurge(block->lines, LINE_PURGE_STATUS, FALSE);

#ifdef PG_SUBSETS
	block->subref_list = MemoryAlloc(pg_rec->globals->mem_globals, sizeof(memory_ref), 0, 8);
#endif

	starts = UseMemory(block->lines);
	starts->flags = NEW_LINE_BIT | LINE_BREAK_BIT;
	++starts;
	starts->flags = TERMINATOR_BITS;

	UnuseMemory(block->lines);

	block->flags = (NEEDS_CALC | NEEDS_PARNUMS);
	
	pgPopMemoryID(pg_rec);
}


/* DEFAULT FUNCTION TO DO "TEXT LOAD" FOR A BLOCK. This does not do anything
by default. Its purpose is future features of text paging from disk.  */

PG_PASCAL (void) pgTextLoadProc (paige_rec_ptr pg, text_block_ptr block)
{
	if (pg->cache_read_proc && pg->cache_file && !block->text) {
		pg_globals_ptr		globals;
		pg_char_ptr			text;
		long				file_position, data_size, byte_size;

		globals = pg->globals;
		data_size = byte_size = (block->end - block->begin);
		file_position = block->cache_begin;

		block->text = MemoryAllocID(globals->mem_globals, sizeof(pg_char), data_size, 32, pg->mem_id);		
		byte_size = data_size;

#ifdef UNICODE

		text = UseMemory(block->text);

		if (block->cache_flags & CACHE_UNICODE_FLAG) {
			
			data_size += 1;		// Add one for BOM prefix
			byte_size = data_size * sizeof(pg_char);
		}

		pg->cache_read_proc((void PG_FAR *)text, io_data_direct, &file_position, &byte_size, pg->cache_file);

		if (block->cache_flags & CACHE_UNICODE_FLAG)
			data_size = pgUnicodeToUnicode((pg_short_t PG_FAR *)text, data_size, FALSE);
		else {

			data_size = pgBytesToUnicode((pg_bits8_ptr)text, data_size, FALSE);

			if (pg->cache_char_table)
				pgMapCharacters(pg->globals, text, data_size, pg->cache_char_table);

		}
#else

		if (block->cache_flags & CACHE_UNICODE_FLAG) {

			data_size += 1;
			byte_size = data_size * sizeof(pg_char);
			SetMemorySize(block->text, byte_size);
		}

		text = UseMemory(block->text);
		pg->cache_read_proc((void PG_FAR *)text, io_data_direct, &file_position, &byte_size, pg->cache_file);

		if (block->cache_flags & CACHE_UNICODE_FLAG) {
			
			data_size = pgUnicodeToUnicode((pg_short_t PG_FAR *)text, data_size, FALSE);
			data_size = pgUnicodeToBytes((pg_short_t PG_FAR *)text, data_size);
		}
		else
		if (pg->cache_char_table)
			pgMapCharacters(pg->globals, text, data_size, pg->cache_char_table);
#endif

		UnuseMemory(block->text);
		
		SetMemorySize(block->text, data_size);
	}
}


/* pgTextBreakProc must find the best place to break a text block (it is too
large per pg_globals->max_block). The default looks for a CR first, then if not
found looks for the alternate break character, then if not found makes its
best guess as to where to break. */

PG_PASCAL (long) pgTextBreakProc (paige_rec_ptr pg, text_block_ptr block)
{
	style_walk				walker;
	long					text_size, quarter_size, eighth_size;
	long					result;

	pg->procs.load_proc(pg, block);
	pgPrepareStyleWalk(pg, block->begin, &walker, FALSE);

	text_size = block->end - block->begin;
	quarter_size = text_size / 4;
	eighth_size = quarter_size / 2;

	if (!(result = find_breaking_char(pg, block, &walker, quarter_size + eighth_size,
			(quarter_size * 2 - eighth_size), PAR_SEL_BIT))) {

		if (!(result = find_breaking_char(pg, block, &walker, quarter_size,
				quarter_size * 2, PAR_SEL_BIT))) {
			
			if (!(result = find_breaking_char(pg, block, &walker, quarter_size + eighth_size,
					(quarter_size * 2) - eighth_size, WORD_BREAK_BIT))) {
				pg_char_ptr			text;
				long				initial_result, max_offset;
				
				result = text_size / 2;
				pgSetWalkStyle(&walker, block->begin + result);
				text = UseMemory(block->text);
				initial_result = result;
				max_offset = block->end - block->begin;

				while (result < text_size) {
					
					if (!(walker.cur_style->procs.char_info(pg, &walker, text, block->begin,
							initial_result, max_offset, result, LAST_HALF_BIT | MIDDLE_CHAR_BIT)))
						break;
					
					pgWalkStyle(&walker, 1);
					result += 1;
				}
				
				UnuseMemory(block->text);
				
				if (result == text_size)
					result = 0;
			}			
		}
	}

	pgPrepareStyleWalk(pg, 0, NULL, FALSE);

	return	result;
}



/* pgRectFromSelection fills in a rectangle that encloses the line which
corresponds to a selection point. The function returns the absolute location
of the "caret" point.  The block must be the correct block that contains the
point. Note that line_rect can be NULL if you just want the caret point.	*/

PG_PASCAL (long) pgRectFromSelection (paige_rec_ptr pg, text_block_ptr block,
		t_select_ptr selection, rectangle_ptr line_rect, short PG_FAR *baseline)
{
	register point_start_ptr	first_start, alternate_start;
	long						caret_point;
	pg_short_t					begin_start, num_starts;

	alternate_start = first_start = UseMemory(block->lines);
	alternate_start += selection->line;

	if (baseline)
		*baseline = alternate_start->baseline;

	caret_point = alternate_start->bounds.top_left.h + (long) selection->primary_caret;
	
	if (line_rect) {
	
		begin_start = selection->line;
		num_starts = pgLineBoundary(pg, block, &begin_start, NULL, NULL) - 1;

		first_start += begin_start;
		alternate_start = first_start + num_starts;
		
		line_rect->top_left = first_start->bounds.top_left;
		line_rect->bot_right = alternate_start->bounds.bot_right;
	}
	
	UnuseMemory(block->lines);
	
	return	caret_point;
}


/* pgNumTextblocks is intended for apps that want to examine blocks. This
one returns the total number of text blocks in pg.  */

PG_PASCAL (long) pgNumTextblocks (pg_ref pg)
{
	paige_rec_ptr			pg_rec;
	long					result;
	
	pg_rec = UseMemory(pg);
	result = GetMemorySize(pg_rec->t_blocks);
	UnuseMemory(pg);
	
	return	result;
}


/* pgGetTextblock is intended for apps that want to examine blocks. This
one returns a text_block record that owns the specified offset.  The function
result is the record number.  If want_pagination is TRUE the block's lines are
re-calculated if necessary.  */

PG_PASCAL (long) pgGetTextblock (pg_ref pg, long offset, text_block_ptr block,
		pg_boolean want_pagination)
{
	paige_rec_ptr			pg_rec;
	text_block_ptr			the_block;
	long					wanted_offset;
	pg_short_t				rec_num_result;

	pg_rec = UseMemory(pg);
	
	wanted_offset = pgFixOffset(pg_rec, offset);
	the_block = pgFindTextBlock(pg_rec, wanted_offset, &rec_num_result,
			want_pagination, want_pagination);
	pgBlockMove(the_block, block, sizeof(text_block));
	UnuseMemory(pg_rec->t_blocks);

	UnuseMemory(pg);
	
	return	rec_num_result;
}



/* pgFindTextBlock returns a text_block_ptr of the block that owns "offset."
if block_num is non-NULL, the block number is returned (0 to n).
If want_build is TRUE, the block is recalculated if necessary. The will_access_text
param is passed to  (if called).  */

PG_PASCAL (text_block_ptr) pgFindTextBlock (paige_rec_ptr pg_rec, long offset,
			pg_short_t PG_FAR *block_num, pg_boolean want_build,
			pg_boolean will_access_text)
{
	register text_block_ptr		block;
	register long				wanted_offset;
	pg_short_t					num_blocks, block_ctr;

	num_blocks = (pg_short_t)GetMemorySize(pg_rec->t_blocks) - 1;
	block = UseMemory(pg_rec->t_blocks);
	
	wanted_offset = offset;
	block_ctr = 0;

	while (num_blocks) {
		
		if (block->end > wanted_offset)
			break;
		
		++block;
		++block_ctr;
		--num_blocks;
	}

	if (will_access_text)
		pg_rec->procs.load_proc(pg_rec, block);

	if (want_build)
		pgPaginateBlock(pg_rec, block, NULL, will_access_text);

	if (block_num)
		*block_num = block_ctr;

	return block;
}



/* pgFixBadBlocks walks through all the text blocks and deletes blocks that are
completely empty, or splits blocks that are too large.  */

PG_PASCAL (short) pgFixBadBlocks (paige_rec_ptr pg)
{
	register text_block_ptr	block;
	register pg_short_t		num_blocks, block_ctr;
	pg_short_t				charloc_index;
	long					block_size, max_block_size;
	short					some_fixed;

	max_block_size = pg->globals->max_block_size;
	block = UseMemory(pg->t_blocks);
	num_blocks = (pg_short_t)GetMemorySize(pg->t_blocks);
	block_ctr = 0;
	some_fixed = FALSE;

	while (num_blocks) {
		
		block_size = block->end - block->begin;

		if (some_fixed)
			block->flags |= NEEDS_PAGINATE;

		if (((block->flags & BROKE_BLOCK) && (num_blocks > 1)) || (!(block_size))) {

			inval_selections(pg);
			
			if (block_size) {	/* Block has no terminator, merge with next */
				text_ref		src_text_ref;
				pg_char_ptr		inserted_text;
				rectangle		old_bounds;
				long			old_block_begin, old_text_size;
				
				some_fixed = TRUE;
				pg->procs.load_proc(pg, block);
				block->cache_flags |= CACHE_CHANGED_FLAG;
				src_text_ref = block->text;
				old_block_begin = block->begin;
				old_text_size = block->end - block->begin;

				pgBlockMove(&block->bounds, &old_bounds, sizeof(rectangle));

				++block;
				pg->procs.load_proc(pg, block);
				block->cache_flags |= CACHE_CHANGED_FLAG;
				block->begin = old_block_begin;
				pgTrueUnionRect(&old_bounds, &block->bounds, &block->bounds);
				
				inserted_text = InsertMemory(block->text, 0, old_text_size);
				pgBlockMove(UseMemory(src_text_ref), inserted_text, old_text_size * sizeof(pg_char));
				UnuseMemory(block->text);
				UnuseMemory(src_text_ref);
				
#ifdef PG_SUBSETS
			{
				memory_ref			src_subrefs;
				pg_subref_ptr		subref_list;
				long				num_subs;
				
				--block;
				src_subrefs = block->subref_list;
				++block;
				
				if ((num_subs = GetMemorySize(src_subrefs)) > 0) {
					
					subref_list = InsertMemory(block->subref_list, 0, num_subs);
					pgBlockMove(UseMemory(src_subrefs), subref_list, num_subs * sizeof(pg_subref));
					UnuseMemory(src_subrefs);
					SetMemorySize(src_subrefs, 0);
				}
			}
#endif
				block->flags = (NEEDS_CALC | NEEDS_PARNUMS);
				
				if (pgFindCharLocs(pg, block, &charloc_index, NULL, TRUE, FALSE))
					pgFreeCharLocs(pg, charloc_index);
				
				--block;
			}
			
			if (block->begin || (num_blocks > 1)) {

				some_fixed = TRUE;

				if (pgFindCharLocs(pg, block, &charloc_index, NULL, TRUE, FALSE))
					pgFreeCharLocs(pg, charloc_index);

				DisposeMemory(block->lines);
				DisposeNonNilMemory(block->text);

				if (block->subref_list)
					DisposeMemory(block->subref_list);

				UnuseMemory(pg->t_blocks);
				DeleteMemory(pg->t_blocks, block_ctr, 1);

				block = UseMemory(pg->t_blocks);
				block += block_ctr;
				
				if (num_blocks == 1)
					break;
				
				block->flags = (NEEDS_CALC | NEEDS_PARNUMS);
			}
		}
		else {

			if (block_size > max_block_size) {
				text_ref		new_text_ref;
				pg_char_ptr		src_text;
				long			old_bounds_bottom;
				long			split_offset, new_text_size, old_text_size;
				
				pg->procs.load_proc(pg, block);
				block->cache_flags |= CACHE_CHANGED_FLAG;

				if (split_offset = pg->procs.break_proc(pg, block)) {

					pgPushMemoryID(pg);
	
					inval_selections(pg);
					some_fixed = TRUE;
					
					split_offset += block->begin;
					new_text_size = block->end - split_offset;
					block->end = split_offset;
					old_text_size = (block->end - block->begin);
					
					new_text_ref = MemoryAlloc(pg->globals->mem_globals, sizeof(pg_char), new_text_size, 32);
					
					pg->procs.load_proc(pg, block);
	
					src_text = UseMemoryRecord(block->text, old_text_size, new_text_size - 1, TRUE);
					pgBlockMove(src_text, UseMemory(new_text_ref), new_text_size * sizeof(pg_char));
					UnuseMemory(block->text);
					UnuseMemory(new_text_ref);
					SetMemorySize(block->text, old_text_size);
					
					block->flags = (NEEDS_CALC | NEEDS_PARNUMS);
					old_bounds_bottom = block->bounds.bot_right.v;
					++block_ctr;
					
					block = InsertMemory(pg->t_blocks, block_ctr, 1);
					
					pgInitTextblock(pg, split_offset, new_text_ref, block, FALSE);
					block->bounds.top_left.v = block->bounds.bot_right.v = old_bounds_bottom;
#ifdef PG_SUBSETS
					--block;
					split_subref_list(block, &block[1]);
					++block;
#endif
					pgPopMemoryID(pg);
				}
			}
			
			++block;
			++block_ctr;
		}
		
		--num_blocks;
	}

	UnuseMemory(pg->t_blocks);
	
	pg->last_key_check = 0;
	
	if (some_fixed)
		pg->t_blocks = ForceMinimumMemorySize(pg->t_blocks);
	
	return	some_fixed;
}




/* pgTextFromOffset returns a <<used>> pointer to the text at the specified offset.
The memory_ref for the text is returned in the_ref;  the maximum size of text --
which is the offset to end of text block -- is returned in max_length. The
max_length param can be NULL if you just want the pointer. */

PG_PASCAL (pg_char_ptr) pgTextFromOffset (paige_rec_ptr pg, long offset,
		text_ref PG_FAR *the_ref, long PG_FAR *max_length)
{
	register text_block_ptr		block;
	register pg_char_ptr		text_result;
	register long				local_offset;

	if (offset >= pg->t_length)
		return	NULL;

	block = pgFindTextBlock(pg, offset, NULL, FALSE, TRUE);
	*the_ref = block->text;
	
	if (max_length)
		*max_length = block->end - offset;

	local_offset = offset - block->begin;

	UnuseMemory(pg->t_blocks);
	
	text_result = UseMemory(*the_ref);
	text_result += local_offset;
	
	return	text_result;
}



/*********************************** Local Functions **********************************/


/* This function flags all selection points to require recalculating  */

static void inval_selections (paige_rec_ptr pg)
{
	register t_select_ptr		selections;
	register pg_short_t			num_selects;
	
	selections = UseMemory(pg->select);
	
	if (!(num_selects = pg->num_selects))
		selections->flags |= SELECTION_DIRTY;
	else {
		
		num_selects *= 2;

		while (num_selects) {
			
			selections->flags |= SELECTION_DIRTY;
			++selections;
			--num_selects;
		}
	}
	
	UnuseMemory(pg->select);
}


/* find_breaking_char scans the text in block beginning at begin_offset
for byte_count bytes. The scan occurs forward (1).
The desired_flags indicate the kind of break being searched, e.g. CR or
LF or word break, etc.  A non-zero response means the offset has been located. */

static long find_breaking_char (paige_rec_ptr pg, text_block_ptr block,
		style_walk_ptr styles, long begin_offset, long byte_count,
		long desired_flags)
{
	register style_walk_ptr		walker;
	pg_char_ptr					text;
	long						offset_ctr, remaining_bytes, max_offset, c_info;

	walker = styles;
	pgSetWalkStyle(walker, block->begin + begin_offset);
	text = UseMemory(block->text);
	max_offset = block->end - block->begin;

	remaining_bytes = byte_count;
	offset_ctr = begin_offset;

	while (remaining_bytes) {

		if (c_info = walker->cur_style->procs.char_info(pg, walker, text, block->begin,
				begin_offset, max_offset, offset_ctr, desired_flags))
			break;
		
		pgWalkStyle(walker, 1);
		remaining_bytes -= 1;
		offset_ctr += 1;
	}
	
	while (remaining_bytes) {
		
		pgWalkStyle(walker, 1);
		remaining_bytes -= 1;
		offset_ctr += 1;
		
		if (!(walker->cur_style->procs.char_info(pg, walker, text, block->begin,
				begin_offset, max_offset, offset_ctr, LAST_HALF_BIT | MIDDLE_CHAR_BIT)))
			break;
	}

	UnuseMemory(block->text);
	
	if (remaining_bytes)
		return	offset_ctr;

	return	0;
}


#ifdef PG_SUBSETS
static void split_subref_list (text_block_ptr block1, text_block_ptr block2)
{
	register pg_char_ptr		text;
	pg_subref_ptr				subref_list;
	long						num_subs, split_refs, text_size, list1_size;
	
	if (num_subs = GetMemorySize(block1->subref_list)) {
		
		text_size = GetMemorySize(block2->text);
		text = UseMemory(block2->text);
		split_refs = 0;

		while (text_size) {
			
			if (*text++ == SUBREF_CHAR)
				split_refs += 1;
			
			--text_size;
		}
		
		UnuseMemory(block2->text);
		
		if (split_refs) {
			
			SetMemorySize(block2->subref_list, split_refs);
			subref_list = UseMemory(block1->subref_list);
			list1_size = num_subs - split_refs;
			pgBlockMove(&subref_list[list1_size], UseMemory(block2->subref_list),
						split_refs * sizeof(pg_subref));
			UnuseMemory(block2->subref_list);
			UnuseMemory(block1->subref_list);
			SetMemorySize(block1->subref_list, list1_size);
		}
	}
}

#endif
// End PG_SUBSETS
