/********************************************************
**                                                     **
**      $VER: Utility.c 2.0 (25 feb 2002)              **
**      ASL requesters, clipboard, locale support,     **
**      pref font list and pref file.                  **
**                                                     **
**      © T.Pierron, Free software under terms of      **
**      GNU public license.                            **
**                                                     **
********************************************************/


#include <libraries/gadtools.h>
#include <libraries/asl.h>
#include <libraries/iffparse.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/rdargs.h>
#include <string.h>

#include "CharMap.h"
#include "Utility.h"
#define	CATCOMP_STRINGS
#define  CATCOMP_NUMBERS
#include "CharMapStrs.h"

/** Clipboard support **/
#define ID_FTXT MAKE_ID('F','T','X','T')
#define ID_CHRS MAKE_ID('C','H','R','S')

#define	MIN_ENTRIES      16

/* Error messages */
STRPTR Errors[] = {
	ERR_BADOS_STR,         ERR_NOINTERFACE_STR,
	ERR_FONTREALLYBIG_STR, ERR_NOASL_STR,
	ERR_CANTOPENCLIP_STR,  ERR_NOTEXTINCLIP_STR,
	ERR_CANTREADCLIP_STR,  ERR_WRITINGTOCLIP_STR,
	WARN_KSNOTFOUND_STR
};

STRPTR popmsg[] = 
{
	MSG_CODE_STR,   MSG_DEC_STR,
	MSG_HEX_STR,    MSG_OCT_STR, 0
};

UBYTE PrefTmpl[] = CFG_TEMPLATE;
UBYTE PrefPath[] = DEF_PREFPATH;

extern struct AslBase *AslBase;
extern struct Window * window;

extern STRPTR GadTxt[];
extern ULONG  FontTags[];
extern struct NewMenu newmenu[];
extern struct TextFont * cmapfont;
extern UBYTE  VertSort, CharsetNum;

APTR catalog = NULL;

UBYTE TranslateGadget[] = {
	NM_FONTTYPE-1, NM_NOTRANS,    NM_COPY, NM_PASTE, NM_TRANSLATE,
	NM_CHARSET,    NM_CHARSET+1,  NM_CHARSET+2,      NM_NOTRANS,
	NM_FONTTYPE,   NM_FONTTYPE+1, NM_FONTTYPE+2
};
struct FontRequester	* FR        = NULL;
struct IFFHandle *     iffhandle = NULL;

/* ASL Font requester tags: */
ULONG FileTags[] = {
	ASLFO_Window,      NULL,
	ASLFO_InitialSize, 0,
	ASLFO_InitialName, NULL,
	ASLFO_SleepWindow, TRUE,
	ASLFO_TitleText,   (ULONG) MSG_ASLTITLE_STR,
	ASLFO_MaxHeight,   255,
	TAG_DONE
};

static struct TextAttr attr = {
	NULL, 0, FS_NORMAL, FPF_DISKFONT
};

static struct List fontList;         /* List of preferred font */
static UWORD       nbFont   = 0;     /* Nb. of preferred font */
static UWORD       nbDisp   = 0;     /* Nb. of displayed font */
static STRPTR *    fontName = NULL;  /* NULL-terminated table for cycle */
static UWORD       fontCapa = 0;     /* Available space into fontName */
static PrefFont    fonts[3];         /* Current default screen, text and custom font */

WORD fontType;

/** Do not translate this strings! **/
UBYTE DefScr[] = "SCREEN", DefTxt[] = "TEXT";

/*** Safer open font ***/
struct TextFont * my_OpenFont( struct TextAttr * attr )
{
	extern   struct Library *  DiskfontBase;
	register struct TextFont * font;

	/* First, check if font remains in system cache */
	if( font = (APTR) OpenFont( attr ) )
		return font;
	else if( DiskfontBase )
		return (struct TextFont *) OpenDiskFont( attr );
	return NULL;
}

