/*	SCCS Id: @(#)mactty.c	3.1	93/03/01			*/
/* Copyright (c) Jon W{tte 1993.					*/
/* NetHack may be freely redistributed.  See license for details.	*/

/*
 * mactty.c
 *
 * This file contains the actual code for the tty library. For a 
 * description, see the file mactty.h, which contains all you
 * need to know to use the library.
 */

#include "mttypriv.h"

/* these declarations are here becaues I can't include macwin.h without including the world */
extern void dprintf(char * , ...);								/* dprintf.c */
extern pascal short tty_environment_changed(WindowPtr window);	/* local */

#if PRINTF_TTY
# include <stdio.h>
# include <stdarg.h>
#endif

static void select_onscreen_window ( tty_record * record ) ;
static void select_offscreen_port ( tty_record * record ) ;

#define MEMORY_MARGIN 30000


/*
 * Error code returned when it's probably our fault, or
 * bad parameters.
 */
#define general_failure 1

/*
 * How long lines do we support for input?
 */
#define IB_LIMIT 80

/*
 * Convenience macro for most functions - put last in declaration
 */
#define RECORD(record) \
tty_record * record ; \
	if ( ! window ) { \
		dprintf ( "*** null Window ***" ) ; \
		return general_failure ; \
	} \
	record = ( tty_record * ) GetWRefCon ( window ) ; \
	if ( ! record ) { \
		return general_failure ; \
	}

/*
 * Simple macro for deciding wether we draw at once or delay
 */
#define DRAW_DIRECT ( 0L != ( TA_ALWAYS_REFRESH & record -> \
	attribute [ TTY_ATTRIB_FLAGS ] ) )


/*
 * Module variable used as return value for various calls.
 */
static short s_err = 0 ;

/*
 * Table of special characters. Zero is ALWAYS special; it means
 * end of string and would be MISSED if it was not included here.
 */
static const unsigned char s_cooked_controls [ ] = {
	1,	0,	0,	0,	0,	0,	0,	1,	1,	0,	1,	0,	0,	1,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
} ;

static const unsigned char s_raw_controls [ ] = {
	1,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,	0,
} ;

static const unsigned char * s_control = s_cooked_controls ;


/*
 * Memory-related error
 */
static short
mem_err ( void ) {
	if ( MemError ( ) ) {
		return MemError ( ) ;
	}
	return general_failure ;
}


/*
 * Make a rectangle empty
 */
static void
empty_rect ( Rect * r ) {

	r -> right = -20000 ;
	r -> left = 20000 ;
	r -> top = 20000 ;
	r -> bottom = -20000 ;
}


/*
 * Union twp rect together
 */
static void
union_rect ( Rect * r1 , Rect * r2 , Rect * dest ) {

	if ( r1 -> left < r2 -> left ) {
		dest -> left = r1 -> left ;
	} else {
		dest -> left = r2 -> left ;
	}

	if ( r1 -> top < r2 -> top ) {
		dest -> top = r1 -> top ;
	} else {
		dest -> top = r2 -> top ;
	}

	if ( r1 -> bottom > r2 -> bottom ) {
		dest -> bottom = r1 -> bottom ;
	} else {
		dest -> bottom = r2 -> bottom ;
	}

	if ( r1 -> right > r2 -> right ) {
		dest -> right = r1 -> right ;
	} else {
		dest -> right = r2 -> right ;
	}
}


/*
 * Dispose a pointer using the set memory-allocator
 */
static short
dispose_ptr ( tty_record * record , void * ptr ) {
pascal short ( * func ) ( WindowPtr window , void * ptr ) =
	( pascal short ( * ) ( WindowPtr , void * ) )
	record -> attribute [ TTY_FREE_MEMORY_FUNCTION ] ;

	if ( ! ptr ) {
		return noErr ; /* Silently accept disposing nulls */
	}
	if ( func ) {
		s_err = ( * func ) ( record -> its_window , ptr ) ;
	} else {
		DisposePtr ( ptr ) ;
		s_err = MemError ( ) ;
	}
	return s_err ;
}


/*
 * Allocate a pointer using the set memory-allocator
 */
static short
alloc_ptr ( tty_record * record , void * * ptr , long size ) {
pascal short ( * func ) ( WindowPtr window , void * * ptr , long ) =
	( pascal short ( * ) ( WindowPtr , void * * , long ) )
	record -> attribute [ TTY_ALLOCATE_MEMORY_FUNCTION ] ;

	if ( func ) {
		s_err = ( * func ) ( record -> its_window , ptr , size ) ;
	} else {
		* ptr = NewPtr ( size ) ;
		s_err = * ptr ? noErr : mem_err ( ) ;
	}
	return s_err ;
}


/*
 * Set up a GWorld in the record
 */
static short
allocate_offscreen_world ( tty_record * record ) {
GWorldPtr gw = (GWorldPtr)0 ;
GWorldFlags world_flags = 0 ;
long mem_here , mem_there , other , required_mem ;
Point p = { 0 , 0 } ;
Rect r_screen ;
GDHandle gdh ;

	select_onscreen_window ( record ) ;
	LocalToGlobal ( & p ) ;
	r_screen = record -> its_bits . bounds ;
	OffsetRect ( & r_screen , p . h , p . v ) ;

	gdh = GetMaxDevice ( & r_screen ) ;
	required_mem = ( long ) ( * ( ( * gdh ) -> gdPMap ) ) -> pixelSize *
		( ( long ) record -> its_bits . bounds . right *
		record -> its_bits . bounds . bottom ) >> 3 ;

	PurgeSpace ( & other , & mem_here ) ;
	if ( other < mem_here + MEMORY_MARGIN ) {
		mem_here = other - MEMORY_MARGIN ;
	}
	dprintf ( "Heap %ld Required %ld" , mem_here , required_mem ) ;
	if ( required_mem > mem_here ) {
		mem_there = required_mem ;
		if ( required_mem > MFMaxMem ( & mem_there ) ) {
			dprintf ( "No memory" ) ;
			return memFullErr ;
		}
		world_flags |= useTempMem ;
	}
	s_err = NewGWorld ( & gw , 0 , & r_screen , (CTabHandle) 0 , (GDHandle) 0 , world_flags ) ;
	if ( ! s_err ) {
		record -> offscreen_world = gw ;
		select_offscreen_port ( record ) ;
		SetOrigin ( 0 , 0 ) ;
		select_onscreen_window ( record ) ;
		dprintf ( "New GWorld @ %lx;dm %lx CGrafPtr" , gw , gw ) ;
	}
	return s_err ;
}


