/* Import-Export module. Copyright 1995-96 by DataPak Software, Inc.  This software is a
part of the total PAIGE library.

This source file contains all the default member and non-member functions for the import class. */


#include "Paige.h"
#include "machine.h"
#include "pgTraps.h"
#include "pgTxrCPP.h"
#include "defprocs.h"
#include "pgDefStl.h"
#include "pgSelect.h"
#include "pgErrors.h"
#include "pgUtils.h"
#include "pgdeftbl.h"

static pg_error read_buffer (pg_char_ptr buffer, long PG_FAR *bytesize, long PG_FAR *position,
		long max_position, file_io_proc io_proc, file_ref filemap, pg_short_t is_unicode);
static pg_boolean find_font_in_list (paige_rec_ptr pg, font_info_ptr font);


/* PaigeImportFilter constructor. This sets up the various members common to all filters.
No error checking or file verification occurs just yet. */

PaigeImportFilter::PaigeImportFilter ()
{
	pgFillBlock(&translator, sizeof(pg_translator), 0);
	bytes_imported = bytes_read = 0;
	buffer_ref = MEM_NULL;			// Zero buffer so we don't try to dispose yet

	file_type = pg_text_type;	// Default type for this class
	feature_bits = IMPORT_TEXT_FEATURE | IMPORT_CACHE_CAPABILITY;
	import_bits = IMPORT_TEXT_FLAG;
	file_os = CURRENT_OS;
	max_buffer = MAX_TEXT_BUFFER;
	font_cross_table = cross_font_table;
	character_table = cross_character_table;
	
	unicode_flag = 0;
	io_result = NO_ERROR;
}


/* PaigeImportFilter destructor. This removes common items. */

PaigeImportFilter::~PaigeImportFilter ()
{
	::pgDisposeTranslatorRec(&translator);
	
	if (buffer_ref)
		::UnuseAndDispose(buffer_ref);
}

/* pgInitImportFile prepares the physical file params to be imported. No data is transferred until the
first pgReadNextBlock function call. */

pg_error PaigeImportFilter:: pgInitImportFile (pg_globals_ptr globals, pg_file_unit fileref, memory_ref memory_unit,
					file_io_proc read_proc, long first_position, long last_position)
{
	pg_error			result = NO_ERROR;

	paige_globals = globals;
	memory_file = memory_unit;
	filemap = (file_ref)fileref;

	if ((io_proc = read_proc) == NULL) {

		if (memory_unit) {
		
			io_proc = pgScrapMemoryRead;
			filemap = (file_ref)memory_unit;
		}
		else
			io_proc = pgOSReadProc;
	}

	import_pg = MEM_NULL;
	text_position = 0;
	bytes_imported = 0;

	file_begin = filepos = first_position;
	
	if ((file_eof = last_position) == UNKNOWN_POSITION) {
	
		result = io_proc((void PG_FAR *)&file_eof, io_get_eof, &filepos, &filepos, filemap);
		filepos = file_begin;
	}
	
	::pgInitTranslatorRec(globals, &translator);

	buffer_size = 0;
	buffer_ref = MEM_NULL;
	io_buffer = NULL;

	if (feature_bits & IMPORT_SERIAL_SETUP) {
		pg_short_t		unicode_test;
		long			test_size, test_position;

		buffer_size = max_buffer;
		buffer_ref = ::MemoryAllocClear(globals->mem_globals, sizeof(pg_char), buffer_size, 0);
		io_buffer = (pg_char_ptr)::UseMemory(buffer_ref);

		test_position = filepos;
		test_size = 2;
		io_proc((void PG_FAR *)&unicode_test, io_data_direct, &test_position, &test_size, filemap);
		
		if (unicode_test == PG_BOM || unicode_test == PG_REVERSE_BOM)
			unicode_flag = (pg_char)unicode_test;

		result = read_buffer(io_buffer, &buffer_size, &filepos, file_eof, io_proc, filemap, unicode_flag);
		buffer_index = 0;
	}

	return	result;
}


/* pgImportFile is the main entry point to import a file. The file that is imported is assumed
to be set up in this object. */