/*** Try to open a ASL font requester ***/
struct TextFont *Get_pref_font( UWORD num )
{
	fonts[2]->pf_Charset = CharsetNum;
	if( num != PTYP_CUSTOM )
	{
		/* User wants a preferred font */
		register PrefFont font = HEAD(fontList);
		register STRPTR   ID   = fontName[ num - PTYP_CUSTOM + 1 ];

		switch( num )
		{
			case PTYP_SCREENFONT: font = fonts[0]; break;
			case PTYP_DEFAULT:    font = fonts[1]; break;
			default:
				for( ; SUCC(font) && ID != font->pf_FontName; font = SUCC(font) );
		}
		CharsetNum    = font->pf_Charset; fonts[2] = font;
		attr.ta_Name  = font->pf_FontName;
		attr.ta_YSize = font->pf_Size;
		return my_OpenFont( &attr );
	}
	else /* Let user choose its own custom font */
	{
		FileTags[1] = (ULONG) window;
		FileTags[3] = cmapfont->tf_YSize;
		FileTags[5] = (ULONG) cmapfont->tf_Message.mn_Node.ln_Name;

		if( !FR && !(FR = (void *) AllocAslRequest(ASL_FontRequest,NULL)) )
		{
			ThrowError( ErrMsg( ERR_NOASL ) );
			return NULL;
		}

		if( AslRequest((APTR) FR,FileTags) )
		{
			struct TextFont * font = my_OpenFont( (APTR) &FR->fo_TAttr );

			/* Add this font in the preferred list */
			if( font && (fonts[2] = Add_pref_font(font, PTYP_CUSTOM, CHARSET_NOCARE)) )
			{
				CharsetNum = fonts[2]->pf_Charset;
				return font;
			}
		}
		/* Else user may hit cancel or close gadget */
	}
	return NULL;
}

struct EasyStruct FontReq = {
	sizeof(struct EasyStruct),0,
	NULL,	NULL,	NULL
};
extern struct EasyStruct Request;

BYTE avert( STRPTR msg )
{
	if( FontReq.es_Title == NULL )
	{
		FontReq.es_Title = Request.es_Title;
		FontReq.es_TextFormat = msg;
		if( AslBase == NULL )
			FontReq.es_GadgetFormat = strchr( FontReq.es_GadgetFormat, '|' ) + 1;
	}
	/* Something goes wrong: the font seems too big, so inquiring for a more **
	** convenient font... only if ASL library is present, otherwise quit.    */
	return (BYTE) EasyRequestArgs( window, &FontReq, NULL, NULL );
}

/*** Duplicate a string ***/
static STRPTR my_StrDup( STRPTR fontName )
{
	STRPTR new;
	UWORD  size = strlen( fontName );

	if( new = (STRPTR) AllocVec( size+1, MEMF_PUBLIC ) )
		CopyMem(fontName, new, size), new[size] = 0;

	return new;
}

/*** Search for a specific font name in the preferred list ***/
PrefFont Find_font( STRPTR name )
{
	PrefFont font;
	for( font = HEAD(fontList); SUCC(font); font = SUCC(font) )
		if( strcmp(font->pf_FontName, name) == 0 ) return font;
	return NULL;
}

/*** Alloc a new preferred font ***/
PrefFont Create_pref_font( STRPTR name, UBYTE charset, UBYTE size, UBYTE type )
{
	PrefFont new;
	/* Check first if not already in the list */
	if( new = Find_font( name ) )
	{
		if( type    == PTYP_CUSTOM )    new->pf_Type    = PTYP_CUSTOM;
		if( charset != CHARSET_NOCARE ) new->pf_Charset = charset;
		goto set_vars;
	}

	if( new = (PrefFont) AllocVec(sizeof(*new), MEMF_PUBLIC) )
	{
		if( new->pf_FontName = my_StrDup(name) )
		{
			AddTail(&fontList, new);   nbFont++;
			new->pf_Charset = charset;
			new->pf_Type    = type;    set_vars:
			new->pf_Size    = size;
			return new;
		}
		FreeVec(new);
	}
	return NULL;
}

/*** Register a pref font ****/
PrefFont Add_pref_font( struct TextFont * tf, UBYTE Type, UBYTE Charset )
{
	PrefFont new;

	if(nbFont == 0) NewList(&fontList);

	if( new = Create_pref_font(
	            tf->tf_Message.mn_Node.ln_Name,
	            Charset != CHARSET_NOCARE ? Charset : Get_charset(tf),
	            tf->tf_YSize, Type ) )
	{
		fonts[ Type-1 ] = new;
		return new;
	}
}

