/*******************************************************************************
 * Copyright (c) 2000, 2008 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     Abeer Bagul (Tensilica) - bug 102434
 *     Anton Leherbauer (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.utils;


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 ELF 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 Elf object for performing Elf
 *  class operations.
 *  @see ARHeader
 */
public class AR {

	protected String filename;
	protected ERandomAccessFile efile;
	protected long strtbl_pos = -1;
	private ARHeader[] headers;

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

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

	/**
	 * The <code>ARHeader</code> class is used to store the per-object file 
	 *  archive headers.  It can also create an Elf object for inspecting
	 *  the object file data.
	 */
	public class ARHeader {

		private static final int NAME_IDX= 0;
		private static final int NAME_LEN= 16;
//		private static final int MTIME_IDX= 16;
//		private static final int MTIME_LEN= 12;
//		private static final int UID_IDX= 28;
//		private static final int UID_LEN= 6;
//		private static final int GID_IDX= 34;
//		private static final int GID_LEN= 6;
//		private static final int MODE_IDX= 40;
//		private static final int MODE_LEN= 8;
		private static final int SIZE_IDX= 48;
		private static final int SIZE_LEN= 10;
//		private static final int TRAILER_IDX= 58;
//		private static final int TRAILER_LEN= 2;
		private static final int HEADER_LEN= 60;
		
		private String object_name;
//		private String modification_time;
//		private String uid;
//		private String gid;
//		private String mode;
		private long size;
		private long obj_offset;

		/**
		 * Remove the padding from the archive header strings.
		 */
		private String removeBlanks(String str) {
		    return str.trim();
		}

		/**
		 * Look up the name stored in the archive's string table based
		 * on the offset given. 
		 *
		 * Maintains <code>efile</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 = efile.getFilePointer();

			try {
				if (strtbl_pos != -1) {
					byte temp;
					efile.seek(strtbl_pos + offset);
					while ((temp = efile.readByte()) != '\n')
						name.append((char) temp);
				}
			} finally {
				efile.seek(pos);
			}

			return name.toString();
		}

		/**
		 * Creates a new archive header object.  
		 *
		 * Assumes that efile is already at the correct location in the file.
		 *
		 * @throws IOException 
		 *    There was an error processing the header data from the file.
		 */
		ARHeader() throws IOException {
			byte[] buf= new byte[HEADER_LEN];
			//
			// Read in the archive header data. Fixed sizes.
			//
			efile.readFully(buf);
			//
			// Save this location so we can create the Elf object later.
			//
			obj_offset = efile.getFilePointer();

			//
			// Convert the raw bytes into strings and numbers.
			//
			this.object_name = removeBlanks(new String(buf, NAME_IDX, NAME_LEN));
//			this.modification_time = new String(buf, MTIME_IDX, MTIME_LEN);
//			this.uid = new String(buf, UID_IDX, UID_LEN);
//			this.gid = new String(buf, GID_IDX, GID_LEN);
//			this.mode = new String(buf, MODE_IDX, MODE_LEN);
			this.size = Long.parseLong(removeBlanks(new String(buf, SIZE_IDX, SIZE_LEN)));

			//
			// If the name is of the format "/<number>", get name from the
			// string table.
			//
			if (strtbl_pos != -1 && this.object_name.length() > 1 && this.object_name.charAt(0) == '/') {
				try {
					long offset = Long.parseLong(this.object_name.substring(1));
					this.object_name = nameFromStringTable(offset);
				} catch (java.lang.Exception e) {
				}
			}

			//
			// Strip the trailing / from the object name.
			//
			int len = this.object_name.length();
			if (len > 2 && this.object_name.charAt(len - 1) == '/') {
				this.object_name = this.object_name.substring(0, len - 1);
			}
		}

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

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

		public long	getObjectDataOffset() {
			return obj_offset;
		}
				
		public byte[] getObjectData() throws IOException {
			byte[] temp = new byte[(int) size];
			if (efile != null) {
				efile.seek(obj_offset);
				efile.read(temp);
			} else {
				efile = new ERandomAccessFile(filename, "r"); //$NON-NLS-1$
				efile.seek(obj_offset);
				efile.read(temp);
				efile.close();
				efile = null;
			}
			return temp;
		}
	}

	public static boolean isARHeader(byte[] ident) {
		if (ident.length < 7
			|| ident[0] != '!'
			|| ident[1] != '<'
			|| ident[2] != 'a'
			|| ident[3] != 'r'
			|| ident[4] != 'c'
			|| ident[5] != 'h'
			|| ident[6] != '>')
			return false;
		return true;
	}

	/**
	 *  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;
		boolean goodAr = false;
		try {
			efile = new ERandomAccessFile(filename, "r"); //$NON-NLS-1$
			byte [] hdrBytes = new byte[7];
			efile.readFully(hdrBytes);
			goodAr = isARHeader(hdrBytes);
			if (!goodAr) {
				throw new IOException(CCorePlugin.getResourceString("Util.exception.invalidArchive")); //$NON-NLS-1$
			}
			efile.readLine();
		} finally {
			if (!goodAr && efile != null) {
				efile.close();
				efile = null;
			}
		}
	}

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

		Vector<ARHeader> v = new Vector<ARHeader>();
		try {
			//
			// Check for EOF condition
			//
			while (efile.getFilePointer() < efile.length()) {
				ARHeader header = new ARHeader();
				String name = header.getObjectName();

				long pos = efile.getFilePointer();

				//
				// If the name starts with a / it is specical.
				//
				if (name.charAt(0) != '/')
					v.add(header);

				//
				// If the name is "//" then this is the string table section.
				//
				if (name.compareTo("//") == 0) //$NON-NLS-1$
					strtbl_pos = pos;

				//
				// Compute the location of the next header in the archive.
				//
				pos += header.getSize();
				if ((pos % 2) != 0)
					pos++;

				efile.seek(pos);
			}
		} catch (IOException e) {
		}
		headers = v.toArray(new ARHeader[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 ARHeader[] getHeaders() throws IOException {
		loadHeaders();
		return headers;
	}

	private boolean stringInStrings(String str, String[] set) {
		for (int i = 0; i < set.length; i++)
			if (str.compareTo(set[i]) == 0)
				return true;
		return false;
	}

	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 (int i = 0; i < headers.length; i++) {
			object_name = headers[i].getObjectName();
			if (names != null && !stringInStrings(object_name, names))
				continue;

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

			byte[] data = headers[i].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]);
	}

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

}
