/*
 * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
/*
 *******************************************************************************
 * Copyright (C) 1996-2011, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */

package jdk_internal.icu.impl;

import java.io.IOException;
import java.util.Locale;

import jdk_internal.icu.lang.UCharacter;
import jdk_internal.icu.text.UTF16;

public final class Utility {

	/* This map must be in ASCENDING ORDER OF THE ESCAPE CODE */
	private static final char[] UNESCAPE_MAP = {
			/* " 0x22, 0x22 */
			/* ' 0x27, 0x27 */
			/* ? 0x3F, 0x3F */
			/* \ 0x5C, 0x5C */
			/* a */ 0x61, 0x07, /* b */ 0x62, 0x08, /* e */ 0x65, 0x1b, /* f */ 0x66, 0x0c, /* n */ 0x6E, 0x0a,
			/* r */ 0x72, 0x0d, /* t */ 0x74, 0x09, /* v */ 0x76, 0x0b };

	static final char DIGITS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
			'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };

	/**
	 * Convert characters outside the range U+0020 to U+007F to Unicode escapes, and
	 * convert backslash to a double backslash.
	 */
	public static final String escape(String s) {
		StringBuilder buf = new StringBuilder();
		for (int i = 0; i < s.length();) {
			int c = Character.codePointAt(s, i);
			i += UTF16.getCharCount(c);
			if (c >= ' ' && c <= 0x007F) {
				if (c == '\\') {
					buf.append("\\\\"); // That is, "\\"
				} else {
					buf.append((char) c);
				}
			} else {
				boolean four = c <= 0xFFFF;
				buf.append(four ? "\\u" : "\\U");
				buf.append(hex(c, four ? 4 : 8));
			}
		}
		return buf.toString();
	}

	/**
	 * Escape unprintable characters using <backslash>uxxxx notation for U+0000 to
	 * U+FFFF and <backslash>Uxxxxxxxx for U+10000 and above. If the character is
	 * printable ASCII, then do nothing and return FALSE. Otherwise, append the
	 * escaped notation and return TRUE.
	 */
	public static <T extends Appendable> boolean escapeUnprintable(T result, int c) {
		try {
			if (isUnprintable(c)) {
				result.append('\\');
				if ((c & ~0xFFFF) != 0) {
					result.append('U');
					result.append(DIGITS[0xF & (c >> 28)]);
					result.append(DIGITS[0xF & (c >> 24)]);
					result.append(DIGITS[0xF & (c >> 20)]);
					result.append(DIGITS[0xF & (c >> 16)]);
				} else {
					result.append('u');
				}
				result.append(DIGITS[0xF & (c >> 12)]);
				result.append(DIGITS[0xF & (c >> 8)]);
				result.append(DIGITS[0xF & (c >> 4)]);
				result.append(DIGITS[0xF & c]);
				return true;
			}
			return false;
		} catch (IOException e) {
			throw new IllegalArgumentException(e);
		}
	}

	/**
	 * Supplies a zero-padded hex representation of an integer (without 0x)
	 */
	public static String hex(long i, int places) {
		if (i == Long.MIN_VALUE)
			return "-8000000000000000";
		boolean negative = i < 0;
		if (negative) {
			i = -i;
		}
		String result = Long.toString(i, 16).toUpperCase(Locale.ENGLISH);
		if (result.length() < places) {
			result = "0000000000000000".substring(result.length(), places) + result;
		}
		if (negative) {
			return '-' + result;
		}
		return result;
	}

	/**
	 * Return true if the character is NOT printable ASCII. The tab, newline and
	 * linefeed characters are considered unprintable.
	 */
	public static boolean isUnprintable(int c) {
		// 0x20 = 32 and 0x7E = 126
		return !(c >= 0x20 && c <= 0x7E);
	}

	/**
	 * Convert an escape to a 32-bit code point value. We attempt to parallel the
	 * icu4c unescapeAt() function.
	 *
	 * @param offset16 an array containing offset to the character <em>after</em>
	 *                 the backslash. Upon return offset16[0] will be updated to
	 *                 point after the escape sequence.
	 * @return character value from 0 to 10FFFF, or -1 on error.
	 */
	public static int unescapeAt(String s, int[] offset16) {
		int c;
		int result = 0;
		int n = 0;
		int minDig = 0;
		int maxDig = 0;
		int bitsPerDigit = 4;
		int dig;
		int i;
		boolean braces = false;

		/* Check that offset is in range */
		int offset = offset16[0];
		int length = s.length();
		if (offset < 0 || offset >= length) {
			return -1;
		}

		/* Fetch first UChar after '\\' */
		c = Character.codePointAt(s, offset);
		offset += UTF16.getCharCount(c);

		/* Convert hexadecimal and octal escapes */
		switch (c) {
		case 'u':
			minDig = maxDig = 4;
			break;
		case 'U':
			minDig = maxDig = 8;
			break;
		case 'x':
			minDig = 1;
			if (offset < length && UTF16.charAt(s, offset) == 0x7B /* { */) {
				++offset;
				braces = true;
				maxDig = 8;
			} else {
				maxDig = 2;
			}
			break;
		default:
			dig = UCharacter.digit(c, 8);
			if (dig >= 0) {
				minDig = 1;
				maxDig = 3;
				n = 1; /* Already have first octal digit */
				bitsPerDigit = 3;
				result = dig;
			}
			break;
		}
		if (minDig != 0) {
			while (offset < length && n < maxDig) {
				c = UTF16.charAt(s, offset);
				dig = UCharacter.digit(c, (bitsPerDigit == 3) ? 8 : 16);
				if (dig < 0) {
					break;
				}
				result = (result << bitsPerDigit) | dig;
				offset += UTF16.getCharCount(c);
				++n;
			}
			if (n < minDig) {
				return -1;
			}
			if (braces) {
				if (c != 0x7D /* } */) {
					return -1;
				}
				++offset;
			}
			if (result < 0 || result >= 0x110000) {
				return -1;
			}
			// If an escape sequence specifies a lead surrogate, see
			// if there is a trail surrogate after it, either as an
			// escape or as a literal. If so, join them up into a
			// supplementary.
			if (offset < length && UTF16.isLeadSurrogate((char) result)) {
				int ahead = offset + 1;
				c = s.charAt(offset); // [sic] get 16-bit code unit
				if (c == '\\' && ahead < length) {
					int o[] = new int[] { ahead };
					c = unescapeAt(s, o);
					ahead = o[0];
				}
				if (UTF16.isTrailSurrogate((char) c)) {
					offset = ahead;
					result = UCharacterProperty.getRawSupplementary((char) result, (char) c);
				}
			}
			offset16[0] = offset;
			return result;
		}

		/* Convert C-style escapes in table */
		for (i = 0; i < UNESCAPE_MAP.length; i += 2) {
			if (c == UNESCAPE_MAP[i]) {
				offset16[0] = offset;
				return UNESCAPE_MAP[i + 1];
			} else if (c < UNESCAPE_MAP[i]) {
				break;
			}
		}

		/* Map \cX to control-X: X & 0x1F */
		if (c == 'c' && offset < length) {
			c = UTF16.charAt(s, offset);
			offset16[0] = offset + UTF16.getCharCount(c);
			return 0x1F & c;
		}

		/*
		 * If no special forms are recognized, then consider the backslash to
		 * generically escape the next character.
		 */
		offset16[0] = offset;
		return c;
	}
}
