/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * IBM - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.utils.som;

import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.utils.coff.ReadMemoryAccess;

/**
 * Representation of a HP-UX SOM binary format
 *
 * @author vhirsl
 */
public class SOM {
	public static final String NL = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$

	String filename;
	FileHeader filehdr;
	RandomAccessFile rfile;
	long startingOffset;
	byte[] string_table;
	Symbol[] symbols;

	/**
	 * SOM Header record
	 *
	 * @author vhirsl
	 */
	public static class FileHeader {
		public final static int FILHSZ = 32 * 4;

		// Consts
		public static final short PA_RISC_10 = 0x20b;
		public static final short PA_RISC_11 = 0x210;
		public static final short PA_RISC_20 = 0x214;

		public static final short EXE_SOM_LIB = 0x104; // executable SOM library
		public static final short REL_SOM = 0x106; // relocatable SOM
		public static final short PRIV_EXEC_SOM = 0x107; // non-sharable, executable SOM
		public static final short SHARE_EXEC_SOM = 0x108; // sharable, executable SOM
		public static final short SHARE_DEMAND_LOAD_EXE_SOM = 0x10b; // sharable, demand-loadable executable SOM
		public static final short DYN_LOAD_LIB = 0x10d; // dynamic load library
		public static final short SHARED_LIB = 0x10e; // shared library
		public static final short RELOC_SOM_LIB = 0x619; // relocatable SOM library

		// Fields
		public short system_id; // magic number - system
		public short a_magic; // magic number - file type
		public int version_id; // version id; format = YYMMDDHH
		public long file_time_sec; // system clock - zero if unused
		public long file_time_nano; // system clock - zero if unused
		public int entry_space; // index of space containing entry point
		public int entry_subspace; // index of subspace for entry point
		public int entry_offset; // offset of entry point
		public int aux_header_location; // auxiliary header location
		public int aux_header_size; // auxiliary header size
		public int som_length; // length in bytes of entire som
		public int presumed_dp; // DP value assumed during compilation
		public int space_location; // location in file of space dictionary
		public int space_total; // number of space entries
		public int subspace_location; // location of subspace entries
		public int subspace_total; // number of subspace entries
		public int loader_fixup_location; // MPE/iX loader fixup
		public int loader_fixup_total; // number of loader fixup records
		public int space_strings_location; // file location of string area for space and subspace names
		public int space_strings_size; // size of string area for space and subspace names
		public int init_array_location; // reserved for use by system
		public int init_array_total; // reserved for use by system
		public int compiler_location; // location in file of module dictionary
		public int compiler_total; // number of modules
		public int symbol_location; // location in file of symbol dictionary
		public int symbol_total; // number of symbol records
		public int fixup_request_location; // location in file of fix-up requests
		public int fixup_request_total; // number of fixup requests
		public int symbol_strings_location; // file location of string area for module and symbol names
		public int symbol_strings_size; // size of string area for module and symbol names
		public int unloadable_sp_location; // byte offset of first byte of datafor unloadable spaces
		public int unloadable_sp_size; // byte length of data for unloadable spaces
		public int checksum;

		public FileHeader(RandomAccessFile file) throws IOException {
			this(file, file.getFilePointer());
		}

		public FileHeader(RandomAccessFile file, long offset) throws IOException {
			file.seek(offset);
			byte[] hdr = new byte[FILHSZ];
			file.readFully(hdr);
			commonSetup(hdr, false);
		}

		public FileHeader(byte[] hdr, boolean little) throws IOException {
			commonSetup(hdr, little);
		}