/*
 * Done with GWorld, release data
 */
static short
deallocate_gworld ( tty_record * record ) {
	if ( record -> offscreen_world ) {
		DisposeGWorld ( record -> offscreen_world ) ;
		record -> offscreen_world = (GWorldPtr) 0 ;
	}
	return noErr ;
}


/*
 * Save the current port/world in a safe place for later retrieval
 */
static void
save_port ( tty_record * record , void * save ) {
GWorldPtr gw ;
GDHandle gh ;
GrafPtr gp ;

	if ( record -> uses_gworld ) {
		GetGWorld ( & gw , & gh ) ;
		* ( GWorldPtr * ) save = gw ;
	} else {
		GetPort ( & gp ) ;
		* ( GrafPtr * ) save = gp ;
	}
}


/*
 * Restore current port/world after a save
 */
static void
use_port ( tty_record * record , void * port ) {
	if ( record -> uses_gworld ) {
	PixMapHandle pix_map ;

		SetGWorld ( ( GWorldPtr ) port , (GDHandle) 0 ) ;
		if ( port == record -> offscreen_world ) {
			pix_map = GetGWorldPixMap ( record -> offscreen_world ) ;
			if ( pix_map ) {
				LockPixels ( pix_map ) ;
			}
		} else {
			pix_map = GetGWorldPixMap ( record -> offscreen_world ) ;
			if ( pix_map ) {
				UnlockPixels ( pix_map ) ;
			}
		}
	} else {
		SetPort ( ( GrafPtr ) port ) ;
	}
}


/*
 * Use offscreen drawing - lock the pixels through use_port
 */
static void
select_offscreen_port ( tty_record * record ) {
	if ( record -> uses_gworld ) {
		use_port ( record , record -> offscreen_world ) ;
	} else {
		use_port ( record , record -> offscreen_port ) ;
	}
}


/*
 * Use the window - unlock pixels
 */
static void
select_onscreen_window ( tty_record * record ) {
	if ( record -> uses_gworld ) {
		use_port ( record , record -> its_window_world ) ;
		SetPort ( record -> its_window ) ;
	} else {
		use_port ( record , record -> its_window ) ;
	}
}


/*
 * Do bits copy depending on if we're using color or not
 */
static void
copy_bits ( tty_record * record , Rect * bounds , short xfer_mode , RgnHandle mask_rgn ) {
RGBColor old_fore , old_back ;
RGBColor rgb_black = { 0 , 0 , 0 } ;
RGBColor rgb_white = { 0xffff , 0xffff , 0xffff } ;

	if ( record -> uses_gworld ) {
	GWorldFlags pix_state = GetPixelsState ( GetGWorldPixMap ( record -> offscreen_world ) ) ;

		LockPixels ( GetGWorldPixMap ( record -> offscreen_world ) ) ;
		GetForeColor ( & old_fore ) ;
		GetBackColor ( & old_back ) ;
		RGBForeColor ( & rgb_black ) ;
		RGBBackColor ( & rgb_white ) ;
		CopyBits ( ( BitMap * ) & ( record -> offscreen_world -> portPixMap ) ,
			& ( record -> its_window -> portBits ) , bounds , bounds , xfer_mode ,
			mask_rgn ) ;
		RGBForeColor ( & old_fore ) ;
		RGBBackColor ( & old_back ) ;
		SetPixelsState ( GetGWorldPixMap ( record -> offscreen_world ) , pix_state ) ;
	} else {
		CopyBits ( & ( record -> its_bits ) , & ( record -> its_window -> portBits ) ,
			bounds , bounds , xfer_mode , mask_rgn ) ;
	}
}


/*
 * Fill an area with the background color
 */
static void
erase_rect ( tty_record * record , Rect * area ) {
#if defined(applec)
# pragma unused(record)
#endif

	EraseRect ( area ) ;
}


/*
 * Get rid of offscreen bitmap
 */
static short
free_bits ( tty_record * record ) {
	if ( record -> uses_gworld ) {
		s_err = deallocate_gworld ( record ) ;
	} else {
		if ( record -> offscreen_port ) {
			ClosePort ( record -> offscreen_port ) ;
			s_err = dispose_ptr ( record , record -> offscreen_port ) ;
			if ( ! s_err ) {
				record -> offscreen_port = (GrafPtr) 0 ;
			} else {
				return s_err ;
			}
		}
		s_err = dispose_ptr ( record , record -> its_bits . baseAddr ) ;
		if ( ! s_err ) {
			record -> its_bits . baseAddr = (char *)0 ;
		}
	}
	return s_err ;
}


/*
 * Snatch a window from the resource fork. Create the record.
 * Otherwise, do nothing.
 */
