/* $XTermId: xutf8.c,v 1.20 2025/04/06 23:29:27 tom Exp $ */

/*
 * Copyright 2002-2020,2025 by Thomas E. Dickey
 * Copyright (c) 2001 by Juliusz Chroboczek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <xterm.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/Xmu/Xmu.h>

#include <xutf8.h>
#include <keysym2ucs.h>

#ifndef X_HAVE_UTF8_STRING

#undef XA_UTF8_STRING

Atom
_xa_utf8_string(Display *dpy)
{
    static AtomPtr p = NULL;

    if (p == NULL)
	p = XmuMakeAtom("UTF8_STRING");

    return XmuInternAtom(dpy, p);
}
#define XA_UTF8_STRING(dpy) _xa_utf8_string(dpy)

static int
utf8countBytes(int c)
{
    if (c < 0)
	return 0;

    if (c <= 0x7F) {
	return 1;
    } else if (c <= 0x7FF) {
	return 2;
    } else if (c <= 0xFFFF) {
	return 3;
    } else
	return 4;
}

static void
utf8insert(char *dest, int c, size_t *len_return)
{
    if (c < 0)
	return;

    if (c <= 0x7F) {
	dest[0] = (char) c;
	*len_return = 1;
    } else if (c <= 0x7FF) {
	dest[0] = (char) (0xC0 | ((c >> 6) & 0x1F));
	dest[1] = (char) (0x80 | (c & 0x3F));
	*len_return = 2;
    } else if (c <= 0xFFFF) {
	dest[0] = (char) (0xE0 | ((c >> 12) & 0x0F));
	dest[1] = (char) (0x80 | ((c >> 6) & 0x3F));
	dest[2] = (char) (0x80 | (c & 0x3F));
	*len_return = 3;
    } else {
	dest[0] = (char) (0xF0 | ((c >> 18) & 0x07));
	dest[1] = (char) (0x80 | ((c >> 12) & 0x3f));
	dest[2] = (char) (0x80 | ((c >> 6) & 0x3f));
	dest[3] = (char) (0x80 | (c & 0x3f));
	*len_return = 4;
    }
}

static size_t
l1countUtf8Bytes(char *s, size_t len)
{
    size_t l = 0;
    while (len != 0) {
	if ((*s & 0x80) == 0)
	    l++;
	else
	    l += 2;
	s++;
	len--;
    }
    return l;
}

static void
l1utf8copy(char *d, char *s, size_t len)
{
    size_t l;
    while (len != 0) {
	utf8insert(d, (*s) & 0xFF, &l);
	d += (int) l;
	s++;
	len--;
    }
}

static void
utf8l1strcpy(char *d, char *s)
{
#define SKIP do { s++; } while(((*s & 0x80) != 0) && (*s & 0xC0) != 0xC0)
    while (*s) {
	if ((*s & 0x80) == 0)
	    *d++ = *s++;
	else if ((*s & 0x7C) == 0x40) {
	    if ((s[1] & 0x80) == 0) {
		s++;		/* incorrect UTF-8 */
		continue;
	    } else if ((*s & 0x7C) == 0x40) {
		*d++ = (char) (((*s & 0x03) << 6) | (s[1] & 0x3F));
		s += 2;
	    } else {
		*d++ = BAD_ASCII;
		SKIP;
	    }
	} else {
	    *d++ = BAD_ASCII;
	    SKIP;
	}
    }
    *d = 0;
#undef SKIP
}

/* Keep this in sync with utf8l1strcpy! */
static int
utf8l1strlen(char *s)
{
#define SKIP do { s++; } while(((*s & 0x80) != 0) && (*s & 0xC0) != 0xC0)
    int len = 0;
    while (*s) {
	if ((*s & 0x80) == 0) {
	    s++;
	    len++;
	} else if ((*s & 0x7C) == 0x40) {
	    if ((s[1] & 0x80) == 0) {
		s++;
		continue;
	    } else if ((*s & 0x7C) == 0x40) {
		len++;
		s += 2;
	    } else {
		len++;
		SKIP;
	    }
	} else {
	    len++;
	    SKIP;
	}
    }
#undef SKIP
    return len;
}

