// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib.asn1;

import krb5.lib.Asn1Exception;
import krb5.lib.Krb5;

/**
 * Utilities
 */
public class util {

	/**
	 * Prints a given encoding reference
	 *
	 * @param ref is of type EncodeRef
	 * @param level is of type int
	 * @param tagged is of type boolean
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 */
	public static void print(EncodeRef ref, int level, boolean tagged)
		throws Asn1Exception {
		String filler = "";
		for (int i = 0; i < level; i++)
			filler += "    ";
		if (ref.method_num == asn1Method.PRIMITIVE) {
			if (!tagged)
				System.out.print(filler);
			if (ref.class_num == asn1Class.UNIVERSAL) {
				switch (ref.tag_num) {
					case asn1Type.END_OF_CONTENTS:
						break;
					case asn1Type.BIT_STRING:
						System.out.print("BIT_STRING { ");
						if (ref.length - 1 > 16)
							System.out.print("\n    " + filler);
						System.out.print(byte2hexString2(
							decode.BitString(ref), filler));
						if (ref.length - 1 > 16)
							System.out.print("\n" + filler);
						System.out.print("}\n");
						break;
					case asn1Type.BOOLEAN:
						System.out.print("BOOLEAN { ");
						if (decode.Boolean(ref))
							System.out.print("TRUE");
						else
							System.out.print("FALSE");
						System.out.print(" }\n");
						break;
					case asn1Type.INTEGER:
						System.out.print("INTEGER { ");
						System.out.print(decode.Integer(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.GeneralizedTime:
						System.out.print("GeneralizedTime { ");
						System.out.print(decode.GeneralizedTime(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.GeneralString:
						System.out.print("GeneralString { ");
						System.out.print(decode.GeneralString(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.IA5String:
						System.out.print("IA5String { ");
						System.out.print(decode.IA5String(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.NULL:
						System.out.print("NULL\n");
						decode.Null(ref.data, ref.index);
						break;
					case asn1Type.NumericString:
						System.out.print("NumericString { ");
						System.out.print(decode.NumericString(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.OBJECT_IDENTIFIER:
						System.out.print("OBJECT_IDENTIFIER { ");
						int[] oid = decode.ObjectIdentifier(ref);
						for (int i = 0; i < oid.length; i++)
							System.out.print(oid[i] + " ");
						System.out.print("}\n");
						break;
					case asn1Type.OCTET_STRING:
						System.out.print("OCTET_STRING { ");
						if (ref.length > 16)
							System.out.print("\n    " + filler);
						System.out.print(byte2hexString2(
							decode.OctetString(ref), filler));
						if (ref.length > 16)
							System.out.print("\n" + filler);
						System.out.print("}\n");
						break;
					case asn1Type.PrintableString:
						System.out.print("PrintableString { ");
						System.out.print(decode.PrintableString(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.T61String:
						System.out.print("T61String { ");
						System.out.print(decode.T61String(ref));
						System.out.print(" }\n");
						break;
					case asn1Type.UTCTime:
						System.out.print("UTCTime { ");
						System.out.print(decode.UTCTime(ref));
						System.out.print(" }\n");
						break;
					default:
						//XXX there are actually more, e.g., REAL
						throw new Asn1Exception(Krb5.ASN1_UNSUPPORTED_TYPE);
				}
			}
			else
				System.out.print("IMPLICIT\n");
		}
		else {
			if (ref.method_num == asn1Method.CONSTRUCTED) {
				if (ref.class_num == asn1Class.UNIVERSAL) {
					if (ref.tag_num == asn1Type.SEQUENCE) {
						if (!tagged)
							System.out.print(filler);
						System.out.print("SEQUENCE {\n");
						EncodeRef subRef = ref.startOfSequence();
						while (subRef.isMoreInSequence()) {
							print(subRef, level + 1, false);
							subRef.next();
						}
						System.out.print(filler);
						System.out.print("}\n");
					}
					else
						if (ref.tag_num == asn1Type.SET) {
							if (!tagged)
								System.out.print(filler);
							System.out.print("SET {\n");
							EncodeRef subRef = ref.startOfSequence();
							while (subRef.isMoreInSequence()) {
								print(subRef, level + 1, false);
								subRef.next();
							}
							System.out.print(filler);
							System.out.print("}\n");
						}
						else
							throw new Asn1Exception(Krb5.ASN1_UNSUPPORTED_TYPE);
				}
				else {
					if (!tagged)
						System.out.print(filler);
					System.out.print("[");
					if (ref.class_num == asn1Class.APPLICATION)
						System.out.print("APPLICATION ");
					if (ref.class_num == asn1Class.PRIVATE)
						System.out.print("PRIVATE ");
					if (ref.class_num == asn1Class.UNIVERSAL)
						System.out.print("UNIVERSAL ");
					System.out.print(ref.tag_num + "] ");
					print(ref.startOfData(), level, true);
				}
			}
			else
				throw new Asn1Exception(Krb5.ASN1_PARSE_ERROR);
		}
	}

	/**
	 * Prints a encoding reference in byte array with a given index
	 *
	 * @param data is of type byte[]
	 * @param index is of type int
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public static void print(byte[] data, int index) throws Asn1Exception {
		print(new EncodeRef(data, index), 0, false);
	}

	/**
	 * Prints a encoding reference in byte array 
	 *
	 * @param data is of type byte[]
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public static void print(byte[] data) throws Asn1Exception {
		print(new EncodeRef(data), 0, false);
	}

	/**
	 * Hexidecimal digit
	 */
	public static final String[] hex_digit = {
		"0", "1", "2", "3", "4", "5", "6", "7",
		"8", "9", "a", "b", "c", "d", "e", "f"
	};

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param x is of type byte
	 */
	public static String byte2hexString(byte x) {
		String s = "";
		for (int i = 0; i < 2; i++) {
			s = hex_digit[((((int)x) & 0xff) &
				(15 << (i * 4))) >>> (i * 4)] + s;
		}
		return s + " ";
	}

	/**
	 * Converts integer to hex string
	 *
	 * @return String is a result
	 * @param x is of type int
	 */
	public static String int2hexString(int x) {
		String s = "";
		for (int i = 0; i < 8; i++) {
			s = hex_digit[(x & (15 << (i * 4))) >>> (i * 4)] + s;
		}
		return s;
	}

	/**
	 * Convert integer to binary string
	 *
	 * @return String is a result
	 * @param x is of type int
	 */
	public static String int2binString(int x) {
		String s = "";
		for (int i = 0; i < 32; i++) {
			if ((i > 0) && (i % 4 == 0)) s = " " + s;
			s = hex_digit[(x & (1 << i)) >>> i] + s;
		}
		return s;
	}

	/**
	 * Converts long to hex string
	 *
	 * @return String is a result
	 * @param x is of type long
	 */
	public static String long2hexString(long x) {
		String s = "";
		for (int i = 0; i < 16; i++) {
			s = hex_digit[(int)((x & (15L << (i * 4))) >>> (i * 4))] + s;
		}
		return s;
	}

	/**
	 * Converts long to binary string
	 *
	 * @return String is a result
	 * @param x is of type long
	 */
	public static String long2binString(long x) {
		String s = "";
		for (int i = 0; i < 64; i++) {
			if ((i > 0) && (i % 4 == 0)) s = " " + s;
			s = hex_digit[(int)((x & (1L << i)) >>> i)] + s;
		}
		return s;
	}

	/**
	 * Converts octet to long
	 *
	 * @return long is a result
	 * @param input is of type byte[]
	 */
	public static long octet2long(byte[] input) {
		return octet2long(input, 0);
	}

	/**
	 * Converts octet to long
	 *
	 * @return long is a result
	 * @param input is of type byte[]
	 * @param offset is of type int
	 */
	public static long octet2long(byte[] input, int offset) {
		long result = 0;
		for (int i = 0; i < 8; i++) {
			if (i + offset < input.length) {
				result |= (((long)input[i + offset]) & 0xffL) <<
					((7 - i) * 8);
			}
		}
		return result;
	}

	/**
	 * Converts long to octet
	 *
	 * @return byte[] is a result
	 * @param input is of type long
	 */
	public static byte[] long2octet(long input) {
		byte[] output = new byte[8];
		for (int i = 0; i < 8; i++) {
			output[i] =
				(byte)((input & (0xffL << ((7 - i) * 8))) >>>
				((7 - i) * 8));
		}
		return output;
	}

	/**
	 * Converts long to octet
	 *
	 * @param input is of type long
	 * @param output is of type byte[]
	 */
	public static void long2octet(long input, byte[] output) {
		long2octet(input, output, 0);
	}

	/**
	 * Converts long to octet
	 *
	 * @param input is of type long
	 * @param output is of type byte[]
	 * @param offset is of type int
	 */
	public static void long2octet(long input, byte[] output, int offset) {
		for (int i = 0; i < 8; i++) {
			if (i + offset < output.length) {
				output[i + offset] =
					(byte)((input & (0xffL << ((7 - i) * 8))) >>>
					((7 - i) * 8));
			}
		}
	}

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param input is of type byte[]
	 */
	public static String byte2hexString(byte[] input) {
		return byte2hexString(input, 0, input.length);
	}

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param input is of type byte[]
	 * @param filler is of type String
	 */
	public static String byte2hexString2(byte[] input, String filler) {
		return byte2hexString2(input, 0, input.length, filler);
	}

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param input is of type byte[]
	 * @param offset is of type int
	 */
	public static String byte2hexString(byte[] input, int offset) {
		return byte2hexString(input, offset, input.length);
	}

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param input is of type byte[]
	 * @param offset is of type int
	 * @param length is of type int
	 */
	public static String byte2hexString(byte[] input, int offset,
		int length) {
		String result = "";
		for (int i = 0; i < length; i++) {
			if (i + offset < input.length) {
				if (i > 0 && i % 16 == 0)
					result += "\n";
				result += byte2hexString(input[i + offset]);
			}
		}
		return result;
	}

	/**
	 * Converts byte to hex string
	 *
	 * @return String is a result
	 * @param input is of type byte[]
	 * @param offset is of type int
	 * @param length is of type int
	 * @param filler is of type String
	 */
	public static String byte2hexString2(byte[] input, int offset,
		int length, String filler) {
		String result = "";
		for (int i = 0; i < length; i++) {
			if (i + offset < input.length) {
				if (i > 0 && i % 16 == 0)
					result += "\n" + filler + "    ";
				result += byte2hexString(input[i + offset]);
			}
		}
		return result;
	}

	/**
	 * Converts string to byte
	 *
	 * @return byte[] is a result
	 * @param str is of type String
	 */
	public static byte[] string2byte(String str) {
		byte[] result = new byte[str.length()];
		str.getBytes(0, str.length(), result, 0);
		return result;
	}

}