/**
 * WinPR: Windows Portable Runtime
 * Unicode Conversion (CRT)
 *
 * Copyright 2022 Armin Novak <anovak@thincast.com>
 * Copyright 2022 Thincast Technologies GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <winpr/config.h>
#include <winpr/assert.h>
#include <winpr/string.h>

#include "../utils/android.h"
#include "unicode.h"

#ifndef MIN
#define MIN(a, b) (a) < (b) ? (a) : (b)
#endif

#include "../log.h"
#define TAG WINPR_TAG("unicode")

static int convert_int(JNIEnv* env, const void* data, size_t size, void* buffer, size_t buffersize,
                       BOOL toUTF16)
{
	WINPR_ASSERT(env);
	WINPR_ASSERT(data || (size == 0));
	WINPR_ASSERT(buffer || (buffersize == 0));

	jstring utf8 = (*env)->NewStringUTF(env, "UTF-8");
	jstring utf16 = (*env)->NewStringUTF(env, "UTF-16LE");
	jclass stringClass = (*env)->FindClass(env, "java/lang/String");

	if (!utf8 || !utf16 || !stringClass)
	{
		WLog_ERR(TAG, "utf8-%p, utf16=%p, stringClass=%p", utf8, utf16, stringClass);
		return -1;
	}

	jmethodID constructorID =
	    (*env)->GetMethodID(env, stringClass, "<init>", "([BLjava/lang/String;)V");
	jmethodID getBytesID =
	    (*env)->GetMethodID(env, stringClass, "getBytes", "(Ljava/lang/String;)[B");
	if (!constructorID || !getBytesID)
	{
		WLog_ERR(TAG, "constructorID=%p, getBytesID=%p", constructorID, getBytesID);
		return -2;
	}

	jbyteArray ret = (*env)->NewByteArray(env, size);
	if (!ret)
	{
		WLog_ERR(TAG, "NewByteArray(%" PRIuz ") failed", size);
		return -3;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, size, data);

	jobject obj = (*env)->NewObject(env, stringClass, constructorID, ret, toUTF16 ? utf8 : utf16);
	if (!obj)
	{
		WLog_ERR(TAG, "NewObject(String, byteArray, UTF-%d) failed", toUTF16 ? 16 : 8);
		return -4;
	}

	jbyteArray res = (*env)->CallObjectMethod(env, obj, getBytesID, toUTF16 ? utf16 : utf8);
	if (!res)
	{
		WLog_ERR(TAG, "CallObjectMethod(String, getBytes, UTF-%d) failed", toUTF16 ? 16 : 8);
		return -4;
	}

	jsize rlen = (*env)->GetArrayLength(env, res);
	if (buffersize > 0)
	{
		if (rlen > buffersize)
		{
			SetLastError(ERROR_INSUFFICIENT_BUFFER);
			return 0;
		}
		rlen = MIN(rlen, buffersize);
		(*env)->GetByteArrayRegion(env, res, 0, rlen, buffer);
	}

	if (toUTF16)
		rlen /= sizeof(WCHAR);

	return rlen;
}

static int convert(const void* data, size_t size, void* buffer, size_t buffersize, BOOL toUTF16)
{
	int rc;
	JNIEnv* env = nullptr;
	jboolean attached = winpr_jni_attach_thread(&env);
	rc = convert_int(env, data, size, buffer, buffersize, toUTF16);
	if (attached)
		winpr_jni_detach_thread();
	return rc;
}

int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
                            LPWSTR lpWideCharStr, int cchWideChar)
{
	size_t cbCharLen = (size_t)cbMultiByte;

	WINPR_UNUSED(dwFlags);

	/* If cbMultiByte is 0, the function fails */
	if ((cbMultiByte == 0) || (cbMultiByte < -1))
		return 0;

	if (cchWideChar < 0)
		return -1;

	if (cbMultiByte < 0)
	{
		const size_t len = strlen(lpMultiByteStr);
		if (len >= INT32_MAX)
			return 0;
		cbCharLen = (int)len + 1;
	}
	else
		cbCharLen = cbMultiByte;

	WINPR_ASSERT(lpMultiByteStr);
	switch (CodePage)
	{
		case CP_ACP:
		case CP_UTF8:
			break;

		default:
			WLog_ERR(TAG, "Unsupported encoding %u", CodePage);
			return 0;
	}

	return convert(lpMultiByteStr, cbCharLen, lpWideCharStr, cchWideChar * sizeof(WCHAR), TRUE);
}

int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
                            LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
                            LPBOOL lpUsedDefaultChar)
{
	size_t cbCharLen = (size_t)cchWideChar;

	WINPR_UNUSED(dwFlags);
	/* If cchWideChar is 0, the function fails */
	if ((cchWideChar == 0) || (cchWideChar < -1))
		return 0;

	if (cbMultiByte < 0)
		return -1;

	WINPR_ASSERT(lpWideCharStr);
	/* If cchWideChar is -1, the string is null-terminated */
	if (cchWideChar == -1)
	{
		const size_t len = _wcslen(lpWideCharStr);
		if (len >= INT32_MAX)
			return 0;
		cbCharLen = (int)len + 1;
	}
	else
		cbCharLen = cchWideChar;

	/*
	 * if cbMultiByte is 0, the function returns the required buffer size
	 * in bytes for lpMultiByteStr and makes no use of the output parameter itself.
	 */
	return convert(lpWideCharStr, cbCharLen * sizeof(WCHAR), lpMultiByteStr, cbMultiByte, FALSE);
}