		public void commonSetup(byte[] hdr, boolean little) throws IOException {
			if (hdr == null || hdr.length < FILHSZ) {
				throw new EOFException(CCorePlugin.getResourceString("Util.exception.arrayToSmall")); //$NON-NLS-1$
			}
			if (!isSOMHeader(hdr)) {
				throw new IOException(CCorePlugin.getResourceString("Util.exception.notSOM")); //$NON-NLS-1$
			}
			ReadMemoryAccess memory = new ReadMemoryAccess(hdr, little);

			system_id = memory.getShort();
			a_magic = memory.getShort();
			version_id = memory.getInt();
			file_time_sec = memory.getInt();
			file_time_nano = memory.getInt();
			entry_space = memory.getInt();
			entry_subspace = memory.getInt();
			entry_offset = memory.getInt();
			aux_header_location = memory.getInt();
			aux_header_size = memory.getInt();
			som_length = memory.getInt();
			presumed_dp = memory.getInt();
			space_location = memory.getInt();
			space_total = memory.getInt();
			subspace_location = memory.getInt();
			subspace_total = memory.getInt();
			loader_fixup_location = memory.getInt();
			loader_fixup_total = memory.getInt();
			space_strings_location = memory.getInt();
			space_strings_size = memory.getInt();
			init_array_location = memory.getInt();
			init_array_total = memory.getInt();
			compiler_location = memory.getInt();
			compiler_total = memory.getInt();
			symbol_location = memory.getInt();
			symbol_total = memory.getInt();
			fixup_request_location = memory.getInt();
			fixup_request_total = memory.getInt();
			symbol_strings_location = memory.getInt();
			symbol_strings_size = memory.getInt();
			unloadable_sp_location = memory.getInt();
			unloadable_sp_size = memory.getInt();
			checksum = memory.getInt();
		}

		@Override
		public String toString() {
			StringBuilder buffer = new StringBuilder();
			buffer.append("FILE HEADER VALUES").append(NL); //$NON-NLS-1$

			buffer.append("system_id               = ").append(system_id).append(NL); //$NON-NLS-1$
			buffer.append("a_magic                 = ").append(a_magic).append(NL); //$NON-NLS-1$
			buffer.append("version_id              = ").append(version_id).append(NL); //$NON-NLS-1$
			buffer.append("file_time_sec           = ").append(file_time_sec).append(NL); //$NON-NLS-1$
			buffer.append("file_time_nano          = ").append(file_time_nano).append(NL); //$NON-NLS-1$
			buffer.append("entry_space             = ").append(entry_space).append(NL); //$NON-NLS-1$
			buffer.append("entry_subspace          = ").append(entry_subspace).append(NL); //$NON-NLS-1$
			buffer.append("aux_header_location     = ").append(aux_header_location).append(NL); //$NON-NLS-1$
			buffer.append("aux_header_size         = ").append(aux_header_size).append(NL); //$NON-NLS-1$
			buffer.append("som_length              = ").append(som_length).append(NL); //$NON-NLS-1$
			buffer.append("presumed_dp             = ").append(presumed_dp).append(NL); //$NON-NLS-1$
			buffer.append("space_location          = ").append(space_location).append(NL); //$NON-NLS-1$
			buffer.append("space_total             = ").append(space_total).append(NL); //$NON-NLS-1$
			buffer.append("subspace_location       = ").append(subspace_location).append(NL); //$NON-NLS-1$
			buffer.append("subspace_total          = ").append(subspace_total).append(NL); //$NON-NLS-1$
			buffer.append("loader_fixup_location   = ").append(loader_fixup_location).append(NL); //$NON-NLS-1$
			buffer.append("loader_fixup_total      = ").append(loader_fixup_total).append(NL); //$NON-NLS-1$
			buffer.append("space_strings_location  = ").append(space_strings_location).append(NL); //$NON-NLS-1$
			buffer.append("space_strings_size      = ").append(space_strings_size).append(NL); //$NON-NLS-1$
			buffer.append("init_array_location     = ").append(init_array_location).append(NL); //$NON-NLS-1$
			buffer.append("init_array_total        = ").append(init_array_total).append(NL); //$NON-NLS-1$
			buffer.append("compiler_location       = ").append(compiler_location).append(NL); //$NON-NLS-1$
			buffer.append("compiler_total          = ").append(compiler_total).append(NL); //$NON-NLS-1$
			buffer.append("symbol_location         = ").append(symbol_location).append(NL); //$NON-NLS-1$
			buffer.append("symbol_total            = ").append(symbol_total).append(NL); //$NON-NLS-1$
			buffer.append("fixup_request_location  = ").append(fixup_request_location).append(NL); //$NON-NLS-1$
			buffer.append("fixup_request_total     = ").append(fixup_request_total).append(NL); //$NON-NLS-1$
			buffer.append("symbol_strings_location = ").append(symbol_strings_location).append(NL); //$NON-NLS-1$
			buffer.append("symbol_strings_size     = ").append(symbol_strings_size).append(NL); //$NON-NLS-1$
			buffer.append("unloadable_sp_location  = ").append(unloadable_sp_location).append(NL); //$NON-NLS-1$
			buffer.append("unloadable_sp_size      = ").append(unloadable_sp_size).append(NL); //$NON-NLS-1$
			buffer.append("checksum                = ").append(checksum).append(NL); //$NON-NLS-1$
			return buffer.toString();
		}
	}