pg_error PaigeImportFilter::pgImportFile (pg_ref pg, long pg_position, long import_flags,
			pg_boolean keep_selection, short draw_mode)
{
	paige_rec_ptr		pg_rec;
	style_info_ptr		style_ptr;
	par_info_ptr		par_ptr;
	font_info_ptr		font_ptr;
	font_info			inserted_font;
	pg_boolean			data_transferred, is_cached;
	long				bytes_to_insert;
	pg_error			result = NO_ERROR;

	import_pg = pg;
	import_pg_rec = pg_rec = (paige_rec_ptr)::UseMemory(pg);
	import_bits = import_flags;
	
	is_cached = (pg_boolean)((import_bits & IMPORT_CACHE_FLAG) != 0 && (feature_bits & IMPORT_CACHE_CAPABILITY) != 0);
	
	if (is_cached) {
		
		import_pg_rec->cache_file = filemap;
		import_pg_rec->cache_read_proc = io_proc;
		import_pg_rec->cache_char_table = character_table;
	}

	text_position = ::pgFixOffset(pg_rec, pg_position);

	::pgTurnOffHighlight(pg_rec, TRUE);
	
	if ((result = this->pgPrepareImport()) == NO_ERROR) {
		
		if (pg_rec->num_selects)
			::pgDelete(pg, NULL, draw_none);
		
		::pgSetSelection(pg, text_position, text_position, 0, FALSE);
		
		pgBeginImport(pg, text_position);

		output_ptr = (pg_char_ptr)UseMemory(translator.data);
		
		do {
			
			data_transferred = this->pgReadNextBlock();
			
			output_ptr = (pg_char_ptr)UseMemoryRecord(translator.data, 0, 0, FALSE);

			if (translator.bytes_transferred) {

				if (translator.format_changed && (import_bits & IMPORT_TEXT_FORMATS_FLAG))
					style_ptr = &translator.format;
				else
					style_ptr = NULL;

				if (translator.par_format_changed && (import_bits & IMPORT_PAR_FORMATS_FLAG))
					par_ptr = &translator.par_format;
				else
					par_ptr = NULL;
				
				translator.par_format_changed = FALSE;
				
				if (translator.font_changed && (import_bits & IMPORT_TEXT_FORMATS_FLAG)) {
					
					inserted_font = translator.font;
					font_ptr = &inserted_font;
					
					if (!FindFontInList(font_ptr))
						this->pgMapFont(font_ptr, file_os, CURRENT_OS);
				}
				else
					font_ptr = NULL;

				if (import_bits & IMPORT_TEXT_FLAG) {
				
					bytes_to_insert = translator.bytes_transferred;
					
					if  (!(import_bits & IMPORT_CACHE_FLAG))
						this->pgMapChars(output_ptr, bytes_to_insert, file_os, CURRENT_OS);
				}
				else
					bytes_to_insert = 0;

				pgInsertText(pg, output_ptr, bytes_to_insert, font_ptr, style_ptr, par_ptr, draw_none);

				text_position += translator.bytes_transferred;
				bytes_imported += translator.bytes_transferred;
			}

			pg_rec->procs.wait_proc(pg_rec, open_wait, file_begin + bytes_read, file_eof);

		} while (data_transferred);

// Change document info, if appropriate:

		if (translator.doc_info_changed && (import_bits & IMPORT_PAGE_INFO_FLAG)) {
			pg_doc_info		doc_info;
			rectangle		page_bounds;
			
			pgGetDocInfo(import_pg, &doc_info);
			doc_info.margins = translator.doc_info.margins;
			doc_info.print_target = translator.doc_info.print_target;
			pgSetDocInfo(import_pg, &doc_info, FALSE, draw_none);
			
			if (import_bits & APPLY_PAGE_DIMENSIONS) {
				
				if (!pgEmptyRect(&doc_info.print_target)) {
					
					page_bounds = doc_info.print_target;
					page_bounds.top_left.h += doc_info.margins.top_left.h;
					page_bounds.top_left.v += doc_info.margins.top_left.v;
					page_bounds.bot_right.h -= doc_info.margins.bot_right.h;
					page_bounds.bot_right.v -= doc_info.margins.bot_right.v;

					pgSetAreaBounds(import_pg, &page_bounds, NULL);
				}
			}
		}

		if (is_cached) {
			text_block_ptr			block;
			
			block = (text_block_ptr)UseMemory(import_pg_rec->t_blocks);
			DisposeNonNilMemory(block->text);
			block->text = MEM_NULL;
			UnuseMemory(import_pg_rec->t_blocks);
		}

		this->pgImportDone();
		result = io_result;
		pg_rec->procs.wait_proc(pg_rec, open_wait, file_eof, file_eof);
		
		UnuseMemory(translator.data);
		
		::pgEndImport(pg, keep_selection, draw_mode);
	}

	::UnuseMemory(pg);

	if (result == EOF_ERR)
		return	NO_ERROR;

	return		result;
}


