/*
 * 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) 2004-2014, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 *
 *******************************************************************************
 *   file name:  UBiDiProps.java
 *   encoding:   US-ASCII
 *   tab size:   8 (not used)
 *   indentation:4
 *
 *   created on: 2005jan16
 *   created by: Markus W. Scherer
 *
 *   Low-level Unicode bidi/shaping properties access.
 *   Java port of ubidi_props.h/.c.
 */

package jdk_internal.icu.impl;

import java.io.IOException;
import java.nio.ByteBuffer;

import jdk_internal.icu.lang.UCharacter;

public final class UBiDiProps {
	// constructors etc. --------------------------------------------------- ***

	// implement ICUBinary.Authenticate
	private static final class IsAcceptable implements ICUBinary.Authenticate {
		@Override
		public boolean isDataVersionAcceptable(byte version[]) {
			return version[0] == 2;
		}
	}

	// data format constants ----------------------------------------------- ***
	@SuppressWarnings("deprecation")
	private static final String DATA_FILE_NAME = "/assets/eagler/icudt/ubidi.icu";

	/* format "BiDi" */
	private static final int FMT = 0x42694469;

	// property access functions ------------------------------------------- ***

	/* indexes into indexes[] */
	private static final int IX_TRIE_SIZE = 2;

	private static final int IX_MIRROR_LENGTH = 3;

	private static final int IX_JG_START = 4;

	private static final int IX_JG_LIMIT = 5;

	private static final int IX_JG_START2 = 6; /* new in format version 2.2, ICU 54 */

	private static final int IX_JG_LIMIT2 = 7;

	private static final int IX_TOP = 16;

	/* CLASS_SHIFT=0, */ /* bidi class: 5 bits (4..0) */
	private static final int JT_SHIFT = 5; /* joining type: 3 bits (7..5) */
	private static final int BPT_SHIFT = 8; /* Bidi_Paired_Bracket_Type(bpt): 2 bits (9..8) */
	private static final int MIRROR_DELTA_SHIFT = 13; /* bidi mirroring delta: 3 bits (15..13) */
	private static final int CLASS_MASK = 0x0000001f;

	private static final int JT_MASK = 0x000000e0;

	private static final int BPT_MASK = 0x00000300;

	private static final int ESC_MIRROR_DELTA = -4;

	/* the source Unicode code point takes 21 bits (20..0) */
	private static final int MIRROR_INDEX_SHIFT = 21;
	/*
	 * public singleton instance
	 */
	public static final UBiDiProps INSTANCE;

	// This static initializer block must be placed after
	// other static member initialization
	static {
		try {
			INSTANCE = new UBiDiProps();
		} catch (IOException e) {
			throw new RuntimeException("Missing resource: \"" + DATA_FILE_NAME + "\"; Reason: " + e.getMessage());
		}
	}

	private static final int getClassFromProps(int props) {
		return props & CLASS_MASK;
	}

	private static final boolean getFlagFromProps(int props, int shift) {
		return ((props >> shift) & 1) != 0;
	}

	private static final int getMirrorCodePoint(int m) {
		return m & 0x1fffff;
	}

	private static final int getMirrorDeltaFromProps(int props) {
		return (short) props >> MIRROR_DELTA_SHIFT;
	}

	// definitions for 16-bit bidi/shaping properties word ----------------- ***

	private static final int getMirrorIndex(int m) {
		return m >>> MIRROR_INDEX_SHIFT;
	}

	// data members -------------------------------------------------------- ***
	private int indexes[];

	private int mirrors[];

	private byte jgArray[];
	private byte jgArray2[];
	private Trie2_16 trie;

	// port of ubidi_openProps()
	private UBiDiProps() throws IOException {
		ByteBuffer bytes = ICUBinary.getRequiredData(DATA_FILE_NAME);
		readData(bytes);
	}

	public final int getClass(int c) {
		return getClassFromProps(trie.get(c));
	}

	public final int getJoiningGroup(int c) {
		int start, limit;

		start = indexes[IX_JG_START];
		limit = indexes[IX_JG_LIMIT];
		if (start <= c && c < limit) {
			return (int) jgArray[c - start] & 0xff;
		}
		start = indexes[IX_JG_START2];
		limit = indexes[IX_JG_LIMIT2];
		if (start <= c && c < limit) {
			return (int) jgArray2[c - start] & 0xff;
		}
		return UCharacter.JoiningGroup.NO_JOINING_GROUP;
	}

	public final int getJoiningType(int c) {
		return (trie.get(c) & JT_MASK) >> JT_SHIFT;
	}

	// definitions for 32-bit mirror table entry --------------------------- ***

	public final int getMirror(int c) {
		int props = trie.get(c);
		return getMirror(c, props);
	}

	private final int getMirror(int c, int props) {
		int delta = getMirrorDeltaFromProps(props);
		if (delta != ESC_MIRROR_DELTA) {
			return c + delta;
		} else {
			/* look for mirror code point in the mirrors[] table */
			int m;
			int i, length;
			int c2;

			length = indexes[IX_MIRROR_LENGTH];

			/* linear search */
			for (i = 0; i < length; ++i) {
				m = mirrors[i];
				c2 = getMirrorCodePoint(m);
				if (c == c2) {
					/* found c, return its mirror code point using the index in m */
					return getMirrorCodePoint(mirrors[getMirrorIndex(m)]);
				} else if (c < c2) {
					break;
				}
			}

			/* c not found, return it itself */
			return c;
		}
	}

	public final int getPairedBracket(int c) {
		int props = trie.get(c);
		if ((props & BPT_MASK) == 0) {
			return c;
		} else {
			return getMirror(c, props);
		}
	}

	public final int getPairedBracketType(int c) {
		return (trie.get(c) & BPT_MASK) >> BPT_SHIFT;
	}

	private void readData(ByteBuffer bytes) throws IOException {
		// read the header
		ICUBinary.readHeader(bytes, FMT, new IsAcceptable());

		// read indexes[]
		int i, count;
		count = bytes.getInt();
		if (count < IX_TOP) {
			throw new IOException("indexes[0] too small in " + DATA_FILE_NAME);
		}
		indexes = new int[count];

		indexes[0] = count;
		for (i = 1; i < count; ++i) {
			indexes[i] = bytes.getInt();
		}

		// read the trie
		trie = Trie2_16.createFromSerialized(bytes);
		int expectedTrieLength = indexes[IX_TRIE_SIZE];
		int trieLength = trie.getSerializedLength();
		if (trieLength > expectedTrieLength) {
			throw new IOException(DATA_FILE_NAME + ": not enough bytes for the trie");
		}
		// skip padding after trie bytes
		ICUBinary.skipBytes(bytes, expectedTrieLength - trieLength);

		// read mirrors[]
		count = indexes[IX_MIRROR_LENGTH];
		if (count > 0) {
			mirrors = new int[count];
			for (i = 0; i < count; ++i) {
				mirrors[i] = bytes.getInt();
			}
		}

		// read jgArray[]
		count = indexes[IX_JG_LIMIT] - indexes[IX_JG_START];
		jgArray = new byte[count];
		for (i = 0; i < count; ++i) {
			jgArray[i] = bytes.get();
		}

		// read jgArray2[]
		count = indexes[IX_JG_LIMIT2] - indexes[IX_JG_START2];
		jgArray2 = new byte[count];
		for (i = 0; i < count; ++i) {
			jgArray2[i] = bytes.get();
		}
	}
}
