/*******************************************************************************
 * 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.xcoff;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Vector;

import org.eclipse.cdt.core.CCorePlugin;

/**
 *  The <code>AR</code> class is used for parsing standard XCOFF32 archive (ar) files.
 *
 *  Each object within the archive is represented by an ARHeader class.  Each of
 *  of these objects can then be turned into an XCOFF32 object for performing XCOFF32
 *  class operations.
 *  @see MemberHeader
 * 
 * @author vhirsl
 */
public class AR {
	protected String filename;
	private RandomAccessFile file;
	private ARHeader header;
	private MemberHeader[] memberHeaders;

	/**
	 * Content of an archive in AIX XCOFF32 format
	 * 
	 * @author vhirsl
	 */
	public class ARHeader {
		private final static int SAIAMAG = 8;
		
		private byte[] fl_magic = new byte[SAIAMAG];// Archive magic string
		private byte[] fl_memoff = new byte[20];	// Offset to member table
		private byte[] fl_gstoff = new byte[20];	// Offset to global symbol table
		private byte[] fl_gst64off = new byte[20];	// Offset to global symbol table for 64-bit objects  
		private byte[] fl_fstmoff = new byte[20];	// Offset to first archive member
		private byte[] fl_lstmoff = new byte[20];	// Offset to last archive member
		private byte[] fl_freeoff = new byte[20];	// Offset to first member on free list
		
		private long fstmoff = 0;
		private long lstmoff = 0;
//		private long memoff = 0;
		
		public ARHeader() throws IOException {
			try {
				RandomAccessFile file = getRandomAccessFile();
				file.seek(0);
				file.read(fl_magic);
				if (isARHeader(fl_magic)) {
					file.read(fl_memoff);
					file.read(fl_gstoff);
					file.read(fl_gst64off);
					file.read(fl_fstmoff);
					file.read(fl_lstmoff);
					file.read(fl_freeoff);
					fstmoff = Long.parseLong(removeBlanks(new String(fl_fstmoff)));
					lstmoff = Long.parseLong(removeBlanks(new String(fl_lstmoff)));
//					memoff = Long.parseLong(removeBlanks(new String(fl_memoff)));
				}
				
			} catch (IOException e) {
				dispose();
				CCorePlugin.log(e);
			}
		}

		/**
		 * @return boolean
		 */
		public boolean isXcoffARHeader() {
			return (fstmoff != 0);
		}

	}

	/**
	 *  Creates a new <code>AR</code> object from the contents of 
	 *  the given file.
	 *
	 *  @param filename The file to process.
	 *  @throws IOException The file is not a valid archive.
	 */
	public AR(String filename) throws IOException {
		this.filename = filename;
		file = new RandomAccessFile(filename, "r"); //$NON-NLS-1$
		header = new ARHeader();
		if (!header.isXcoffARHeader()) {
			file.close();
			throw new IOException(CCorePlugin.getResourceString("Util.exception.invalidArchive")); //$NON-NLS-1$
		}
	}

	public void dispose() {
		try {
			if (file != null) {
				file.close();
				file = null;
			}
		} catch (IOException e) {
		}
	}

	@Override
	protected void finalize() throws Throwable {
		try {
			dispose();
		} finally {
			super.finalize();
		}
	}

	public static boolean isARHeader(byte[] ident) {
		if (ident == null || ident.length < 8
			|| ident[0] != '<'
			|| ident[1] != 'b'
			|| ident[2] != 'i'
			|| ident[3] != 'g'
			|| ident[4] != 'a'
			|| ident[5] != 'f'
			|| ident[6] != '>'
			|| ident[7] != '\n')
			return false;
		return true;
	}

	/**
	 * The <code>ARHeader</code> class is used to store the per-object file 
	 *  archive headers.  It can also create an XCOFF32 object for inspecting
	 *  the object file data.
	 */
	public class MemberHeader {
		byte[] ar_size = new byte[20];		// File member size - decimal
		byte[] ar_nxtmem = new byte[20];	// Next member offset - decimal
		byte[] ar_prvmem = new byte[20];	// Previous member offset - decimal
		byte[] ar_date = new byte[12];		// File member date - decimal
		byte[] ar_uid = new byte[12];		// File member userid - decimal
		byte[] ar_gid = new byte[12];		// File member group id - decimal
		byte[] ar_mode = new byte[12];		// File member mode - octal
		byte[] ar_namlen = new byte[4];		// File member name length -decimal
		byte[] ar_name;						// Start of member name
		byte[] ar_fmag = new byte[2];		// AIAFMAG - string to end "`\n"
		
		// converted values
		long size;
		long nxtmem;
		long prvmem;
		int namlen;
		String name;
		
		long file_offset;

		/**
		 * Remove the padding from the archive header strings.
		 */
		private String removeBlanks(String str) {
			while (str.charAt(str.length() - 1) == ' ')
				str = str.substring(0, str.length() - 1);
			return str;
		}