/* pgGetImportByte returns the next byte in the read buffer. This is used for "serial" formats
such as ASCII text and RTF. */

pg_error PaigeImportFilter::pgGetImportByte (pg_char_ptr the_byte)
{
	pg_error			result = NO_ERROR;

	if (buffer_index == buffer_size) {
		
		*the_byte = 0;
		result = EOF_ERR;
	}
	else {

		*the_byte = io_buffer[buffer_index];
		buffer_index += 1;
		
		if (buffer_index == buffer_size)
			if (filepos < file_eof) {

			buffer_size = max_buffer;
			result = read_buffer(io_buffer, &buffer_size, &filepos, file_eof, io_proc, filemap, unicode_flag);
			buffer_index = 0;
			
			if (result == EOF_ERR)
				result = NO_ERROR;
		}
	}

	return	result;
}


/* pgGetImportNBytes is the same as pgGetImportByte except multiple bytes are read. */

pg_error PaigeImportFilter::pgGetImportNBytes (long PG_FAR *num_bytes, pg_char_ptr bytes)
{
	register pg_char_ptr		output;
	register long				bytecount;
	pg_error					result = NO_ERROR;
	
	output = bytes;
	
	for (bytecount = 0; bytecount < *num_bytes; ++bytecount) {
	
		if ( (result = pgGetImportByte(output)) != NO_ERROR )
			break;
		++output;
	}

	*num_bytes = bytecount;

	return		result;
}


/* pgVerifySignature checks the existing file to verify that it is the appropriate type.
If not, BAD_TYPE is returned as an error. */

pg_error PaigeImportFilter::pgVerifySignature (void)
{
	return	0;
}

pg_error PaigeImportFilter::pgPrepareImport (void)
{
	return	NO_ERROR;
}


/* pgReadNextBlock should load the next textblock into the buffer. If no bytes are read (end
of file reached last call), FALSE is returned, otherwise TRUE is return even if only one byte
is read. Note, however, for some translators the read process may process the data, in which case
the buffer will contain ZERO bytes even though the function returns TRUE.

DEFAULT BEHAVIOUR:  This specific code assumes raw text. */

pg_boolean PaigeImportFilter::pgReadNextBlock (void)
{	
	if (file_begin == filepos) {
		pg_short_t		unicode_test;
		long			test_size, test_position;

		test_position = filepos;
		test_size = 2;
		io_proc((void PG_FAR *)&unicode_test, io_data_direct, &test_position, &test_size, filemap);
		
		if (unicode_test == PG_BOM || unicode_test == PG_REVERSE_BOM)
			unicode_flag = (pg_char)unicode_test;
	}

	if (filepos == file_eof)
		translator.bytes_transferred = 0;
	else {
		
		translator.cache_begin = filepos;
		translator.bytes_transferred = max_buffer;
		io_result = read_buffer(output_ptr, &translator.bytes_transferred, &filepos, file_eof, io_proc, filemap, unicode_flag);
	}
	
	bytes_read = filepos - file_begin;
	
	if (translator.bytes_transferred == 0)
		return	FALSE;

	return	TRUE;
}

/* pgImportDone gets called after everything has been imported. */