/*** Convert font list to null-terminated string table for cycle gadget ***/
void Font_list_to_table( void )
{
	/* Enlarge buffer if too small */
	if( fontCapa < nbFont + 4 )
	{
		STRPTR * new;
		fontCapa = ((nbFont + 4) & ~(MIN_ENTRIES-1)) + MIN_ENTRIES;
		/* Alloc a larger buffer */
		if( new = (APTR) AllocVec( fontCapa * sizeof(STRPTR), MEMF_PUBLIC ) )
		{
			if( fontName ) FreeVec( fontName );
			fontName    = new;
			fontName[0] = GadTxt[ GTXT_FONTTYPE ];
			fontName[1] = GadTxt[ GTXT_FONTTYPE+1 ];
		}
		else return;
	}
	/* Complete font names */
	{	STRPTR * font = fontName+2;
		PrefFont lst  = HEAD(fontList);
		UWORD    nb   = 0;
		for( nbDisp = 2; nb < nbFont; nb++, lst = SUCC(lst) )
			/* Remove all font duplicates */
			if( lst != fonts[0] && lst != fonts[1] )
			{
				STRPTR * tmp;
				for( tmp = fontName+2; tmp < font; tmp++)
					if( *tmp == lst->pf_FontName ) break;

				if( lst == fonts[2] ) FontTags[3] = nbDisp;
				if( tmp == font ) *font++ = lst->pf_FontName, nbDisp++;
			}
		*font = GadTxt[ GTXT_FONTTYPE+2 ]; font[1] = NULL;
		if( fontType < PTYP_CUSTOM ) FontTags[3] = fontType-PTYP_SCREENFONT;
	}
	/* Replace old font list by the new created one */
	FontTags[1] = (ULONG) fontName;
}

ULONG Translate_code( UWORD num )
{
	if( num < 2 )      return (ULONG) NMUD_FONT + num;
	if( num < nbDisp ) return (ULONG) NMUD_FONT + 1 + num;
	return NMUD_FONT + 2;
}

/*** Free allocated ressources ***/
void FreePrefFonts( void )
{
	PrefFont font, next;

	for( font = HEAD(fontList); next = SUCC(font); font = next )
	{
		FreeVec(font->pf_FontName);
		FreeVec(font);
	}
	if( fontName ) FreeVec( fontName );
}

/*** Read CharMap's preference files ***/
void Read_pref( void )
{
	ULONG opt[3];
	WORD  ch;
	BPTR  input, oldinput;

	fonts[2] = HEAD(fontList);
	fontType = PTYP_SCREENFONT;

	if( input = Open( PrefPath, MODE_OLDFILE ) )
	{
		oldinput = SelectInput( input );
		do
		{
			struct RDArgs ra, *ret;
			STRPTR p;

			memset(&ra,  0, sizeof(ra));
			memset(&opt, 0, sizeof(opt));

			/* Use stdin instead anything else */
			ra.RDA_Flags = RDAF_NOPROMPT | RDAF_STDIN;

			if( ret = (APTR) ReadArgs(PrefTmpl, opt, &ra) )
			{
				/* Horizontal sort preferred */
				if( opt[0] ) VertSort = FALSE;
			
				/* Get preferred list of fonts */
				if( opt[2] )
				for( p = (STRPTR) opt[2]; *p; )
				{
					LONG size, charset; STRPTR name;

					name = p; p = strchr(p, '/'); *p++ = 0;
					p += StrToLong(p, &size) + 1;
					p += StrToLong(p, &charset); if( *p ) p++;

					if( !Create_pref_font(name, charset, size, PTYP_CUSTOM) ) break;
				}

				/* Current selected font */
				if( opt[1] )
				{
					if( !Stricmp( opt[1], DefScr ) )
						fontType = PTYP_SCREENFONT, fonts[2] = fonts[0];
					else if( !Stricmp( opt[1], DefTxt ) )
						fontType = PTYP_DEFAULT,    fonts[2] = fonts[1];
					else if( p = (STRPTR) Find_font( (STRPTR) opt[1] ) )
						fontType = PTYP_CUSTOM,     fonts[2] = (APTR) p;
				}
				FreeArgs( ret );
			}
			ch = FGetC( input );
		} while( ch != -1 && UnGetC(input, ch) );
		Close( SelectInput( oldinput ) );
	}
	CharsetNum    = fonts[2]->pf_Charset;
	attr.ta_Name  = fonts[2]->pf_FontName;
	attr.ta_YSize = fonts[2]->pf_Size;
	cmapfont      = my_OpenFont( &attr );

	/* Setup newmenus */
	Register_charset( CharsetNum );
	Check_font_menu( fontType );
}