	public class Symbol {
		public static final int SYMSZ = 5 * 4; // 5 words = 20 bytes
		// masks
		// NOTE: HP-UX denotes bit 0 as a leftmost bit in a word
		// following representation denotes bit 0 as a rightmost bit in a word
		public static final int B31_MASK = 0x80000000;
		public static final int B30_MASK = 0x40000000;
		public static final int B29_24_MASK = 0x3f000000;
		public static final int B23_20_MASK = 0x00f00000;
		public static final int B19_17_MASK = 0x000e0000;
		public static final int B16_MASK = 0x00010000;
		public static final int B15_MASK = 0x00008000;
		public static final int B14_MASK = 0x00004000;
		public static final int B13_MASK = 0x00002000;
		public static final int B12_MASK = 0x00001000;
		public static final int B11_10_MASK = 0x00000C00;
		public static final int B9_0_MASK = 0x000003ff;
		public static final int B23_0_MASK = 0x00ffffff;
		public static final int B7_0_MASK = 0x000000ff;

		// symbol type
		public static final int NULL = 0; // Invalid symbol record
		public static final int ABSOLUTE = 1; // Absolute constant
		public static final int DATA = 2; // Normal initialized data
		public static final int CODE = 3; // Unspecified code
		public static final int PRI_PROG = 4; // Primary program entry point
		public static final int SEC_PROG = 5; // Secondary program entry point
		public static final int ENTRY = 6; // Any code entry point
		public static final int STORAGE = 7; // Uninitialized common data blocks
		public static final int STUB = 8; // Import external call stub or a parameter relocation stub
		public static final int MODULE = 9; // Source module name
		public static final int SYM_EXT = 10; // Extension record of the current entry
		public static final int ARG_EXT = 11; // -||-
		public static final int MILLICODE = 12; // Millicode routine
		public static final int PLABEL = 13; // Export stub for a procedure
		public static final int OCT_DIS = 14; // Pointer to translated code segment exists but disabled
		public static final int MILLI_EXT = 15; // Address of an external millicode routine
		public static final int ST_DATA = 15; // Thread specific data

		// symbol scope
		public static final int UNSAT = 0; // Import request that has not been satisfied
		public static final int EXTERNAL = 1; // Import request linked to a symbol in another SOM
		public static final int LOCAL = 2; // The symbol is not exported for use outside the SOM
		public static final int UNIVERSAL = 3; // The symbol is exported for use outside the SOM

		// fields
		public boolean hidden; // W1 b31
		public boolean secondary_def; // W1 b30
		public int symbol_type; // W1 b29-24
		public int symbol_scope; // W1 b23-20
		public int check_level; // W1 b19-17
		public boolean must_qualify; // W1 b16
		public boolean initially_frozen; // W1 b15
		public boolean memory_resident; // W1 b14
		public boolean is_common; // W1 b13
		public boolean dup_common; // W1 b12
		public int xleast; // W1 b11-10
		public int arg_reloc; // W1 b9-0
		public int name_offset; // W2
		public int qualifier_name_offset; // W3
		public boolean has_long_return; // W4 b31
		public boolean no_relocation; // W4 b30
		public int symbol_info; // W4 b23-0
		public int symbol_value; // W5

		public Symbol(RandomAccessFile file) throws IOException {
			this(file, file.getFilePointer());
		}

