/*
    psftools: Manipulate console fonts in the .PSF format
    Copyright (C) 2002, 2007, 2021  John Elliott <seasip.webmaster@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "psfio.h"

/* Create and initialise a psf_unicode_buffer struct. Its entries will
 * form a linked list; the tail is psfb_dirents[0] and the head is 
 * psfb_dirents[PSF_ENTRIES_PER_BUFFER - 1] */
psf_unicode_buffer *psf_malloc_unicode_buffer(void)
{
	int n;
	psf_unicode_buffer *b = malloc(sizeof(psf_unicode_buffer));
	
	if (!b) return NULL;
	
	for (n = 1; n < PSF_ENTRIES_PER_BUFFER; n++) 
	{
		b->psfb_dirents[n].psfu_next = &b->psfb_dirents[n-1];
	}
	b->psfb_dirents[0].psfu_next = NULL;
	b->psfb_next = NULL;
	return b;
}

static psf_errno_t create_unicode(PSF_UNICODE_DIR *f, psf_dword nchars)
{
	int n;

	f->psf_dirents_used = malloc(nchars * sizeof (psf_unicode_dirent *));
	if (!f->psf_dirents_used) return PSF_E_NOMEM;
	for (n = 0; n < nchars; n++) f->psf_dirents_used[n] = NULL;

	f->psf_dirents_buffer = psf_malloc_unicode_buffer();
	if (!f->psf_dirents_buffer) return PSF_E_NOMEM;
	f->psf_dirents_nused = 0;
	f->psf_dirents_nfree = PSF_ENTRIES_PER_BUFFER;
	f->psf_dirents_free  = 
	    &(f->psf_dirents_buffer->psfb_dirents[PSF_ENTRIES_PER_BUFFER - 1]);
	return PSF_E_OK;
}

/* Initialise the Unicode structures of a font file */
psf_errno_t psf_file_create_unicode(PSF_FILE *f)
{
	int nchars = psf_count_chars(f);
/* Always allocate enough slots for a PSF1 to output correctly */
	if (nchars < 256) nchars = 256;
	if (nchars > 256 && nchars < 512) nchars = 512;	
	/* Clear out any existing unicode directory */
	psf_file_delete_unicode(f);

	f->psf_flags |= 1;

	return create_unicode(&f->psf_dir, nchars);
}

psf_errno_t vfont_create_unicode(VFONT *f)
{
	int nchars = vfont_count_chars(f);

	/* Clear out any existing unicode directory */
	vfont_delete_unicode(f);

	f->vf_flags |= 1;

	return create_unicode(&f->vf_dir, nchars);
}



/* Remove any unicode directory from a PSF file */
static void delete_unicode(PSF_UNICODE_DIR *f)
{
        psf_unicode_buffer *b, *bnew;
        if (f->psf_dirents_used) free(f->psf_dirents_used);

        b = f->psf_dirents_buffer;
        while(b)
        {
                bnew = b->psfb_next;
                free(b);
                b = bnew;
        }
        f->psf_dirents_used = NULL;
        f->psf_dirents_free = NULL;
        f->psf_dirents_buffer = NULL;
        f->psf_dirents_nused = 0;
        f->psf_dirents_nfree = 0;
}

void psf_file_delete_unicode(PSF_FILE *f)
{
	delete_unicode(&f->psf_dir);
        f->psf_flags &= ~1;
}


void vfont_delete_unicode(VFONT *f)
{
	delete_unicode(&f->vf_dir);
        f->vf_flags &= ~1;
}




psf_errno_t psf_unicode_banned(psf_dword token)
{
	/* The range FDD0-FDEF aren't valid Unicode chars */
	if (token >= 0xFDD0 && token <= 0xFDEF) return PSF_E_BANNED;

	/* Any character ending FFFE or FFFF isn't valid. However, PSF
	 * allows FFFE to be written to the directory; it behaves as
	 * a separator. 
	if ((token & 0xFFFF) == 0xFFFE) return PSF_E_BANNED;
	*/
	if ((token & 0xFFFF) == 0xFFFF) return PSF_E_BANNED;

	return PSF_E_OK;
}

/* Add a chain to the Unicode directory from the map */
psf_errno_t psf_unicode_addmap(PSF_FILE *f, psf_word destchar,
	                                PSF_MAPPING *m, psf_word slot)
{
	psf_dword *pw;
	psf_errno_t rv;

	if (slot >= m->psfm_count) return PSF_E_OK;
       	pw = m->psfm_tokens[slot];
	if (!pw) return PSF_E_OK;

	while (*pw != 0xFFFF && *pw != 0x1FFFF)
	{
		rv = psf_unicode_add(f, destchar, *pw);
		if (rv) return rv;
		++pw;	
	}
	return PSF_E_OK;
}

psf_errno_t psf_unicode_addall(PSF_FILE *f, PSF_MAPPING *m, unsigned first,
	unsigned last)
{
	unsigned n;

	psf_errno_t err = psf_file_create_unicode(f);
	if (err) return err;
	for (n = first; n <= last; n++)
	{
		if (n < m->psfm_count)
			err = psf_unicode_addmap(f, n, m, n);
		if (err) return err;
	}
	return PSF_E_OK;
}

/* Add a (char, ucs) pair to the unicode directory. Does not check if
 * the entry exists already */