pg_error PaigeImportFilter::pgImportDone (void)
{
	if (import_pg_rec->cache_file) {
		text_block_ptr			block;
		long					num_blocks;
		
		num_blocks = GetMemorySize(import_pg_rec->t_blocks);
		
		for (block = (text_block_ptr)UseMemory(import_pg_rec->t_blocks); num_blocks; ++block, --num_blocks) {
			
			block->cache_flags = 0;
			block->cache_begin = file_begin + block->begin;
		}
		
		UnuseMemory(import_pg_rec->t_blocks);
	}

	return	NO_ERROR;
}


/* OutputCharacter appends another text byte to the output.  If we hit the max then TRUE
is returned. */

pg_boolean PaigeImportFilter::OutputCharacter (pg_char the_byte)
{
	*output_ptr++ = the_byte;

	translator.bytes_transferred += 1;
	translator.total_text_read += 1;
	
	return	(pg_boolean)(translator.bytes_transferred == max_buffer);
}

/* OutputString outputs characters in cstring the_str. */

pg_boolean PaigeImportFilter::OutputString (pg_char_ptr the_str)
{
	pg_boolean	result = FALSE;
	short		index;
	
	for (index = 0; the_str[index]; ++index)
		if ((result = OutputCharacter(the_str[index])) != FALSE)
			break;
	
	return	result;
}


/* pgProcessEmbedData accepts data from from a translator then processes that data to
the appropriate type. */

void PG_FAR * PaigeImportFilter::pgProcessEmbedData (memory_ref ref, long embed_type)
{
	void PG_FAR		*result_ptr = NULL;
	long			data_size;
	
	data_size = GetMemorySize(ref);

	switch (embed_type) {
		
		case embed_mac_pict:
		
#ifdef MAC_PLATFORM

			result_ptr = (void PG_FAR *)MemoryToHandle(ref);
#endif
			break;

		case embed_meta_file:
			{
#ifdef WINDOWS_PLATFORM

#ifdef WIN32_COMPILE
				void PG_FAR		*metaptr;
				
				
				metaptr = UseMemory(ref);
				result_ptr = (void PG_FAR *)SetMetaFileBitsEx((UINT)data_size, (BYTE PG_FAR *)metaptr);
				UnuseMemory(ref);
				DisposeMemory(ref);
#else
				HANDLE			metabits;
	
				metabits = MemoryToHandle(ref);
				result_ptr = (void PG_FAR *)SetMetaFileBits(metabits);
#endif
#endif
			}
			
			break;
	}
	
	if (!result_ptr)
		result_ptr = (void PG_FAR *)ref;

	return	result_ptr;
}


/* pgMapFont sets the alternate font name. This function gets called regardless of
source and current OS. Upon entry, file_os is the OS from which the file came,
while current_os is the current platform. */

void PaigeImportFilter::pgMapFont (font_info_ptr font, long importing_os, long current_os)
{
	register pg_char_ptr		table_ptr;
	pg_char					alternate_buffer[FONT_SIZE];
	short						alternate_index, out_index;

	if ((table_ptr = font_cross_table) == (pg_char_ptr)NULL)
		return;
	
	if (pgIsRealFont(paige_globals, font, FALSE))
		return;

	while (*table_ptr <= font->name[1]) {
		
		if (alternate_index = pgCompareFontTable(&font->name[1], table_ptr)) {
			
			table_ptr += alternate_index;
			pgBlockMove(font->name, font->alternate_name, FONT_SIZE * sizeof(pg_char));
			pgFillBlock(font->name, FONT_SIZE * sizeof(pg_char), 0);
			
			for (out_index = 1; out_index < FONT_SIZE; ++out_index) {
				
				font->name[out_index] = *table_ptr++;
				font->name[0] += 1;
				
				if (*table_ptr == ']' || *table_ptr == 0)
					break;
			}
			
			break;
		}
		
		for (;;) {
			
			if (*table_ptr++ == 0)
				break;
		}
	}

// Check to see if the font is valid on this machine:

	if (!pgIsRealFont(paige_globals, font, FALSE)) {

		pgBlockMove(font->name, alternate_buffer, FONT_SIZE * sizeof(pg_char));

		if (pgIsRealFont(paige_globals, font, TRUE)) {
			
			pgBlockMove(font->name, font->alternate_name, FONT_SIZE * sizeof(pg_char));
			pgBlockMove(alternate_buffer, font->name, FONT_SIZE * sizeof(pg_char));
		}
		else {
		
			*font = paige_globals->def_font;
			pgBlockMove(alternate_buffer, font->alternate_name, FONT_SIZE * sizeof(pg_char));
		}
	}
}