		/**
		 * Look up the name stored in the archive's string table based
		 * on the offset given. 
		 *
		 * Maintains <code>file</code> file location.
		 *
		 * @param offset 
		 *    Offset into the string table for first character of the name.
		 * @throws IOException 
		 *    <code>offset</code> not in string table bounds.
		 */
//		private String nameFromStringTable(long offset) throws IOException {
//			StringBuffer name = new StringBuffer(0);
//			long pos = file.getFilePointer();
//
//			try {
//				if (strtbl_pos != -1) {
//					byte temp;
//					file.seek(strtbl_pos + offset);
//					while ((temp = file.readByte()) != '\n')
//						name.append((char) temp);
//				}
//			} finally {
//				file.seek(pos);
//			}
//
//			return name.toString();
//		}

		/**
		 * Creates a new archive header object.  
		 *
		 * Assumes that file is already at the correct location in the file.
		 *
		 * @throws IOException 
		 *    There was an error processing the header data from the file.
		 */
		public MemberHeader() throws IOException {
			//
			// Read in the archive header data. Fixed sizes.
			//
			RandomAccessFile file = getRandomAccessFile();
			file.read(ar_size);
			file.read(ar_nxtmem);
			file.read(ar_prvmem);
			file.read(ar_date);
			file.read(ar_uid);
			file.read(ar_gid);
			file.read(ar_mode);
			file.read(ar_namlen);
			namlen = Integer.parseInt(removeBlanks(new String(ar_namlen)));
			ar_name = new byte[namlen];
			file.read(ar_name);
			file.read(ar_fmag);

			size = Long.parseLong(removeBlanks(new String(ar_size)));
			nxtmem = Long.parseLong(removeBlanks(new String(ar_nxtmem)));
			prvmem = Long.parseLong(removeBlanks(new String(ar_prvmem)));
			name = new String(ar_name, 0, namlen);
			
			//
			// Save this location so we can create the XCOFF32 object later.
			//
			file_offset = file.getFilePointer();
			if ((file_offset % 2) == 1) {
				++file_offset;
			}
		}

		/** Get the name of the object file */
		public String getObjectName() {
			return name;
		}

		/** Get the size of the object file . */
		public long getSize() {
			return size;
		}
		
		public String getArchiveName() {
			return filename;
		}

		public long getObjectDataOffset() {
			return file_offset;
		}
		
		public byte[] getObjectData() throws IOException {
			byte[] temp = new byte[(int) size];
			RandomAccessFile file = getRandomAccessFile();
			file.seek(file_offset);
			file.read(temp);
			dispose();
			return temp;
		}
	}

	/** Load the headers from the file (if required).  */
	private void loadHeaders() throws IOException {
		if (memberHeaders != null)
			return;

		Vector<MemberHeader> v = new Vector<MemberHeader>();
		try {
			//
			// Check for EOF condition
			//
			MemberHeader aHeader;
			for (long pos = header.fstmoff; pos < file.length(); pos = aHeader.nxtmem) {
				file.seek(pos);
				aHeader = new MemberHeader();
				v.add(aHeader);
				if (pos == 0 || pos == header.lstmoff) {	// end of double linked list
					break;
				}
			}
		} catch (IOException e) {
		}
		memberHeaders = v.toArray(new MemberHeader[0]);
	}

	/**
	 *  Get an array of all the object file headers for this archive.
	 * 
	 * @throws IOException 
	 *    Unable to process the archive file.
	 * @return An array of headers, one for each object within the archive.
	 * @see ARHeader
	 */
	public MemberHeader[] getHeaders() throws IOException {
		loadHeaders();
		return memberHeaders;
	}

	public String[] extractFiles(String outdir, String[] names) throws IOException {
		Vector<String> names_used = new Vector<String>();
		String object_name;
		int count;

		loadHeaders();

		count = 0;
		for (MemberHeader memberHeader : memberHeaders) {
			object_name = memberHeader.getObjectName();
			if (names != null && !stringInStrings(object_name, names))
				continue;

			object_name = "" + count + "_" + object_name; //$NON-NLS-1$ //$NON-NLS-2$
			count++;

			byte[] data = memberHeader.getObjectData();
			File output = new File(outdir, object_name);
			names_used.add(object_name);

			RandomAccessFile rfile = new RandomAccessFile(output, "rw"); //$NON-NLS-1$
			rfile.write(data);
			rfile.close();
		}

		return names_used.toArray(new String[0]);
	}

	private boolean stringInStrings(String str, String[] set) {
		for (String element : set)
			if (str.compareTo(element) == 0)
				return true;
		return false;
	}

	/**
	 * Remove the padding from the archive header strings.
	 */
	protected String removeBlanks(String str) {
		while (str.charAt(str.length() - 1) == ' ')
			str = str.substring(0, str.length() - 1);
		return str;
	}

	public String[] extractFiles(String outdir) throws IOException {
		return extractFiles(outdir, null);
	}

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

	public static void main(String[] args) {
		try {
			AR ar = new AR(args[0]);
			ar.getHeaders();
			ar.extractFiles(args[1]);
			System.out.println(ar);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