pascal short
create_tty ( WindowPtr * window , short resource_id , Boolean in_color ) {
tty_record * record ;
Boolean was_allocated = !! * window ;

	if ( in_color ) {
		* window = GetNewCWindow ( resource_id , ( Ptr ) * window , ( WindowPtr ) -1L ) ;
	} else {
		* window = GetNewWindow ( resource_id , ( Ptr ) * window , ( WindowPtr ) -1L ) ;
	}
	if ( ! * window ) {
		return mem_err ( ) ;
	}

	record = ( tty_record * ) NewPtrClear ( sizeof ( tty_record ) ) ;
	if ( ! record ) {
		if ( was_allocated ) {
			CloseWindow ( * window ) ;
		} else {
			DisposeWindow ( * window ) ;
		}
		return mem_err ( ) ;
	}
	record -> its_window = * window ;
	SetWRefCon ( * window , ( long ) record ) ;
	record -> was_allocated = was_allocated ;
	record -> its_bits . baseAddr = (char *)0 ;
#if TTY_INPUT
	record -> input_buffer = (char *)0 ;
#endif

/*
 * We need to keep the window world around if we switch worlds
 */
	record -> offscreen_world = (GWorldPtr) 0 ;
	record -> uses_gworld = in_color ;
	if ( in_color ) {
	GDHandle gh ;

		SetPort ( * window ) ;
		GetGWorld ( & ( record -> its_window_world ) , & gh ) ;
	} else {
		record -> its_window_world = (GWorldPtr)0 ;
	}

#if CLIP_RECT_ONLY
	empty_rect ( & ( record -> invalid_rect ) ) ;
#else
	record -> invalid_part = NewRgn ( ) ;
	if ( ! record -> invalid_part ) {
	short err = mem_err ( ) ;

		err = destroy_tty ( * window ) ;
		return err ;
	}
#endif

	return noErr ;
}


/*
 * Initialize the struct so it actually works as a tty.
 */
pascal short
init_tty_name ( WindowPtr window , unsigned char * font_name , short font_size ,
	short x_size , short y_size ) {
short font_num = 0 ;

	GetFNum ( font_name , & font_num ) ;
	if ( ! font_num ) {
		return general_failure ;
	}
	return init_tty_number ( window , font_num , font_size , x_size , y_size ) ;
}


pascal short
init_tty_number ( WindowPtr window , short font_number , short font_size ,
	short x_size , short y_size ) {
RECORD ( record ) ;

	record -> font_number = font_number ;
	record -> font_size = font_size ;
	record -> x_size = x_size ;
	record -> y_size = y_size ;

	record -> offscreen_port = (GrafPtr) 0 ;
	record -> attribute [ TTY_ATTRIB_BACKGROUND ] = 0xffffff ; /* White */

#if TTY_INPUT
	record -> input_buffer_len = 0 ;
	record -> input_buffer_limit = IB_LIMIT ;
	s_err = alloc_ptr ( record , & ( record -> input_buffer ) , IB_LIMIT ) ;
	if ( s_err ) {
		return s_err ;
	}
#endif

	return force_tty_coordinate_system_recalc ( window ) ;
}


/*
 * Done with a window - destroy it. Release the memory only if
 * it wasn't allocated when we got it!
 */
pascal short
destroy_tty ( WindowPtr window ) {
Boolean close_flag ;
RECORD ( record ) ;

	s_err = free_bits ( record ) ;
#if TTY_INPUT
	if ( ! s_err ) {
		s_err = dispose_ptr ( record -> input_buffer ) ;
		if ( ! s_err ) {
			record -> input_buffer = (char *)0 ;
		}
	}
#endif
	if ( ! s_err ) {
		close_flag = record -> was_allocated ;
		DisposePtr ( ( Ptr ) record ) ;
		s_err = MemError ( ) ;
		if ( close_flag ) {
			CloseWindow ( window ) ;
		} else {
			DisposeWindow ( window ) ;
		}
	}
	
	return s_err ;
}


/*
 * Use a new font for drawing.
 */
pascal short
set_tty_font_name ( WindowPtr window , unsigned char * font_name ) {
RECORD ( record ) ;

	record -> font_number = 0 ;
	GetFNum ( font_name , & ( record -> font_number ) ) ;

	return ! record -> font_number ;
}


pascal short
set_tty_font_number ( WindowPtr window , short font_number ) {
RECORD ( record ) ;

	record -> font_number = font_number ;

	return noErr ;
}


pascal short
set_tty_font_size ( WindowPtr window , short font_size ) {
RECORD ( record ) ;

	record -> font_size = font_size ;

	return noErr ;
}


static void
do_set_port_font ( tty_record * record ) {

	PenNormal ( ) ;
	TextFont ( record -> font_number ) ;
	TextFace ( 0 ) ;
	TextSize ( record -> font_size ) ;
	if ( 0L != ( record -> attribute [ TTY_ATTRIB_FLAGS ] & TA_OVERSTRIKE ) ) {
		TextMode ( srcOr ) ;
	} else {
		TextMode ( srcCopy ) ;
	}
}


/*
 * Fill in some fields from some other fields that may have changed
 */
static void
calc_font_sizes ( tty_record * record ) {
FontInfo font_info ;

	do_set_port_font ( record ) ;

	GetFontInfo ( & font_info ) ;
	record -> char_width = font_info . widMax ;
	record -> ascent_height = font_info . ascent + font_info . leading ;
	record -> row_height = record -> ascent_height + font_info . descent ;
}


/*
 * Allocate memory for the bitmap holding the tty window
 */
