/*******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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() {
			StringBuffer buffer = new StringBuffer();
			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() {
			StringBuffer buffer = new StringBuffer();
			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<Symbol>(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() {
		StringBuffer buffer = new StringBuffer();
		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();
		}
	}
}