psf_errno_t unicode_add(PSF_UNICODE_DIR *f, psf_word nchar, psf_dword token)
{
	psf_unicode_dirent *ude, *ude2;

	if (psf_unicode_banned(token)) return PSF_E_BANNED;

	if (!f->psf_dirents_nfree)	/* Need to allocate some more */
					/* memory for the Unicode directory */
	{
		psf_unicode_buffer *nbuf = psf_malloc_unicode_buffer();
		if (!nbuf) return PSF_E_NOMEM;		
		/* Prepend the new buffer to the buffer list */

		nbuf->psfb_next = f->psf_dirents_buffer;
		f->psf_dirents_buffer = nbuf;

		/* Add the new free entries to the free entries list, which 
		 * should be empty */
		 nbuf->psfb_dirents[0].psfu_next = f->psf_dirents_free;
		f->psf_dirents_free  = 
		    &(nbuf->psfb_dirents[PSF_ENTRIES_PER_BUFFER - 1]);	
		f->psf_dirents_nfree += PSF_ENTRIES_PER_BUFFER;	 
			
	}
	/* Construct the new entry and take it off the free list */
	ude = f->psf_dirents_free;
	f->psf_dirents_free = ude->psfu_next;
	ude->psfu_next  = NULL;
	ude->psfu_token = token;

	/* Update "used" and "free" counts */
	--f->psf_dirents_nfree;
	++f->psf_dirents_nused;
	/* Go to the end of the linked list of existing entries to append
         * the new one */
	ude2 = f->psf_dirents_used[nchar];
	if (!ude2) 
	{
		f->psf_dirents_used[nchar] = ude;	
		return PSF_E_OK;
	}
	else while (ude2->psfu_next) ude2 = ude2->psfu_next;
	ude2->psfu_next = ude;
	return PSF_E_OK;
}

psf_errno_t psf_unicode_add(PSF_FILE *f, psf_word nchar, psf_dword token)
{
	if (nchar >= f->psf_length) return PSF_E_RANGE;
	if (!psf_is_unicode(f)) return PSF_E_ASCII;

	return unicode_add(&f->psf_dir, nchar, token);
}

psf_errno_t vfont_unicode_add(VFONT *f, psf_word nchar, psf_dword token)
{
	if (nchar >= f->vf_length) return PSF_E_RANGE;
	if (!vfont_is_unicode(f)) return PSF_E_ASCII;

	return unicode_add(&f->vf_dir, nchar, token);
}







/* Delete a (nchar, token) pair from the Unicode directory - if it's 
 * present */
static psf_errno_t uni_delete(PSF_UNICODE_DIR *f, psf_word nchar, psf_dword token)
{
	psf_unicode_dirent *ude, *ude2, *ude3;
	
	if (psf_unicode_banned(token)) return PSF_E_BANNED;

	ude  = f->psf_dirents_used[nchar];
	ude2 = NULL; 
	while (ude)
	{
		if (ude->psfu_token == token)
		{
			/* Take entry off "used" and add it to "free". */
			ude3 = ude->psfu_next;

			if (ude2) ude2->psfu_next = ude->psfu_next;
			else      f->psf_dirents_used[nchar] = ude->psfu_next;
			
			ude->psfu_next = f->psf_dirents_free;
			f->psf_dirents_free = ude;
			--f->psf_dirents_nused;
			++f->psf_dirents_nfree;
			ude = ude3;
			continue;
		}
		ude2 = ude;
		ude  = ude->psfu_next;
	}
	return PSF_E_OK;
	
}




psf_errno_t psf_unicode_delete(PSF_FILE *f, psf_word nchar, psf_dword token)
{
	if (!psf_is_unicode(f)) return PSF_E_ASCII;

	return uni_delete(&f->psf_dir, nchar, token);
}


psf_errno_t vfont_unicode_delete(VFONT *f, psf_word nchar, psf_dword token)
{
	if (!vfont_is_unicode(f)) return PSF_E_ASCII;

	return uni_delete(&f->vf_dir, nchar, token);
}



psf_errno_t psf_unicode_lookupmap(PSF_FILE *f, PSF_MAPPING *m, psf_word slot, 
		psf_dword *nchar, psf_dword *found)
{
	psf_dword *pw;
	psf_errno_t rv;

	if (slot >= m->psfm_count) return PSF_E_NOTFOUND;
       	pw = m->psfm_tokens[slot];
	if (!pw) return PSF_E_NOTFOUND;

	while (*pw != 0xFFFF)
	{
		if (*pw == 0x1FFFF) { ++pw; continue; }
		rv = psf_unicode_lookup(f, *pw, nchar);
		if (!rv) 
		{
			if (found) *found = *pw;
			return rv;
		}
		++pw;
	}
	return PSF_E_NOTFOUND;	
}



static psf_errno_t unicode_lookup(PSF_UNICODE_DIR *f, psf_dword nchars, psf_dword token, psf_dword *nchar)
{
	psf_unicode_dirent *ude;
	psf_dword n;
	if (psf_unicode_banned(token)) return PSF_E_BANNED;

	for (n = 0; n < nchars; n++)
	{
		for (ude = f->psf_dirents_used[n]; 
		     ude != NULL;
		     ude = ude->psfu_next)
		{
/* Don't look in multibyte sequences, only single-byte */
			if (ude->psfu_token == 0xFFFE) break;
			if (ude->psfu_token == token) 
			{
				if (nchar) *nchar = n;
				return PSF_E_OK;
			}
		}
	}
	return PSF_E_NOTFOUND;
}



/* Look up a token in the Unicode directory */
psf_errno_t psf_unicode_lookup(PSF_FILE *f, psf_dword token, psf_dword *nchar)
{
	if (!psf_is_unicode(f)) return PSF_E_ASCII;

	return unicode_lookup(&f->psf_dir, f->psf_length, token, nchar);
}

psf_errno_t vfont_unicode_lookup(VFONT *f, psf_dword token, psf_dword *nchar)
{
	if (!vfont_is_unicode(f)) return PSF_E_ASCII;

	return unicode_lookup(&f->vf_dir, f->vf_length, token, nchar);
}