int
Xutf8TextPropertyToTextList(Display *dpy,
			    const XTextProperty * tp,
			    char ***list_return,
			    int *count_return)
{
    int utf8;
    char **list;
    int nelements;
    char *cp;
    char *start;
    size_t i;
    int j;
    size_t datalen = tp->nitems;
    size_t len;

    if (tp->format != 8)
	return XConverterNotFound;

    if (tp->encoding == XA_STRING)
	utf8 = 0;
    else if (tp->encoding == XA_UTF8_STRING(dpy))
	utf8 = 1;
    else
	return XConverterNotFound;

    if (datalen == 0) {
	*list_return = NULL;
	*count_return = 0;
	return 0;
    }

    nelements = 1;
    for (cp = (char *) tp->value, i = datalen; i != 0; cp++, i--) {
	if (*cp == '\0')
	    nelements++;
    }

    list = TypeMallocN(char *, (unsigned) nelements);
    if (!list)
	return XNoMemory;

    if (utf8)
	len = datalen;
    else
	len = l1countUtf8Bytes((char *) tp->value, datalen);

    start = malloc(len + 1);
    if (!start) {
	free(list);
	return XNoMemory;
    }

    if (utf8)
	memcpy(start, (char *) tp->value, datalen);
    else
	l1utf8copy(start, (char *) tp->value, datalen);
    start[len] = '\0';

    for (cp = start, i = len + 1, j = 0; i != 0; cp++, i--) {
	if (*cp == '\0') {
	    list[j] = start;
	    start = (cp + 1);
	    j++;
	}
    }

    list[j] = NULL;
    *list_return = list;
    *count_return = nelements;
    return 0;
}

int
Xutf8TextListToTextProperty(Display *dpy,
			    char **list,
			    int count,
			    XICCEncodingStyle style,
			    XTextProperty * text_prop)
{
    XTextProperty proto;
    unsigned int nbytes;
    int i;

    if (style != XStringStyle &&
	style != XCompoundTextStyle &&
	style != XStdICCTextStyle &&
	style != XUTF8StringStyle)
	return XConverterNotFound;

    if (style == XUTF8StringStyle) {
	for (i = 0, nbytes = 0; i < count; i++) {
	    nbytes += (unsigned) ((list[i] ? strlen(list[i]) : 0) + 1);
	}
    } else {
	for (i = 0, nbytes = 0; i < count; i++) {
	    nbytes += (unsigned) ((list[i] ? utf8l1strlen(list[i]) : 0) + 1);
	}
    }

    if (style == XCompoundTextStyle)
	proto.encoding = XA_COMPOUND_TEXT(dpy);
    else if (style == XUTF8StringStyle)
	proto.encoding = XA_UTF8_STRING(dpy);
    else
	proto.encoding = XA_STRING;
    proto.format = 8;
    if (nbytes)
	proto.nitems = nbytes - 1;
    else
	proto.nitems = 0;
    proto.value = NULL;

    if (nbytes > 0) {
	char *buf = TypeMallocN(char, nbytes);
	if (!buf)
	    return XNoMemory;

	proto.value = (unsigned char *) buf;
	for (i = 0; i < count; i++) {
	    char *arg = list[i];

	    if (arg) {
		if (style == XUTF8StringStyle) {
		    strcpy(buf, arg);
		} else {
		    utf8l1strcpy(buf, arg);
		}
		buf += (strlen(buf) + 1);
	    } else {
		*buf++ = '\0';
	    }
	}
    } else {
	proto.value = CastMalloc(unsigned char);	/* easier for client */
	if (!proto.value)
	    return XNoMemory;

	proto.value[0] = '\0';
    }

    *text_prop = proto;
    return 0;
}

int
Xutf8LookupString(XIC ic GCC_UNUSED,
		  XKeyEvent *ev,
		  char *buffer,
		  int nbytes,
		  KeySym * keysym_return,
		  Status * status_return)
{
    int rc;
    KeySym keysym;
    int codepoint;
    size_t len;

    rc = XLookupString(ev, buffer, nbytes, &keysym, NULL);

    if (rc > 0) {
	codepoint = buffer[0] & 0xFF;
    } else if (keysym < 0xfd00 || (keysym >= 0x1000000 && keysym < 0x10000000)) {
	codepoint = keysym2ucs(keysym);
    } else {
	codepoint = -1;
    }

    if (codepoint < 0) {
	if (keysym == None) {
	    *status_return = XLookupNone;
	} else {
	    *status_return = XLookupKeySym;
	    *keysym_return = keysym;
	}
	return 0;
    }

    if (nbytes < utf8countBytes(codepoint)) {
	*status_return = XBufferOverflow;
	return utf8countBytes(codepoint);
    }

    utf8insert(buffer, codepoint, &len);

    if (keysym != None) {
	*keysym_return = keysym;
	*status_return = XLookupBoth;
    } else {
	*status_return = XLookupChars;
    }
    return (int) len;
}

#else /* X_HAVE_UTF8_STRING */
/* Silence the compiler */
void
xutf8_dummy(void)
{
    return;
}
#endif