static short
alloc_bits ( tty_record * record ) {
void * old_port ;

	save_port ( record , & old_port ) ;
	SetRect ( & record -> its_bits . bounds , 0 , 0 ,
		record -> char_width * record -> x_size ,
		record -> row_height * record -> y_size ) ;

/*
 * Clear two highest and lowest bit - not a color pixMap, and even in size
 */
	record -> its_bits . rowBytes = ( ( record -> its_bits . bounds . right + 15 )
		>> 3 ) & 0x1ffe ;

	if ( record -> uses_gworld ) {
		s_err = allocate_offscreen_world ( record ) ;
	} else {
		s_err = alloc_ptr ( record , ( void * * ) & ( record -> its_bits . baseAddr ) ,
			record -> its_bits . rowBytes * record -> its_bits . bounds . bottom ) ;
		if ( ! s_err ) {
			s_err = alloc_ptr ( record , ( void * * ) & ( record -> offscreen_port ) ,
				sizeof ( GrafPort ) ) ;
		}
		if ( ! s_err ) {
			OpenPort ( record -> offscreen_port ) ;
			SetPort ( record -> offscreen_port ) ;
			ClipRect ( & ( record -> its_bits . bounds ) ) ;
			SetPortBits ( & ( record -> its_bits ) ) ;
		}
	}
	use_port ( record , old_port ) ;

	return s_err ;
}


static void
update_offscreen_info ( tty_record * record ) {

	select_offscreen_port ( record ) ;
	do_set_port_font ( record ) ;
	select_onscreen_window ( record ) ;
}


/*
 * Recalculate the window based on new size, font, extent values,
 * and re-allocate the bitmap.
 */
pascal short
force_tty_coordinate_system_recalc ( WindowPtr window ) {
RECORD ( record ) ;

	if ( s_err = free_bits ( record ) ) {
		return s_err ;
	}
	select_onscreen_window ( record ) ;
	calc_font_sizes ( record ) ;

	if ( s_err = alloc_bits ( record ) ) {
/*
 * Catastrophe! We could not allocate memory for the bitmap! Things may go very
 * much downhill from here!
 */
 		dprintf ( "alloc_bits returned null in force_tty_coordinate_system_recalc!" ) ;
		return s_err ;
	}

	update_offscreen_info ( record ) ;
	return clear_tty ( window ) ;
}


/*
 * Update TTY according to new color environment for the window
 */
pascal short
tty_environment_changed ( WindowPtr window ) {
Point p = { 0 , 0 } ;
Rect r_screen ;
RECORD ( record ) ;

	if ( record -> uses_gworld ) {
		select_onscreen_window ( record ) ;
		r_screen = record -> its_bits . bounds ;
		LocalToGlobal ( & p )  ;
		OffsetRect ( & r_screen , p . h , p . v ) ;
		UpdateGWorld ( & ( record -> offscreen_world ) , 0 , & r_screen ,
			(CTabHandle) 0 , (GDHandle) 0 , stretchPix ) ;
		select_offscreen_port ( record ) ;
		SetOrigin ( 0 , 0 ) ;
		select_onscreen_window ( record ) ;
	}
	return s_err ;
}


/*
 * Read a lot of interesting and useful information from the current tty
 */
pascal short
get_tty_metrics ( WindowPtr window , short * x_size , short * y_size ,
	short * x_size_pixels , short * y_size_pixels , short * font_number ,
	short * font_size , short * char_width , short * row_height ) {
RECORD ( record ) ;

/*
 * First, test that we actually have something to draw to...
 */
	if ( ( ( (char *)0 == record -> its_bits . baseAddr ) && ! record -> uses_gworld ) ||
		( ( (GWorldPtr)0 == record -> offscreen_world ) && record -> uses_gworld ) ) {
		return general_failure ;
	}

	* x_size = record -> x_size ;
	* y_size = record -> y_size ;
	* x_size_pixels = record -> its_bits . bounds . right ;
	* y_size_pixels = record -> its_bits . bounds . bottom ;
	* font_number = record -> font_number ;
	* font_size = record -> font_size ;
	* char_width = record -> char_width ;
	* row_height = record -> row_height ;

	return noErr ;
}


/*
 * Map a position on the map to screen coordinates
 */
static void
pos_rect ( tty_record * record , Rect * r , short x_pos , short y_pos ,
	short x_end , short y_end ) {
 
	SetRect ( r , x_pos * ( record -> char_width ) , y_pos * ( record -> row_height ) ,
		( 1 + x_end ) * ( record -> char_width ) , ( 1 + y_end ) *
		( record -> row_height ) ) ;
}


static void
accumulate_rect ( tty_record * record , Rect * rect ) {
#if CLIP_RECT_ONLY
	union_rect ( rect , & ( record -> invalid_rect ) , & ( record -> invalid_rect ) ) ;
#else
RgnHandle rh = NewRgn ( ) ;

	RectRgn ( rh , rect ) ;
	UnionRgn ( record -> invalid_part , rh , record -> invalid_part ) ;
	DisposeRgn ( rh ) ;
#endif
}


/*
 * Invert the specified position
 */
static void
curs_pos ( tty_record * record , short x_pos , short y_pos , short to_state ) {
Rect r ;

	if ( record -> curs_state == to_state ) {
		return ;
	}
	record -> curs_state = to_state ;
	pos_rect ( record , & r , x_pos , y_pos , x_pos , y_pos ) ;

	if ( DRAW_DIRECT ) {
	void * old_port ;

		save_port ( record , & old_port ) ;
		select_onscreen_window ( record ) ;
		InvertRect ( & r ) ;
		use_port ( record , old_port ) ;
	} else {
		accumulate_rect ( record , & r ) ;
	}
}


/*
 * Move the cursor (both as displayed and where drawing goes)
 * HOWEVER: The cursor is NOT stored in the bitmap!
 */