#ifdef	__GNUC__
void PutChProc( void )        /* Register based-argument passing with gcc */
{
	register UBYTE data __asm("d0");
	register BPTR  file __asm("a3");

#else                         /* Same proc with SAS/C */

void __asm PutChProc(register __d0 UBYTE data, register __a3 BPTR file)
{
#endif
	if( data ) FPutC(file, data);
}

/*** Save current CharMap config ***/
void Save_pref( void )
{
	static UBYTE EndVal[] = "\"\n", StartVal[]="=\"";
	BPTR file;

	if( file = Open( PrefPath, MODE_NEWFILE ) )
	{
		PrefFont font;
		STRPTR   opt = strchr(PrefTmpl, ',')-2, p;
		BOOL     first = TRUE;

		/* Table vertically or horizontally sorted */
		if( VertSort == 0 )
			FWrite(file, PrefTmpl, 1, opt - PrefTmpl),
			FPutC(file, '\n');

		/* Current selected font */
		opt = strchr(p = opt + 3, ',')-2;

		FWrite(file, p, 1, opt - p);
		FPuts (file, StartVal);
		switch( FontTags[3] )
		{
			/* The first two strings are localized */
			case 0:  p = DefScr; break;
			case 1:  p = DefTxt; break;
			default: p = ((STRPTR *)FontTags[1])[ FontTags[3] ];
		}
		FPuts (file, p);
		FPuts (file, EndVal);

		/* List of preferred fonts */
		opt = strchr(p = opt + 3, '\0')-2;

		FWrite(file, p, 1, opt - p);
		FPuts (file, StartVal);
		for( font = HEAD(fontList); SUCC(font); font = SUCC(font) )
		{
			struct {
				STRPTR fontName;
				LONG   size, charset;
			}	Args;
			if( font->pf_Type == PTYP_CUSTOM )
			{
				Args.fontName = font->pf_FontName;
				Args.size     = font->pf_Size;
				Args.charset  = font->pf_Charset;
				if( first ) first = 0; else FPutC(file, ',');
				RawDoFmt("%s/%ld/%ld", &Args, PutChProc, file);
			}
		}
		FPuts(file, EndVal);
		Close(file);
	}
}

/*** Convert a geometry string, describing windows positions ***/
void Parse_geometry( STRPTR p )
{
	extern WORD Offset[];
	WORD *nb;
	LONG  buf;

	for(nb=Offset; ; p++)
	{
		if(*p == '-' || *p == '+' || ('0' <= *p && *p <= '9'))
			p += StrToLong(p, &buf), *nb = (WORD) buf;

		switch(*p)
		{
			case 0:
			case ',': if(*p && ++nb - Offset<4) break;
			default:  return;
		}
	}
}

/*** Adjust position to what user wants ***/
WORD Adjust_pos(WORD pos, WORD max, WORD len)
{
	/* if 0x7FFF center between 0 and max */
	if(pos==0x7FFF) return (WORD)(max-len>>1);
	/* if neg, align pos to max */
	if(pos<0) pos+=max-len+1;
	if(pos<0) return 0;
	if(pos+len>max) return (WORD)(max-len);
	return pos;
}

/*** Try to open the clipboard using iffparse.library ***/
BOOL CBOpen( void )
{
	extern struct Library * IFFParseBase;

	/* No iffparse.library, no clipboard support */
	if( IFFParseBase = (APTR) OpenLibrary("iffparse.library", 0) )
	{
		if( (iffhandle = (APTR) AllocIFF()) )
		{
			if( (iffhandle->iff_Stream = (ULONG) OpenClipboard (0)) )
			{
				InitIFFasClip(iffhandle);
				return TRUE;
			}
			FreeIFF( iffhandle ); iffhandle = NULL;
		}
		CloseLibrary( IFFParseBase);
	}
	return FALSE;
}

/*** Close clipboard ***/
void CBClose( void )
{
	if( iffhandle )
	{
		if( iffhandle->iff_Stream )
			CloseClipboard( iffhandle->iff_Stream );

		FreeIFF(iffhandle);
	}
}

/*** Write a string of text to the clipboard.device ***/
void CBWriteFTXT( STRPTR string )
{
	BOOL err = TRUE;
	/* Open clipboard, if not already */
	if( iffhandle == NULL && CBOpen() == FALSE )
	{
		ThrowError( ErrMsg(ERR_CANTOPENCLIP) );
		return;
	}

	/* Open clipboard for writing */
	if( !OpenIFF(iffhandle, IFFF_WRITE) )
	{
		if( !PushChunk(iffhandle, ID_FTXT, ID_FORM, IFFSIZE_UNKNOWN) )
		{
			ULONG size = strlen( string );
			if( !PushChunk(iffhandle, 0, ID_CHRS, size) )
			{
				WriteChunkBytes( iffhandle, string, size );
				PopChunk(iffhandle);
				err = FALSE;
			}
			PopChunk(iffhandle);
		}
		CloseIFF( iffhandle );
	}
	if( err )
		ThrowError( ErrMsg(ERR_WRITINGTOCLIP) );
}

/*** Reads the next CHRS chunk from clipboard ***/
void CBReadCHRS( STRPTR buffer, WORD max )
{
	BOOL err = TRUE;
	/* Open clipboard, if not already */
	if( iffhandle == NULL && CBOpen() == FALSE )
	{
		ThrowError( ErrMsg(ERR_CANTOPENCLIP) );
		return;
	}
	/* Open clipboard for reading */
	if( !OpenIFF(iffhandle, IFFF_READ) )
	{
		/* Get first FTXT CHRS chunk */
		if( !StopChunk(iffhandle, ID_FTXT, ID_CHRS ) )
		{
			/* Let get fun! */
			if( !ParseIFF(iffhandle, IFFPARSE_SCAN) )
			{
				struct ContextNode * cn = (APTR) CurrentChunk( iffhandle );

				if( max > cn->cn_Size ) max = cn->cn_Size;

				if( ReadChunkBytes(iffhandle, buffer, max) == max )
				{
					/* Okay, we have got our chars now */
					buffer[ max ] = 0;
					err = FALSE;
				}
			}
			else ThrowError( ErrMsg(ERR_NOTEXTINCLIP) ), err = FALSE;
		}
		CloseIFF( iffhandle );
	}
	if( err ) ThrowError( ErrMsg(ERR_CANTREADCLIP) );
}

/*** Translate all messages of the application ***/
void Translate_strings( void )
{
	extern struct Library * LocaleBase;
	UWORD msgID = MSG_MENUTITLE, i;

	if((LocaleBase = (APTR) OpenLibrary ("locale.library", 38)) &&
	   (catalog    = (APTR) OpenCatalogA(NULL, "CharMap.catalog", NULL)))
	{
		/* Translate menu strings */
		{	struct NewMenu * nm;
			for( nm = newmenu; nm->nm_Type != NM_END; nm++ )
				if( nm->nm_Label != NM_BARLABEL )
					nm->nm_Label = (STRPTR) GetCatalogStr(catalog, msgID++,
					nm->nm_Label );
		}
		/* Error messages - info window - About requester */
		{	STRPTR * msg = popmsg;
			for(i = 0; i < 2 + NBITEM(Errors) + NBITEM(popmsg); msg ++, i ++)
			{
				if( i == 4 ) msg = &Request.es_Title;
				if( i == 7 ) msg = Errors;
				*msg = (STRPTR) GetCatalogStr(catalog, msgID++, *msg);
			}
		}
		/* Other messages */
		FileTags[7] = (ULONG) GetCatalogStr(catalog, MSG_ASLTITLE, FileTags[7]);
		FontReq.es_GadgetFormat = (STRPTR) GetCatalogStr(catalog, MSG_CHOOSEQUIT, 
				FontReq.es_GadgetFormat);
	}
	/* Gadget messages */
	{	STRPTR * msg = GadTxt+1;
		for (msgID = MSG_STRGAD, i=0; i < NBITEM(TranslateGadget); i ++, msg ++)
			switch( TranslateGadget[ i ] )
			{
				case NM_NOTRANS: break;
				case NM_TRANSLATE: 
					*msg = (STRPTR) GetCatalogStr(catalog, msgID++, *msg);
					break;
				default: *msg = newmenu[ TranslateGadget[i] ] . nm_Label;
			}
	}
}
