/*
 * Copyright 2022 the original author or authors.
 *
 * 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
 *
 *      https://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.
 */
package org.springframework.shell.test.jediterm.terminal.emulator.charset;

import org.springframework.shell.test.jediterm.terminal.util.CharUtils;

/**
 * Provides the (graphical) character sets.
 *
 * @author jediterm authors
 */
public final class CharacterSets {
	private static final int C0_START = 0;
	private static final int C0_END = 31;
	private static final int C1_START = 128;
	private static final int C1_END = 159;
	private static final int GL_START = 32;
	private static final int GL_END = 127;

	public static final String[] ASCII_NAMES = {"<nul>", "<soh>", "<stx>", "<etx>", "<eot>", "<enq>", "<ack>", "<bell>",
		"\b", "\t", "\n", "<vt>", "<ff>", "\r", "<so>", "<si>", "<dle>", "<dc1>", "<dc2>", "<dc3>", "<dc4>", "<nak>",
		"<syn>", "<etb>", "<can>", "<em>", "<sub>", "<esc>", "<fs>", "<gs>", "<rs>", "<us>", " ", "!", "\"", "#", "$",
		"%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "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", "[", "\\", "]", "^", "_", "`", "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", "{", "|",
		"}", "~", "<del>"};

	/**
	 * Denotes the mapping for C0 characters.
	 */
	public final static Object[][] C0_CHARS = {{0, "nul"}, //
		{0, "soh"}, //
		{0, "stx"}, //
		{0, "etx"}, //
		{0, "eot"}, //
		{0, "enq"}, //
		{0, "ack"}, //
		{0, "bel"}, //
		{(int) '\b', "bs"}, //
		{(int) '\t', "ht"}, //
		{(int) '\n', "lf"}, //
		{0, "vt"}, //
		{0, "ff"}, //
		{(int) '\r', "cr"}, //
		{0, "so"}, //
		{0, "si"}, //
		{0, "dle"}, //
		{0, "dc1"}, //
		{0, "dc2"}, //
		{0, "dc3"}, //
		{0, "dc4"}, //
		{0, "nak"}, //
		{0, "syn"}, //
		{0, "etb"}, //
		{0, "can"}, //
		{0, "em"}, //
		{0, "sub"}, //
		{0, "esq"}, //
		{0, "fs"}, //
		{0, "gs"}, //
		{0, "rs"}, //
		{0, "us"}};

	/**
	 * Denotes the mapping for C1 characters.
	 */
	public static final Object[][] C1_CHARS = {{0, null}, //
		{0, null}, //
		{0, null}, //
		{0, null}, //
		{0, "ind"}, //
		{0, "nel"}, //
		{0, "ssa"}, //
		{0, "esa"}, //
		{0, "hts"}, //
		{0, "htj"}, //
		{0, "vts"}, //
		{0, "pld"}, //
		{0, "plu"}, //
		{0, "ri"}, //
		{0, "ss2"}, //
		{0, "ss3"}, //
		{0, "dcs"}, //
		{0, "pu1"}, //
		{0, "pu2"}, //
		{0, "sts"}, //
		{0, "cch"}, //
		{0, "mw"}, //
		{0, "spa"}, //
		{0, "epa"}, //
		{0, null}, //
		{0, null}, //
		{0, null}, //
		{0, "csi"}, //
		{0, "st"}, //
		{0, "osc"}, //
		{0, "pm"}, //
		{0, "apc"}};

	/**
	 * The DEC special characters (only the last 32 characters).
	 * Contains [light][heavy] flavors for box drawing
	 */
	public static final Object[][] DEC_SPECIAL_CHARS = {{'\u25c6', null}, // black_diamond
		{'\u2592', null}, // Medium Shade
		{'\u2409', null}, // Horizontal tab (HT)
		{'\u240c', null}, // Form Feed (FF)
		{'\u240d', null}, // Carriage Return (CR)
		{'\u240a', null}, // Line Feed (LF)
		{'\u00b0', null}, // Degree sign
		{'\u00b1', null}, // Plus/minus sign
		{'\u2424', null}, // New Line (NL)
		{'\u240b', null}, // Vertical Tab (VT)
		{'\u2518', '\u251b'}, // Forms up and left
		{'\u2510', '\u2513'}, // Forms down and left
		{'\u250c', '\u250f'}, // Forms down and right
		{'\u2514', '\u2517'}, // Forms up and right
		{'\u253c', '\u254b'}, // Forms vertical and horizontal
		{'\u23ba', null}, // Scan 1
		{'\u23bb', null}, // Scan 3
		{'\u2500', '\u2501'}, // Scan 5 / Horizontal bar
		{'\u23bc', null}, // Scan 7
		{'\u23bd', null}, // Scan 9
		{'\u251c', '\u2523'}, // Forms vertical and right
		{'\u2524', '\u252b'}, // Forms vertical and left
		{'\u2534', '\u253b'}, // Forms up and horizontal
		{'\u252c', '\u2533'}, // Forms down and horizontal
		{'\u2502', '\u2503'}, // vertical bar
		{'\u2264', null}, // less than or equal sign
		{'\u2265', null}, // greater than or equal sign
		{'\u03c0', null}, // pi
		{'\u2260', null}, // not equal sign
		{'\u00a3', null}, // pound sign
		{'\u00b7', null}, // middle dot
		{' ', null}, //
	};

	public static boolean isDecBoxChar(char c) {
		if (c < '\u2500' || c >= '\u2580') { // fast path
			return false;
		}
		for (Object[] o : DEC_SPECIAL_CHARS) {
			if (c == (Character) o[0]) {
				return true;
			}
		}
		return false;
	}

	public static char getHeavyDecBoxChar(char c) {
		if (c < '\u2500' || c >= '\u2580') { // fast path
			return c;
		}
		for (Object[] o : DEC_SPECIAL_CHARS) {
			if (c == (Character) o[0]) {
				return o[1] != null ? (Character) o[1] : c;
			}
		}
		return c;
	}

	/**
	 * Creates a new {@link CharacterSets} instance, never used.
	 */
	private CharacterSets() {
		// Nop
	}

	// METHODS

	/**
	 * Returns the character mapping for a given original value using the given
	 * graphic sets GL and GR.
	 *
	 * @param original the original character to map;
	 * @param gl       the GL graphic set, cannot be <code>null</code>;
	 * @param gr       the GR graphic set, cannot be <code>null</code>.
	 * @return the mapped character.
	 */
	public static char getChar(char original, GraphicSet gl, GraphicSet gr) {
		Object[] mapping = getMapping(original, gl, gr);

		int ch = (Integer) mapping[0];
		if (ch > 0) {
			return (char)ch;
		}

		return CharUtils.NUL_CHAR;
	}

	/**
	 * Returns the name for the given character using the given graphic sets GL
	 * and GR.
	 *
	 * @param original the original character to return the name for;
	 * @param gl       the GL graphic set, cannot be <code>null</code>;
	 * @param gr       the GR graphic set, cannot be <code>null</code>.
	 * @return the character name.
	 */
	public static String getCharName(char original, GraphicSet gl, GraphicSet gr) {
		Object[] mapping = getMapping(original, gl, gr);

		String name = (String)mapping[1];
		if (name == null) {
			name = String.format("<%d>", (int)original);
		}

		return name;
	}

	/**
	 * Returns the mapping for a given character using the given graphic sets GL
	 * and GR.
	 *
	 * @param original the original character to map;
	 * @param gl       the GL graphic set, cannot be <code>null</code>;
	 * @param gr       the GR graphic set, cannot be <code>null</code>.
	 * @return the mapped character.
	 */
	private static Object[] getMapping(char original, GraphicSet gl, GraphicSet gr) {
		int mappedChar = original;
		if (original >= C0_START && original <= C0_END) {
			int idx = original - C0_START;
			return C0_CHARS[idx];
		}
		else if (original >= C1_START && original <= C1_END) {
			int idx = original - C1_START;
			return C1_CHARS[idx];
		}
		else if (original >= GL_START && original <= GL_END) {
			int idx = original - GL_START;
			mappedChar = gl.map(original, idx);
		}
		//To support UTF-8 we don't use GR table
		//TODO: verify that approach

		//else if (original >= GR_START && original <= GR_END) {
		//  int idx = original - GR_START;
		//  mappedChar = gr.map(original, idx);
		//}

		return new Object[]{mappedChar, null};
	}
}