pascal short
move_tty_cursor ( WindowPtr window , short x_pos , short y_pos ) {
RECORD ( record ) ;

	select_onscreen_window ( record ) ;
	if ( record -> x_curs == x_pos && record -> y_curs == y_pos ) {
		return noErr ;
	}
	if ( record -> x_size <= x_pos || x_pos < 0 ||
		record -> y_size <= y_pos || y_pos < 0 ) {
		return general_failure ;
	}
	curs_pos ( record , record -> x_curs , record -> y_curs , 0 ) ;
	record -> x_curs = x_pos ;
	record -> y_curs = y_pos ;
	curs_pos ( record , x_pos , y_pos , 1 ) ;

	return noErr ;
}


/*
 * Get the current cursor position. Note that the cursor may not be
 * displayed there yet; it depends on wether you've called update_tty()
 * or have the window in TA_ALWAYS_REFRESH mode.
 */
pascal short
get_tty_cursor ( WindowPtr window , short * x_pos , short * y_pos ) {
RECORD ( record ) ;

	* x_pos = record -> x_curs ;
	* y_pos = record -> y_curs ;

	return noErr ;
}


/*
 * Update the screen to match the current bitmap, after adding stuff
 * with add_tty_char etc.
 */
pascal short
update_tty ( WindowPtr window ) {
Rect r ;
RECORD ( record ) ;

#if CLIP_RECT_ONLY
	if ( record -> invalid_rect . right <= record -> invalid_rect . left ||
		record -> invalid_rect . bottom <= record -> invalid_rect . top ) {
		return noErr ;
	}
	r = record -> invalid_rect ;
#else
	if ( EmptyRgn ( record -> invalid_part ) ) {
		return noErr ;
	}
	r = ( * ( record -> invalid_part ) ) -> rgnBBox ;
#endif
	select_onscreen_window ( record ) ;
#if CLIP_RECT_ONLY
	copy_bits ( record , & r , srcCopy , (RgnHandle) 0 ) ;
	empty_rect ( & ( record -> invalid_rect ) ) ;
#else
	copy_bits ( record , & r , srcCopy , (RgnHandle) 0 ) ;
	SetEmptyRgn ( record -> invalid_part ) ;
#endif
	if ( record -> curs_state ) {

		pos_rect ( record , & r , record -> x_curs , record -> y_curs ,
			record -> x_curs , record -> y_curs ) ;
		InvertRect ( & r ) ;
	}

	return noErr ;
}


/*
 * Add a single character. It is drawn directly if the correct flag is set,
 * else deferred to the next update event or call of update_tty()
 */
pascal short
add_tty_char ( WindowPtr window , short character ) {
char s [ 2 ] ;

	s [ 0 ] = character ;
	s [ 1 ] = 0 ;
	return add_tty_string ( window , s ) ;
}


/*
 * Low level add to screen
 */
static void
do_add_string ( tty_record * record , char * str , short len ) {
Rect r ;
register int x_pos , count = len ;

	if ( len < 1 ) {
		return ;
	}
	select_offscreen_port ( record ) ;

	if ( 0L != ( record -> attribute [ TTY_ATTRIB_FLAGS ] & TA_MOVE_EACH_CHAR ) ) {
		x_pos = record -> x_curs ;
		while ( count -- ) {
			MoveTo ( x_pos * record -> char_width , record -> y_curs *
				record -> row_height + record -> ascent_height ) ;
			DrawChar ( * ( str ++ ) ) ;
		}
	} else {
		MoveTo ( record -> x_curs * record -> char_width , record -> y_curs *
			record -> row_height + record -> ascent_height ) ;
		DrawText ( str , 0 , len ) ;
	}

	pos_rect ( record , & r , record -> x_curs , record -> y_curs ,
		record -> x_curs + len - 1 , record -> y_curs ) ;
	if ( DRAW_DIRECT ) {
		select_onscreen_window ( record ) ;
		copy_bits ( record , & r , srcCopy , (RgnHandle)0 ) ;
	} else {
		accumulate_rect ( record , & r ) ;
		select_onscreen_window ( record ) ;
	}
}


/*
 * Low-level cursor handling routine
 */
static void
do_add_cursor ( tty_record * record , short x_pos ) {

	record -> x_curs = x_pos ;
	if ( record -> x_curs >= record -> x_size ) {
		if ( 0L != ( record -> attribute [ TTY_ATTRIB_FLAGS ] & TA_WRAP_AROUND ) ) {
			record -> y_curs ++ ;
			record -> x_curs = 0 ;
			if ( record -> y_curs >= record -> y_size ) {
				if ( 0L != ( record -> attribute [ TTY_ATTRIB_FLAGS ] &
					TA_INHIBIT_VERT_SCROLL ) ) {
					record -> y_curs = record -> y_size ;
				} else {
					scroll_tty ( record -> its_window , 0 , 1 + record -> y_curs -
						record -> y_size ) ;
				}
			}
		} else {
			record -> x_curs = record -> x_size ;
		}
	}
}


/*
 * Beep
 */
static void
do_tty_beep ( tty_record * record ) {
	if ( record -> attribute [ TTY_BEEP_FUNCTION ] ) {
	pascal void ( * tty_beep ) ( WindowPtr ) = ( pascal void ( * ) ( WindowPtr ) )
		record -> attribute [ TTY_BEEP_FUNCTION ] ;
		( * tty_beep ) ( record -> its_window ) ;
	} else {
		SysBeep ( 20 ) ;
	}
}


/*
 * Do control character
 */