		public Symbol(RandomAccessFile file, long offset) throws IOException {
			file.seek(offset);
			byte[] bytes = new byte[SYMSZ];
			file.readFully(bytes);
			ReadMemoryAccess memory = new ReadMemoryAccess(bytes, false); // big endian
			// first word
			int word = memory.getInt();
			hidden = (word & B31_MASK) != 0;
			secondary_def = (word & B30_MASK) != 0;
			symbol_type = (word & B29_24_MASK) >> 24;
			symbol_scope = (word & B23_20_MASK) >> 20;
			check_level = (word & B19_17_MASK) >> 17;
			must_qualify = (word & B16_MASK) != 0;
			initially_frozen = (word & B15_MASK) != 0;
			memory_resident = (word & B14_MASK) != 0;
			is_common = (word & B13_MASK) != 0;
			dup_common = (word & B12_MASK) != 0;
			xleast = (word & B11_10_MASK) >> 10;
			arg_reloc = word & B9_0_MASK;
			// second word
			name_offset = memory.getInt();
			// third word
			qualifier_name_offset = memory.getInt();
			// fourth word
			word = memory.getInt();
			has_long_return = (word & B31_MASK) != 0;
			no_relocation = (word & B30_MASK) != 0;
			symbol_info = word & B23_0_MASK;
			// fifth word
			symbol_value = memory.getInt();

			// check for symbol extension record and descriptor array records
			if (check_level >= 1) {
				// bytes = new byte[SYMSZ];
				file.readFully(bytes);
				memory = new ReadMemoryAccess(bytes, false); // big endian
				// an extension record is present (size 5 words = 20 bytes)
				word = memory.getInt();
				int num_args = word & B7_0_MASK;
				// check for argument descriptor arrays
				if (num_args > 3 && check_level >= 3) {
					int num_descs = (num_args - 3) % 4 == 0 ? (num_args - 3) / 4 : (num_args - 3) / 4 + 1;
					for (int i = 0; i < num_descs; ++i) {
						file.readFully(bytes);
					}
				}
			}
		}

		public String getName(byte[] table) {
			if (qualifier_name_offset != 0) {
				byte[] len = new byte[4];
				System.arraycopy(table, qualifier_name_offset - 4, len, 0, 4);
				ReadMemoryAccess memory = new ReadMemoryAccess(len, false); // big endian
				int length = memory.getInt();
				return new String(table, qualifier_name_offset, length);
			}
			if (name_offset != 0) {
				byte[] len = new byte[4];
				System.arraycopy(table, name_offset - 4, len, 0, 4);
				ReadMemoryAccess memory = new ReadMemoryAccess(len, false); // big endian
				int length = memory.getInt();
				return new String(table, name_offset, length);
			}
			return ""; //$NON-NLS-1$
		}

		public boolean isFunction() {
			return (symbol_type == PRI_PROG || (symbol_type == ENTRY && symbol_scope != LOCAL));
		}

		public boolean isVariable() {
			return ((symbol_type == DATA && symbol_scope != LOCAL) || symbol_type == STORAGE);
		}

		@Override
		public String toString() {
			StringBuilder buffer = new StringBuilder();
			buffer.append("SYMBOL TABLE ENTRY").append(NL); //$NON-NLS-1$
			buffer.append("symbol_name  = "); //$NON-NLS-1$
			try {
				buffer.append(getName(getStringTable())).append(NL);
			} catch (IOException e) {
				buffer.append("I/O error"); //$NON-NLS-1$
			}
			buffer.append("symbol_value = ").append(symbol_value).append(NL); //$NON-NLS-1$
			buffer.append("symbol_type  = ").append(symbol_type).append(NL); //$NON-NLS-1$
			buffer.append("symbol_scope = ").append(symbol_scope).append(NL); //$NON-NLS-1$
			return buffer.toString();
		}
	}

	public static class Attribute {
		public static final int SOM_TYPE_EXE = 1;
		public static final int SOM_TYPE_SHLIB = 2;
		public static final int SOM_TYPE_OBJ = 3;
		public static final int SOM_TYPE_CORE = 4;

		String cpu;
		int type;
		boolean bDebug;
		boolean isle;

		public String getCPU() {
			return cpu;
		}

		public int getType() {
			return type;
		}

		public boolean hasDebug() {
			return bDebug;
		}

		public boolean isLittleEndian() {
			return isle;
		}
	}

	/*
	 * SOM class implementation
	 */
	// A hollow entry, to be used with caution in controlled situations
	protected SOM() {
	}

	public SOM(String filename) throws IOException {
		this(filename, 0);
	}

	public SOM(String filename, long offset) throws IOException {
		this.filename = filename;
		commonSetup(new RandomAccessFile(filename, "r"), offset); //$NON-NLS-1$
	}