/* pgMapChars converts chars tex of num_chars length to the appropriate equivilent value(s). */

void PaigeImportFilter::pgMapChars (pg_char_ptr chars, long num_chars, long file_os, long current_os)
{
	if ((file_os == current_os) || !character_table || !num_chars || unicode_flag)
		return;
	
	::pgMapCharacters(paige_globals, chars, num_chars, character_table);
}


/* FindFontInList returns TRUE if the given font name is already in the list. */

pg_boolean PaigeImportFilter::FindFontInList (font_info_ptr font)
{
	font_info_ptr		fonts;
	pg_boolean			result = FALSE;
	long				num_fonts;
	
	num_fonts = GetMemorySize(import_pg_rec->fonts);
	
	for (fonts = (font_info_ptr)UseMemory(import_pg_rec->fonts); num_fonts; ++fonts, --num_fonts)
		if (pgEqualFontNames(fonts, font, FALSE))
			break;

	UnuseMemory(import_pg_rec->fonts);
	
	return		result;
}


/************************* Utility functions (C interface) ****************************/


PG_PASCAL (pg_error) pgImportFileFromC (pg_ref pg, pg_filetype filetype, long feature_flags,
		long file_begin, pg_file_unit f_ref)
{
	PaigeImportObject			filter;
	pg_globals_ptr				globals;
	long						flags;
	pg_error					result = NO_ERROR;
	
	if (!(flags = feature_flags))
		flags = IMPORT_EVERYTHING_FLAG;

	globals = pgGetGlobals(pg);
	
	switch (filetype) {
		
		case pg_text_type:
			filter = new PaigeImportFilter();
			break;

		case pg_rtf_type:
			filter = (PaigeImportObject) new PaigeRTFImportFilter();
			break;

		case pg_paige_type:
			filter = (PaigeImportObject) new PaigeNativeImportFilter();
			break;

		default:
			return	(pg_error)BAD_TYPE_ERR;
	}

	if ((result = filter->pgInitImportFile(globals, f_ref, NULL, NULL, file_begin, UNKNOWN_POSITION)) == NO_ERROR)
		result = filter->pgImportFile(pg, CURRENT_POSITION, flags, TRUE, best_way);

	delete filter;

	return		result;
}


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

static pg_error read_buffer (pg_char_ptr buffer, long PG_FAR *bytesize, long PG_FAR *position,
		long max_position, file_io_proc io_proc, file_ref filemap, pg_short_t is_unicode)
{
	long			distance_to_end;
	pg_error		result = NO_ERROR;

	if ((distance_to_end = max_position - *position) < *bytesize)
		*bytesize = distance_to_end;

	result = io_proc((void PG_FAR *)buffer, io_data_direct, position, bytesize, filemap);

	if (result == NO_ERROR) {

#ifdef UNICODE

		if (is_unicode)
			*bytesize = pgUnicodeToUnicode(buffer, *bytesize,
						(pg_boolean)(is_unicode == PG_REVERSE_BOM));
		else
			*bytesize = pgBytesToUnicode((pg_bits8_ptr)buffer, *bytesize, FALSE);
#else
		if (is_unicode) {
			long			charsize;
			
			charsize = *bytesize / 2;

			charsize = pgUnicodeToUnicode((pg_short_t PG_FAR *)buffer, charsize / sizeof(pg_char),
					(pg_boolean)(is_unicode == PG_REVERSE_BOM));
			*bytesize = pgUnicodeToBytes((pg_short_t PG_FAR *)buffer, charsize);
		}
#endif

	}

	return		result;
}