static void
do_control ( tty_record * record , short character ) {
static int recurse = 0 ;

/*
 * Check recursion because nl_add_cr and cr_add_nl may both be set and call each other
 */
	recurse ++ ;
	if ( recurse > 2 ) {
		return ;
	}
	switch ( character ) {
	case 10 :
		record -> y_curs ++ ;
		if ( record -> y_curs >= record -> y_size ) {
			scroll_tty ( record -> its_window , 0 , 1 + record -> y_curs -
				record -> y_size ) ;
		}
		if ( 0L != ( record -> attribute [ TTY_ATTRIB_CURSOR ] & TA_NL_ADD_CR ) ) {
			do_control ( record , 13 ) ;
		}
		break ;
	case 13 :
		record -> x_curs = 0 ;
		if ( 0L != ( record -> attribute [ TTY_ATTRIB_CURSOR ] & TA_CR_ADD_NL ) ) {
			do_control ( record , 10 ) ;
		}
		break ;
	case 7 :
		do_tty_beep ( record ) ;
		break ;
	case 8 :
		if ( record -> x_curs > 0 ) {
			record -> x_curs -- ;
		}
		break ;
	default :
		break ;
	}
	recurse -- ;
}


/*
 * Add a null-terminated string of characters
 */
pascal short
add_tty_string ( WindowPtr window , const char * string ) {
register const unsigned char * start_c ;
register const unsigned char * the_c ;
register short max_x , pos_x ;
RECORD ( record ) ;

	select_onscreen_window ( record ) ;
	curs_pos ( record , record -> x_curs , record -> y_curs , 0 ) ;

	the_c = ( const unsigned char * ) string ;
	max_x = record -> x_size ;
	while ( 1 ) {
		start_c = the_c ;
		pos_x = record -> x_curs ;
		if ( ( 0L == ( record -> attribute [ TTY_ATTRIB_FLAGS ] & TA_WRAP_AROUND ) ) &&
			pos_x >= max_x ) { /* Optimize away drawing across border without wrap */
			break ;
		}
		while ( pos_x < max_x && ! s_control [ * the_c ] ) {
			the_c ++ ;
			pos_x ++ ;
		}
		do_add_string ( record , ( char * ) start_c , the_c - start_c ) ;
		do_add_cursor ( record , pos_x ) ;
		if ( ! * the_c ) {
			break ;
		}
		if ( s_control [ * the_c ] ) {
			do_control ( record , * the_c ) ;
			the_c ++ ;
		}
	}
	select_onscreen_window ( record ) ;
	curs_pos ( record , record -> x_curs , record -> y_curs , 1 ) ;

	ShowWindow ( window ) ;

	return noErr ;
}


/*
 * Do a c-style printf - the result shouldn't be too long...
 */
short
printf_tty ( WindowPtr window , const char * fmt , ... ) {
static char buf [ 256 ] ;
va_list list ;

	va_start ( list , fmt ) ;
	vsprintf ( buf , fmt , list ) ;
	va_end ( list ) ;

	return add_tty_string ( window , buf ) ;
}


/*
 * Read or change attributes for the tty. Note that some attribs may
 * very well clear and reallocate the bitmap when changed, whereas
 * others (color, highlight, ...) are guaranteed not to.
 */
pascal short
get_tty_attrib ( WindowPtr window , tty_attrib attrib , long * value ) {
RECORD ( record ) ;

	if ( attrib < 0 || attrib >= TTY_NUMBER_ATTRIBUTES ) {
		return general_failure ;
	}
	* value = record -> attribute [ attrib ] ;

	return noErr ;
}


pascal short
set_tty_attrib ( WindowPtr window , tty_attrib attrib , long value ) {
long old_value ;
RGBColor rgb_color ;
RECORD ( record ) ;

	if ( attrib < 0 || attrib >= TTY_NUMBER_ATTRIBUTES ) {
		return general_failure ;
	}
	old_value = record -> attribute [ attrib ] ;
	if ( old_value == value ) {
		return noErr ;
	}
	record -> attribute [ attrib ] = value ;
	/*
	 * Presently, no attributes generate a new bitmap.
	 */
	switch ( attrib ) {
	case TTY_ATTRIB_CURSOR :
/*
 * Check if we should change tables
 */
		if ( 0L != ( value & TA_RAW_OUTPUT ) ) {
			s_control = s_raw_controls ;
		} else {
			s_control = s_cooked_controls ;
		}
		break ;
	case TTY_ATTRIB_FLAGS :
/*
 * Check if we should flush the output going from cached to draw-direct
 */
		if ( 0L != ( value & TA_ALWAYS_REFRESH ) ) {
			update_tty ( window ) ;
		}
		break ;
	case TTY_ATTRIB_FOREGROUND :
/*
 * Set foreground color
 */
 		TA_TO_RGB ( value , rgb_color ) ;
		select_offscreen_port ( record ) ;
		RGBForeColor ( & rgb_color ) ;
		select_onscreen_window ( record ) ;
		RGBForeColor ( & rgb_color ) ;
		break ;
	case TTY_ATTRIB_BACKGROUND :
/*
 * Set background color
 */
 		TA_TO_RGB ( value , rgb_color ) ;
		select_offscreen_port ( record ) ;
		RGBBackColor ( & rgb_color ) ;
		select_onscreen_window ( record ) ;
		RGBBackColor ( & rgb_color ) ;
		break ;
	default :
		break ;
	}
	return noErr ;
}


/*
 * Scroll the window. Positive is up/left. scroll_tty ( window , 0 , 1 ) is a line feed.
 * Scroll flushes the accumulated update area by calling update_tty().
 */