	void commonSetup(RandomAccessFile file, long offset) throws IOException {
		startingOffset = offset;
		rfile = file;
		try {
			filehdr = new FileHeader(rfile, startingOffset);
		} finally {
			dispose();
		}
	}

	public void dispose() throws IOException {
		if (rfile != null) {
			rfile.close();
			rfile = null;
		}
	}

	RandomAccessFile getRandomAccessFile() throws IOException {
		if (rfile == null) {
			rfile = new RandomAccessFile(filename, "r"); //$NON-NLS-1$
		}
		return rfile;
	}

	public FileHeader getFileHeader() throws IOException {
		return filehdr;
	}

	public Attribute getAttributes() {
		Attribute attrib = new Attribute();
		// Machine type.
		switch (filehdr.system_id) {
		case FileHeader.PA_RISC_10:
		case FileHeader.PA_RISC_11:
		case FileHeader.PA_RISC_20:
			attrib.cpu = "PA_RISC"; //$NON-NLS-1$
			break;
		default:
			attrib.cpu = "unknown"; //$NON-NLS-1$
		}

		/* SOM characteristics, FileHeader.a_magic.  */
		switch (filehdr.a_magic) {
		case FileHeader.EXE_SOM_LIB:
		case FileHeader.PRIV_EXEC_SOM:
		case FileHeader.SHARE_EXEC_SOM:
		case FileHeader.SHARE_DEMAND_LOAD_EXE_SOM:
			attrib.type = Attribute.SOM_TYPE_EXE;
			break;
		case FileHeader.DYN_LOAD_LIB:
		case FileHeader.SHARED_LIB:
			attrib.type = Attribute.SOM_TYPE_SHLIB;
			break;
		default:
			attrib.type = Attribute.SOM_TYPE_OBJ;
		}

		// For HP-UX SOM always assume big endian unless otherwise.
		attrib.isle = false;

		// No debug information.
		if (filehdr.symbol_location == 0 && filehdr.symbol_total == 0) {
			attrib.bDebug = false;
		} else {
			attrib.bDebug = true;
		}

		return attrib;
	}

	public Symbol[] getSymbols() throws IOException {
		if (symbols == null) {
			long offset = startingOffset + getFileHeader().symbol_location;
			getRandomAccessFile();
			rfile.seek(offset);
			int numSymbols = getFileHeader().symbol_total;
			ArrayList<Symbol> symList = new ArrayList<>(numSymbols);
			for (int i = 0; i < numSymbols; ++i) {
				Symbol v = new Symbol(rfile);
				symList.add(v);
			}
			symbols = symList.toArray(new Symbol[symList.size()]);
		}
		return symbols;
	}

	public byte[] getStringTable() throws IOException {
		if (string_table == null) {
			if (getFileHeader().symbol_strings_size > 0) {
				getRandomAccessFile();
				long offset = startingOffset + getFileHeader().symbol_strings_location;
				rfile.seek(offset);
				string_table = new byte[getFileHeader().symbol_strings_size];
				rfile.readFully(string_table);
			} else {
				string_table = new byte[0];
			}
		}
		return string_table;
	}

	@Override
	public String toString() {
		StringBuilder buffer = new StringBuilder();
		try {
			FileHeader header = null;
			header = getFileHeader();
			if (header != null) {
				buffer.append(header);
			}
			getSymbols();
			for (int i = 0; i < symbols.length; ++i) {
				buffer.append(symbols[i]);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return buffer.toString();
	}

	public static boolean isSOMHeader(byte[] hints) {
		if (hints != null && hints[0] == 0x02
				&& (hints[1] == (byte) 0xb || hints[1] == (byte) 0x10 || hints[1] == (byte) 0x14)) {
			return true;
		}
		return false;
	}

	public static Attribute getAttributes(byte[] hints) throws IOException {
		SOM emptyXCoff = new SOM();
		emptyXCoff.filehdr = new SOM.FileHeader(hints, false); // big endian
		Attribute attribute = emptyXCoff.getAttributes();
		emptyXCoff.dispose();
		return attribute;
	}

	public static Attribute getAttributes(String file) throws IOException {
		SOM xcoff = new SOM(file);
		Attribute attribute = xcoff.getAttributes();
		xcoff.dispose();
		return attribute;
	}

	public static void main(String[] args) {
		try {
			SOM som = new SOM(args[0]);
			System.out.println(som);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