pascal short
scroll_tty ( WindowPtr window , short delta_x , short delta_y ) {
RgnHandle rgn ;
RECORD ( record ) ;

	select_onscreen_window ( record ) ;
	s_err = update_tty ( window ) ;

	rgn = NewRgn ( ) ;

	select_offscreen_port ( record ) ;
	ScrollRect ( & ( record -> its_bits . bounds ) , - delta_x * record -> char_width ,
		- delta_y * record -> row_height , rgn ) ;
	EraseRgn ( rgn ) ;
	SetEmptyRgn ( rgn ) ;

	select_onscreen_window ( record ) ;
	ScrollRect ( & ( record -> its_bits . bounds ) , - delta_x * record -> char_width ,
		- delta_y * record -> row_height , rgn ) ;
	EraseRgn ( rgn ) ;
	DisposeRgn ( rgn ) ;

	record -> y_curs -= delta_y ;
	record -> x_curs -= delta_x ;

	return noErr ;
}


/*
 * Clear the screen. Immediate.
 */
pascal short
clear_tty ( WindowPtr window ) {
RECORD ( record ) ;

	select_offscreen_port ( record ) ;
	erase_rect ( record , & ( record -> its_bits . bounds ) ) ;
	select_onscreen_window ( record ) ;
	curs_pos ( record , record -> x_curs , record -> y_curs , 0 ) ;
	erase_rect ( record , & ( record -> its_bits . bounds ) ) ;
#if CLIP_RECT_ONLY
	empty_rect ( & ( record -> invalid_rect ) ) ;
#else
	SetEmptyRgn ( record -> invalid_part ) ;
#endif
	curs_pos ( record , record -> x_curs , record -> y_curs , 1 ) ;

	return noErr ;
}


/*
 * Resize the area - clears and reallocates the bitmap.
 */
pascal short
resize_tty_area ( WindowPtr window , short x_size , short y_size ) {
RECORD ( record ) ;

	record -> x_size = x_size ;
	record -> y_size = y_size ;

	return force_tty_coordinate_system_recalc ( window ) ;
}


/*
 * Echo to the user if echo mode on
 */
static void
do_add_input_character ( tty_record * record , unsigned char character ) {
	if ( 0L != ( record -> attribute [ TTY_ATTRIB_CURSOR ] & TA_ECHO_INPUT ) ) {
		add_tty_char ( record -> its_window , character ) ;
	}
}


#if TTY_INPUT
/*
 * Add a key in the queue.
 */
static short
do_add_key ( tty_record * record , long message ) {
	if ( record -> input_buffer_len >= record -> input_buffer_limit ) {
		do_tty_beep ( record ) ;
		return general_failure ;
	} else {
/*
 * If input is cooked, we should fix up this here line to allow line editting...
 */
		record -> input_buffer [ record -> input_buffer_len ++ ] = message & 0xff ;
		do_add_input_character ( record , message & 0xff ) ;
		return noErr ;
	}
}
#endif


/*
 * Add a key in the queue.
 */
static short
do_cmd_key ( tty_record * record , EventRecord * event ) {
	pascal void ( * callback ) ( EventRecord * event , WindowPtr window ) =
		( pascal void ( * ) ( EventRecord * , WindowPtr ) )
		record -> attribute [ TTY_COMMAND_KEY_CALLBACK ] ;

	if ( callback ) {
		( * callback ) ( event , record -> its_window ) ;
		return noErr ;
	}
	return general_failure ;
}


/*
 * Handle a tty event:
 * Updates, pertaining to our window.
 * Key downs, entered into the queue (if we are frontmost)
 */
pascal short
handle_tty_event ( WindowPtr window , EventRecord * event ) {
RECORD ( record ) ;

	update_tty ( window ) ;

	if ( 0L != ( record -> attribute [ TTY_ATTRIB_CURSOR ] & TA_BLINKING_CURSOR ) ) {
		if ( event -> when > record -> last_cursor + GetCaretTime ( ) ) {
			curs_pos ( record , record -> x_curs , record -> y_curs ,
				! record -> curs_state ) ;
			record -> last_cursor = event -> when ;
		}
	}

	switch ( event -> what ) {
	case updateEvt :
		if ( event -> message == ( long ) window ) {
			BeginUpdate ( window ) ;
			
			/* Why do we have to erase the same area we are about to
				do a copy_bits to?
				
			erase_rect ( record , & ( record -> its_bits . bounds ) ) ;
			
			*/
			
			tty_environment_changed ( window ) ;
			s_err = image_tty ( window ) ;
			EndUpdate ( window ) ;
			return s_err ;
		}
		break ;
#if TTY_INPUT
	case keyDown :
	case autoKey :
		if ( FrontWindow ( ) == window ) {
			if ( event -> modifiers & cmdKey ) {
				return do_cmd_key ( record , event ) ;
			} else {
				return do_add_key ( record , event -> message ) ;
			}
		}
		break ;
#endif
	deafult :
		break ;
	}

	return general_failure ;
}


/*
 * Draw an image of the tty - used for update events and can be called
 * for screen dumps.
 */
pascal short
image_tty ( WindowPtr window ) {
Rect r ;
RECORD ( record ) ;

	select_onscreen_window ( record ) ;
	copy_bits ( record , & ( record -> its_bits . bounds ) , srcCopy , (RgnHandle) 0 ) ;
	if ( record -> curs_state ) {

		pos_rect ( record , & r , record -> x_curs , record -> y_curs ,
			record -> x_curs , record -> y_curs ) ;
		InvertRect ( & r ) ;
	}

	return noErr ;
}


#if TTY_INPUT

/*
 * Read a character depending on the input mode
 */
pascal short
getchar_tty ( WindowPtr window , short * character ) {
RECORD ( record ) ;

	if ( 0L != ( record -> attribute [ TTY_ATTRIB_CURSOR ] & TA_RAW_INPUT ) ) {
		while ( ! record -> input_buffer_len ) {
		EventRecord er ;

			WaitNextEvent ( -1 , & er , GetCaretTime ( ) , (char *)0 ) ;
			if ( handle_tty_event ( window , & er ) ) {
				switch ( er . what ) {
				default :
					break ;
				}
			}
		}
		* character = 
	} else {

	}
}

#endif /* TTY_INPUT */

#if EXTENDED_SUPPORT
/*
 * Delete or insert operations used by many terminals can bottleneck through
 * here. Note that the order of executin for row/colum insertions is NOT
 * specified. Negative values for num_ mean delete, zero means no effect.
 */
pascal short
mangle_tty_rows_columns ( WindowPtr window , short from_row , short num_rows ,
	short from_column , short num_columns ) {
Rect r ;
RgnHandle rh = NewRgn ( ) ;
RECORD ( record ) ;

	update_tty ( window ) ; /* Always make sure screen is OK */
	curs_pos ( record , record -> x_curs , record -> y_curs , 0 ) ;

	if ( num_rows ) {
		pos_rect ( record , & r , 0 , from_row , record -> x_size - 1 ,
			record -> y_size - 1 ) ;
		select_offscreen_port ( record ) ;
		ScrollRect ( & r , 0 , num_rows * record -> row_height , rh ) ;
		EraseRgn ( rh ) ;
		SetEmptyRgn ( rh ) ;
		select_onscreen_window ( record ) ;
		ScrollRect ( & r , 0 , num_rows * record -> row_height , rh ) ;
		EraseRgn ( rh ) ;
		SetEmptyRgn ( rh ) ;
	}
	if ( num_columns ) {
		pos_rect ( record , & r , from_column , 0 , record -> x_size - 1 ,
			record -> y_size - 1 ) ;
		select_offscreen_port ( record ) ;
		ScrollRect ( & r , num_columns * record -> char_width , 0 , rh ) ;
		EraseRgn ( rh ) ;
		SetEmptyRgn ( rh ) ;
		select_onscreen_window ( record ) ;
		ScrollRect ( & r , num_columns * record -> char_width , 0 , rh ) ;
		EraseRgn ( rh ) ;
		SetEmptyRgn ( rh ) ;
	}
	DisposeRgn ( rh ) ;
	if ( record -> x_curs >= from_column ) {
		record -> x_curs += num_columns ;
	}
	if ( record -> y_curs >= from_row ) {
		record -> y_curs += num_rows ;
	}
	curs_pos ( record , record -> x_curs , record -> y_curs , 1 ) ;

	return noErr ;
}


/*
 * Clear an area
 */
pascal short
clear_tty_window ( WindowPtr window , short from_x , short from_y ,
	short to_x , short to_y ) {
Rect r ;
RECORD ( record ) ;

	if ( from_x > to_x || from_y > to_y ) {
		return general_failure ;
	}
	pos_rect ( record , & r , from_x , from_y , to_x , to_y ) ;
	select_offscreen_port ( record ) ;
	erase_rect ( record , & r ) ;
	accumulate_rect ( record , & r ) ;
	if ( DRAW_DIRECT ) {
		update_tty ( window ) ;
	} else
		select_onscreen_window ( record ) ;
}


/*
 * Frame an area in an aesthetically pleasing way.
 */
pascal short
frame_tty_window ( WindowPtr window , short from_x , short from_y ,
	short to_x , short to_y , short frame_fatness ) {
Rect r ;
RECORD ( record ) ;

	if ( from_x > to_x || from_y > to_y ) {
		return general_failure ;
	}
	pos_rect ( record , & r , from_x , from_y , to_x , to_y ) ;
	select_offscreen_port ( record ) ;
	PenSize ( frame_fatness , frame_fatness ) ;
	FrameRect ( & r ) ;
	PenNormal ( ) ;
	accumulate_rect ( record , & r ) ;
	if ( DRAW_DIRECT ) {
		update_tty ( window ) ;
	} else
		select_onscreen_window ( record ) ;
}


/*
 * Highlighting a specific part of the tty window
 */
pascal short
invert_tty_window ( WindowPtr window , short from_x , short from_y ,
	short to_x , short to_y ) {
Rect r ;
RECORD ( record ) ;

	if ( from_x > to_x || from_y > to_y ) {
		return general_failure ;
	}
	pos_rect ( record , & r , from_x , from_y , to_x , to_y ) ;
	select_offscreen_port ( record ) ;
	InvertRect ( & r ) ;
	accumulate_rect ( record , & r ) ;
	if ( DRAW_DIRECT ) {
		update_tty ( window ) ;
	} else
		select_onscreen_window ( record ) ;
}


static void
canonical_rect ( Rect * r , short x1 , short y1 , short x2 , short y2 ) {
	if ( x1 < x2 ) {
		if ( y1 < y2 ) {
			SetRect ( r , x1 , x2 , y1 , y2 ) ;
		} else {
			SetRect ( r , x1 , x2 , y2 , y1 ) ;
		}
	} else {
		if ( y1 < y2 ) {
			SetRect ( r , x2 , x1 , y1 , y2 ) ;
		} else {
			SetRect ( r , x2 , x1 , y2 , y1 ) ;
		}
	}
}


/*
 * Line drawing - very device dependent
 */
pascal short
draw_tty_line ( WindowPtr window , short from_x , short from_y ,
	short to_x , short to_y ) {
Rect r ;
RECORD ( record ) ;

	select_offscreen_port ( record ) ;
	MoveTo ( from_x , from_y ) ;
	LineTo ( to_x , to_y ) ;
	canonical_rect ( & r , from_x , from_y , to_x , to_y ) ;
	accumulate_rect ( record , & r ) ;
	if ( DRAW_DIRECT ) {
		update_tty ( window ) ;
	} else
		select_onscreen_window ( record ) ;
}


#endif /* EXTENDED_SUPPORT */
