/*
 *******************************************************************************
 * Copyright (C) 2003-2004, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
*/

package org.unicode.cldr.icu;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;

import org.unicode.cldr.icu.ICUResourceWriter;
import org.unicode.cldr.icu.UDataInfo;

import com.ibm.icu.text.RuleBasedCollator;
//TODO see note in addCollation() 
//import com.ibm.icu.text.CollatorWriter;
import com.ibm.icu.text.UTF16;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.lang.UCharacterEnums.ECharacterCategory;

/**
 * The LDML2ICUBinaryWriter class is a set of methods which can be used
 * to generate Binary (.res) files in the ICU Binary format.
 * 
 * @author Brian Rower - June 2008
 * 
 */
public class LDML2ICUBinaryWriter 
{
	/**
	 * This string is the copyright to be written into the file.
	 * In the C version, can be found in <I>icu4c_root</I>/source/common/unicode/uversion.h
	 */
	private static final String COPYRIGHT = " Copyright (C) 2008, International Business Machines Corporation and others. All Rights Reserved. ";
	
	public static int written = 0;
	
	/**
	 * Magic numbers!!!!
	 */
	private static final byte MAGIC1 = (byte)0xda;
	private static final byte MAGIC2 = 0x27;
	
	private static final boolean INCLUDE_COPYRIGHT = false;
	/**
	 * The number of bytes it takes to write magic number 1.
	 */
	private static final short BYTES_TAKEN_BY_MAGIC1 = 1;
	
	/**
	 * The number of bytes it takes to write magic number 2;
	 */
	private static final short BYTES_TAKEN_BY_MAGIC2 = 1;
	
	/**
	 * The number of bytes that it takes to write the size of the header.
	 */
	private static final short BYTES_TAKEN_BY_HEADER_SIZE = 2;
	
	/**
	 * The charsets to be used when encoding strings.
	 */
	public static final String CHARSET8 = "UTF-8";
	public static final String CHARSET16 = "UTF-16BE";
	
	/**
	 * The number of bytes that each character takes up. This is dependant on the encoding (see CHARSET above).
	 */
	private static final int BYTES_PER_UTF8_CHAR = 1;
	
	/**
	 * Numeric constants for special elements.
	 */
	private static final int SPECIAL_NONE = 0;
	private static final int SPECIAL_COLLATIONS = 1;
	private static final int SPECIAL_COLLATIONELEMENTS = 2;
	private static final int SPECIAL_DEPENDENCY = 3;
	private static final int SPECIAL_TRANSLITERATOR = 4;
		
	
	/**
	 * Numeric constants for types of resource items.
	 * @see ures_getType
	 * @stable ICU 2.0
	 */

	
	//**************************** ENUM Below is ported from C. See ures.h ***********************
	
	/** Resource type constant for "no resource". @stable ICU 2.6 */
	public static final int URES_NONE = -1;
	
	/** Resource type constant for 16-bit Unicode strings. @stable ICU 2.6 */
	public static final int URES_STRING = 0;

	/** Resource type constant for binary data. @stable ICU 2.6 */
	public static final int URES_BINARY = 1;

	/** Resource type constant for tables of key-value pairs. @stable ICU 2.6 */
	public static final int URES_TABLE = 2;

	/**
	 * Resource type constant for aliases;
	 * internally stores a string which identifies the actual resource
	 * storing the data (can be in a different resource bundle).
	 * Resolved internally before delivering the actual resource through the API.
	 * @stable ICU 2.6
	 */
	public static final int URES_ALIAS = 3;

	/**
	 * Internal use only.
	 * Alternative resource type constant for tables of key-value pairs.
	 * Never returned by ures_getType().
	 * @internal
	 */
	public static final int URES_TABLE32 = 4;

	/**
	 * Resource type constant for a single 28-bit integer, interpreted as
	 * signed or unsigned by the ures_getInt() or ures_getUInt() function.
	 * @see ures_getInt
	 * @see ures_getUInt
	 * @stable ICU 2.6
	 */
	public static final int URES_INT = 7;
	

	/** Resource type constant for arrays of resources. @stable ICU 2.6 */
	public static final int URES_ARRAY = 8;

    /**
     * Resource type constant for vectors of 32-bit integers.
     * @see ures_getIntVector
     * @stable ICU 2.6
     */
	public static final int URES_INT_VECTOR = 14;

	public static final int URES_LIMIT = 16;
	
	
	/*
	 * The enum below is ported from C. See uresdata.h
	 * 
	 * It is used as index references for the array which will be written.
	 */
	/* [0] contains URES_INDEX_TOP==the length of indexes[] */
    private static final int URES_INDEX_LENGTH = 0;
    /* [1] contains the top of the strings, same as the bottom of resources, rounded up */
    private static final int URES_INDEX_STRINGS_TOP = 1;
    /* [2] contains the top of all resources */
    private static final int URES_INDEX_RESOURCES_TOP = 2;
    /* [3] contains the top of the bundle, in case it were ever different from [2]*/
    private static final int URES_INDEX_BUNDLE_TOP = 3;
    /* [4] max. length of any table */
    private static final int URES_INDEX_MAX_TABLE_LENGTH = 4;
    /* [5] attributes bit set, see URES_ATT_* (new in formatVersion 1.2) */
    //private static final int URES_INDEX_ATTRIBUTES = 5;
    /* This one is the length of the array */
    private static final int URES_INDEX_TOP = 6;

    //must be set if writing transliteration
    private static Hashtable<String, String> ruleStringsHash = null;
    
	public static void main()
	{
		
	}
    
    
	
	/**
	 * This method is called upon the top of an ICUResourceWriter.Resource
	 * in order to write the whole Resource tree into binary format.
	 * 
	 * @param resTop The top of the resource tree that you would like written to file. This
	 * 			object should be a ICUResourceWriter.ResourceTable.
	 * @param outDir A string pointing to the path of the output directory.
	 * @param outFile The name of the output file. If filename has an extension other than .res 
	 * 			(ex: .txt) this method will strip that extention and replace with .res.
	 */
	public static void writeBinaryFile(ICUResourceWriter.Resource resTop, String outDir, String outFile)
	{
		String fileName ="";
		int usedOffset = 0;
		String directoryPath = "";
		FileOutputStream out;
		UDataInfo info;
		byte[] dataFormat;
		byte[] formatVersion;
		byte[] dataVersion;
		byte[] padding;
		
		//Do some checks on the file name
		//if it has a period in it...get rid of everything after the period
		if(outFile.indexOf('.') > -1)
		{
			fileName = outFile.substring(0, outFile.indexOf('.'));
			if(fileName.length() == 0)
			{
				printError(outFile + " is not a valid file name.");
				System.exit(1);
			}
			fileName = fileName + ".res";
		}
		else
		{
			fileName = outFile + ".res";
		}
		//add the .res part to the file name
		
		
		//do some checks on the directory path
		//replace all backslashes with forward slashes
		directoryPath = outDir.replace('\\', '/');
		
		//if the path does not end in a slash, then we'll add one
		if(directoryPath.charAt(directoryPath.length()-1) != '/')
		{
			directoryPath = directoryPath + "/";
		}
		
		//create UDataInfo
		//Data format is "ResB"
		dataFormat = new byte[4];
		dataFormat[0] = 0x52; //R
		dataFormat[1] = 0x65; //e
		dataFormat[2] = 0x73; //s
		dataFormat[3] = 0x42; //B
		
		//Format version is 1.2.0.0
		formatVersion = new byte[4];
		formatVersion[0] = 1;
		formatVersion[1] = 2; 
		formatVersion[2] = 0;
		formatVersion[3] = 0;
			
		//data version is 1.4.0.0
		dataVersion = new byte[4];
		dataVersion[0] = 1;
		dataVersion[1] = 4;
		dataVersion[2] = 0;
		dataVersion[3] = 0;		
		
		
		//now that the file and directory name are formatted, lets try to create an output stream
		try
		{
			System.out.println("Creating file: " + directoryPath + fileName);
			File f = new File(directoryPath, fileName);
			out = new FileOutputStream(f);
			
			info = new UDataInfo(UDataInfo.getSize(), (short)0, UDataInfo.BIGENDIAN, UDataInfo.ASCII_FAMILY, 
					UDataInfo.SIZE_OF_UCHAR, (byte)0, dataFormat, formatVersion, dataVersion);
			
			//this method goes through the tree and looks for a table named CollationElements or Collations, and adds the 
			//appropriate data to the tree 
			dealWithSpecialElements(resTop, outDir);
			
			//before we do anything with the resources, sort them
			resTop.sort();
		
			//call writeBinaryHeader.
			writeBinaryHeader(out, info, COPYRIGHT);
	
			
			
			usedOffset = writeKeyString(out, resTop);
			
			//Call writeBinary on the top of the Resource tree
			
			usedOffset = resTop.writeBinary(out, usedOffset);
			padding = createPadding(pad32(usedOffset));
			if(padding != null)
			{
				out.write(padding);
				written += padding.length;
			}
			out.close();
			System.out.println("Finished writing binary.");
		}
		catch (FileNotFoundException e)
		{
			printError(directoryPath + fileName + " could not be opened, please ensure the correct path is given.");
			e.printStackTrace();
			System.exit(1);
		}
		catch(SecurityException e)
		{
			printError("access denied: " + directoryPath + fileName);
			e.printStackTrace();
			System.exit(1);
		}
		catch (Exception e)
		{
			printError(e.getMessage());
			System.exit(1);
		}
	}
	
	
	private static int getSpecialType(ICUResourceWriter.Resource res)
	{
		if(!res.hasKey)
		{
			return SPECIAL_NONE;
		}
		
		if(res.name.equals("CollationElements") && res instanceof ICUResourceWriter.ResourceTable)
		{
			return SPECIAL_COLLATIONELEMENTS;
		}
		
		if(res.name.equals("collations") && res instanceof ICUResourceWriter.ResourceTable)
		{
			return SPECIAL_COLLATIONS;
		}
		
		if(res.name.equals("depends") && res instanceof ICUResourceWriter.ResourceProcess)
		{
			return SPECIAL_DEPENDENCY;
		}
		
		if(res instanceof ICUResourceWriter.ResourceProcess)
		{
			if(((ICUResourceWriter.ResourceProcess)res).ext.equals(ICUResourceWriter.TRANSLITERATOR))
			{
				return SPECIAL_TRANSLITERATOR;
			}
		}
		
		return SPECIAL_NONE;
	}
	
	/**
	 * 
	 * Goes through the resource tree recursively and looks for a table named 
	 * CollationElements, collations, dependency, or transliterator and adds the appropriate data
	 * 
	 * @param top The top of the Resource Tree
	 */
	private static void dealWithSpecialElements(ICUResourceWriter.Resource top, String outDir)
	{
		//if it's a table
		if(top instanceof ICUResourceWriter.ResourceTable)
		{
			//loop through all it's elements and check if they're anything specialCollationElements or Collation
			ICUResourceWriter.Resource cur = top.first;
			while(cur != null)
			{
				switch(getSpecialType(cur))
				{
					case SPECIAL_COLLATIONELEMENTS:
						addCollation(cur);
						break;
					case SPECIAL_COLLATIONS:
						addCollationElements(cur);
						break;
					case SPECIAL_DEPENDENCY:
						addDependency((ICUResourceWriter.ResourceTable)top, (ICUResourceWriter.ResourceProcess)cur, outDir);
						break; 
					case SPECIAL_TRANSLITERATOR:
						addTransliteration((ICUResourceWriter.ResourceTable)top, (ICUResourceWriter.ResourceProcess)cur);
						break;
					case SPECIAL_NONE:
					default:
						dealWithSpecialElements(cur, outDir);
				}
				
				cur = cur.next;
			}
		}
		//if it's not a table...don't do anything...
	}
	
	public static void setRulesHash(Hashtable<String, String> hash)
	{
		ruleStringsHash = hash;
	}
	
	//Parallels the C function for parseTransliterator in parse.c of genrb
	private static void addTransliteration(ICUResourceWriter.ResourceTable parent, ICUResourceWriter.ResourceProcess trans)
	{
		if(ruleStringsHash == null)
		{
			System.err.println("If you are processing transliteration, you must set the Rules Hashtable.");
			System.exit(-1);
		}
		
		String dataString = ruleStringsHash.get(trans.val);

		if(dataString == null)
		{
			System.err.println("Could not find data for: " + trans.val);
			System.exit(-1);
		}
		
		//strip out the unneeded stuff from the buffer (like comments and spaces and line breaks
		dataString = stripRules(dataString);
	
		//create a string resource containing the data and add it to the resource tree
		//remove the ResourceProcess and add the String
		
		ICUResourceWriter.ResourceString replacement = new ICUResourceWriter.ResourceString("Resource", dataString);
		
		ICUResourceWriter.Resource current = parent.first;
		
		//yes, we're using an address comparison below...because they should both be pointing the the same object when we find it.
		if(current != trans)
		{
			while(current != null && current.next != trans)
			{
				current = current.next;
			}
			if(current != null)
			{
				replacement.next = trans.next;
				current.next = replacement;	
			}
			else
			{
				System.err.println("An unexpected error has occured: Could not find Transliteration resource.");
				System.exit(-1);
			}
		}
		else
		{
			replacement.next = trans.next;
			parent.first = replacement;
		}

	}
	
	private static boolean isUWhiteSpace(char c)
	{
		 return (c >= 0x0009 && c <= 0x2029 &&
		            (c <= 0x000D || c == 0x0020 || c == 0x0085 ||
		             c == 0x200E || c == 0x200F || c >= 0x2028));
	}
	
	private static boolean isNewLine(char c)
	{
		if(c == 0x000d || c == 0x000a)
		{
			return true;
		}
		return false;
	}
	
	private static boolean isPunctuation(char c)
	{
		int x = UCharacter.getType(c);
		switch(x)
		{
			case ECharacterCategory.CONNECTOR_PUNCTUATION:
			case ECharacterCategory.DASH_PUNCTUATION:
			case ECharacterCategory.END_PUNCTUATION:
			case ECharacterCategory.FINAL_PUNCTUATION:
			case ECharacterCategory.INITIAL_PUNCTUATION:
			case ECharacterCategory.OTHER_PUNCTUATION:
			case ECharacterCategory.START_PUNCTUATION:
				return true;
			default:
				return false;		
		}
	}
	
	private static boolean isControl(char c)
	{
		int x = UCharacter.getType(c);
		switch(x)
		{
			case ECharacterCategory.CONTROL:
				return true;
			default:
				return false;
		}
	}
	
	
	//parallels the C++ function utrans_stripRules in rbt_pars.cpp in i18n project
	private static String stripRules(String data)
	{
		String newData = "";
		int currentIndex = 0;
		char curChar;
		char curChar2 = '0';
		boolean needChar2 = false;
		boolean quoted = false;
		
		try
		{
			
		
		while(currentIndex < data.length())
		{
			needChar2 = false;
			curChar = data.charAt(currentIndex);
			//if it's a quote, set the flag
			if(curChar == '\'')
			{
				quoted = !quoted;
			}
			//otherwise...if the quote flag is NOT set.
			else if(!quoted)
			{
				//IF comment... ignore comment lines ...starting with #....and until a carriage return or line feed
				if(curChar == '#')
				{
					//if the preceeding characters were whitepace or new lines, go back and get rid of them
					
					while(newData.length() > 0 && (isNewLine(newData.charAt(newData.length()-1)) || isUWhiteSpace(newData.charAt(newData.length()-1))))
					{
						if(newData.length() == 1)
						{
							newData = "";
						}
						else
						{
							newData = newData.substring(0, newData.length()-2);	
						}
						
					}
					
					//move to the end of the line
					while(!isNewLine(curChar) && currentIndex < data.length())
					{
						currentIndex++;
						if(currentIndex < data.length())
						{
							curChar = data.charAt(currentIndex);
						}
					}
					//grab the first character of this new line (no longer part of the comment
					currentIndex++;
					if(currentIndex < data.length())
					{
						curChar = data.charAt(currentIndex);	
					}
					
				}
				else if(curChar == '\\') //OR if its an escape char //((UChar)0x005C) - \
				{
					//skip over the \ and then skip any line breaks that may follow
					do
					{
						currentIndex++;
						if(currentIndex < data.length())
						{
							curChar = data.charAt(currentIndex);
						}
					}
					while(isNewLine(curChar) && currentIndex < data.length());
					
					//if it's a u and there are 4 more characters after it
                	if(curChar == 'u' && (data.length() - currentIndex) >= 4)
                	{
                		//convert it to a character from a codepoint (String)UTF16.valueOf(int)
                		
                		String hexString = data.substring(currentIndex+1,currentIndex+5);
                		int codeNum = Integer.parseInt(hexString, 16);
                		String temp = UTF16.valueOf(codeNum);
                		char tempChar;
                		
                		
                		tempChar = temp.charAt(0);
                		
                		
						//if its 0xFFFFFFFF
                		if(tempChar == 0xFFFFFFFF)
                		{
                			System.err.println("Invalid character found while processing file.");
                			System.exit(-1);
                		}
                		//if NOT whitespace(isUWhiteSpace) && NOT a control character? && not punctuation
                		if(!isUWhiteSpace(tempChar) && !isPunctuation(tempChar) && !isControl(tempChar) )
                		{
    						//set the current character to this character
                			curChar = tempChar;
                			currentIndex += 4; //the 4 numbers...will add one more for the u, already did one for the slash
                			if(temp.length() > 1)
                    		{
                    			curChar2 = temp.charAt(1);
                    			needChar2 = true;
                    		}
                		}

                	}
						
				}
				else if(curChar == '\'')//OR if it's a quote
				{
					quoted = !quoted;
				}
			} //end not quoted
			
			if(isNewLine(curChar))
			{
				quoted = false;
				//while we're not hitting the end of the string
				while(currentIndex < data.length())
				{
					if(!isNewLine(curChar))
					{
						break;
					}
					currentIndex++;
					if(currentIndex < data.length())
					{
						curChar = data.charAt(currentIndex);
					}
				}
				continue;
			}

			//append the character to the new string, because we've decided it's ok
			newData += curChar;
			currentIndex++;
			if(needChar2)
			{
				newData += curChar2;
			}
		}//end loop
		   
		}
		catch(Exception e)
		{
			System.err.println("Had a problem...");
		}
		if(newData.length() > data.length())
		{
			return null;
		}
		return newData;
	}
	
	private static void addDependency(ICUResourceWriter.ResourceTable parent, ICUResourceWriter.ResourceProcess dep, String outDir)
	{
		String filename;
		File f;
		
		filename = outDir;
		if(!(outDir.charAt(outDir.length()-1) == '/' || outDir.charAt(outDir.length()-1) == '\\'))
		{
			filename += "/";
		}
		
		filename += dep.val;
		
		
		f = new File(filename);
		if(!f.exists())
		{
			System.err.println("WARNING: Could not find dependancy: " + filename);
		}
		//create the %%DEPENDENCY array with a string containing the path, add it to the table.
		ICUResourceWriter.ResourceArray a = new ICUResourceWriter.ResourceArray();
		a.name = "%%DEPENDENCY";
		ICUResourceWriter.ResourceString str = new ICUResourceWriter.ResourceString(null, dep.val);
		a.first = str;
		dep.addAfter(a);
		
		//Remove the ResourceProcess object and replace it with a ResourceString object.
		ICUResourceWriter.ResourceString replacement = new ICUResourceWriter.ResourceString(dep.name, dep.val);
		
		ICUResourceWriter.Resource current = parent.first;
		
		//yes, we're using an address comparison below...because they should both be pointing the the same object when we find it.
		while(current != null && current.next != dep)
		{
			current = current.next;
		}
		replacement.next = dep.next;
		current.next = replacement;
				
	}
	
	private static void addCollationElements(ICUResourceWriter.Resource elementTable)
	{
		//Element table name is "Collation"
		//loops through sub tables of Collation and adds CollationBinary as nessisary
		ICUResourceWriter.Resource cur = elementTable.first;
		
		while(cur != null)
		{
			addCollation(cur);
			cur = cur.next;
		}
	}
	
	private static void addCollation(ICUResourceWriter.Resource element)
	{
		ICUResourceWriter.Resource cur = element.first;
		
		while(cur != null)
		{
			if(cur.hasKey && (cur instanceof ICUResourceWriter.ResourceString))
			{
				ICUResourceWriter.ResourceString strElement = (ICUResourceWriter.ResourceString)cur;
				
				if(strElement.name.equals("Sequence"))
				{
					try
					{
						//RuleBasedCollator rbc = new RuleBasedCollator(strElement.val);
						//TODO Generate proper binary data for Collator
						/*
						 * currently CollatorWriter does not work properly
						 * Need to write something to generate proper bytes,
						 * bytes do not seem to exist at this time
						 * CollatorWriter was not committed to the ICU4J trunk, it currently lives in the bdrower subdirectory of icu4j in the IBM local cvs 
						 */						
						//byte[] bytes = CollatorWriter.writeRBC(rbc);
						//ICUResourceWriter.ResourceBinary b = new ICUResourceWriter.ResourceBinary();
						//b.data = bytes;
						//b.name = "%%CollationBin";
						//element.addAfter(b);
						
					}
					catch(Exception e)
					{
						System.err.println("Could not create Collation Binary");
					}
				}
			}
			cur = cur.next;
		}
	}
	
	
	/**
	 * Write the header section of the file. This section of the file currently contains:<br>
	 * 		-A 2 byte number containing the length (in bytes) of the header.<br>
	 * 		-Two "magic numbers" each 1 byte in size.<br>
	 * 		-The UDataInfo structure
	 * 		-The null terminated copyright string (if it should be written)
	 * 
	 * @param out
	 * @param info
	 * @param copyright
	 */
	private static void writeBinaryHeader(FileOutputStream out, UDataInfo info, String copyright)
	{
		short headSize = 0;
		byte[] magics = new byte[2];
		int pad = 0;
		byte[] padding;
		/*
		 * The header includes a 2 byte number containing the size of the header,
		 * two magic numbers each 1 byte in size, the UDataInfo structure, and the 
		 * copyright plus null terminator. Subject to change.
		 */
		headSize += info.size + BYTES_TAKEN_BY_HEADER_SIZE + BYTES_TAKEN_BY_MAGIC1 + BYTES_TAKEN_BY_MAGIC2;
		if(copyright != null && INCLUDE_COPYRIGHT)
		{
			headSize += copyright.length() + 1;
		}
		if((pad = pad16Bytes(headSize)) != 0)
		{
			headSize += pad;
		}
		
		magics[0] = MAGIC1;
		magics[1] = MAGIC2;
			
		try
		{
			//write the size of the header
			out.write(shortToBytes(headSize));
			written += (shortToBytes(headSize)).length;
			
			//write the two magic numbers
			out.write(magics);
			written += magics.length;
			
			//write the UDataInfo structure
			out.write(info.getByteArray());
			written += info.getByteArray().length;
			
			//write the copyright and null terminating byte(s) if writing it
			if(copyright != null && INCLUDE_COPYRIGHT)
			{
				out.write((copyright + "\0").getBytes(CHARSET8));
				written += ((copyright + "\0").getBytes(CHARSET8)).length;
				
			}
			
			if(pad != 0)
			{
				padding = new byte[pad];
				for(int i = 0; i < padding.length; i++)
				{
					padding[i] = 0;
				}
				out.write(padding);
				written += padding.length;
			}
			
		}
		catch (IOException e)
		{
			printError(e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
	}
	
	
	/**
	 * Write some information about the key string and then write a chunk of bytes which mirrors the 
	 * SRBRoot->fkeys character buffer. This will be a list of null
	 * terminated strings. Each string pertains to a certain resource. This method also modifies the resources in
	 * 'resTop' by setting the keyStringOffset variable. The keyStringOffset variable is the number of bytes from 
	 * the start of the key string that the resources key starts. For example:
	 * 
	 * <p>In the 'en_PK' locale, you may have a Table resource with the key "Version." The Table contains a string
	 * resource with the key "1.31."</p>
	 * <p>If this were the whole of the locale data, the key string would be an encoded version of this:</p>
	 * 
	 * "Version\01.31\0" 
	 * 
	 * <br><br>In UTF-16 encoding, each character will take 2 bytes. 
	 * <br>keyStringOffset for the table object would be 0.
	 * <br>keyStringOffset for the string resource would be = "Version".length() + 2 = 16
	 * 
	 * 
	 * @param out The output stream to write this to.
	 * @param resTop The top of the resource tree whose keys shall be written
	 */
	private static int writeKeyString(FileOutputStream out, ICUResourceWriter.Resource resTop)
	{
		String keyList = "";
		byte[] padding = null;
		int padBytes = 0;
		int end;
		int root;
		byte[] rootBytes;
		int[] indexes = new int[URES_INDEX_TOP];
		byte[] indexBytes = new byte[URES_INDEX_TOP * 4];
		byte[] keyBytes;
		int usedOffset;
		int sizeOfIndexes;
		int sizeOfIndexesAndKeys;
		int tableID;
		
		//set flag so that we know which resource is the top of the tree
		resTop.isTop = true;
		
		
		sizeOfIndexes = (1 + URES_INDEX_TOP) * ICUResourceWriter.SIZE_OF_INT;
		
		usedOffset = sizeOfIndexes;
		
		//Build the String of keys
		keyList = buildKeyList(keyList, resTop, usedOffset);
		
		sizeOfIndexesAndKeys = sizeOfIndexes + keyList.length();
		usedOffset = sizeOfIndexesAndKeys + pad32(sizeOfIndexesAndKeys);
		
		end = sizeOfIndexesAndKeys + resTop.sizeOfChildren;
		
		//if it is not 16 byte aligned
		if((padBytes = pad32(sizeOfIndexesAndKeys)) != 0)
		{
			padding = createPadding(padBytes);
			if(padding != null)
			{
				usedOffset += padding.length;
				end += padding.length;
			}
			
		}
		
		
		
		//build a set of 32 bits (in C this variable is called 'root' in reslist.c)
		//the number of bytes included in the keyList, keyList padding, all the children
		
		if(((ICUResourceWriter.ResourceTable)resTop).is32Bit())
		{
			tableID = (URES_TABLE32 << 28);
		}
		else
		{
			tableID = (URES_TABLE << 28);
		}
		root = (end >>> 2) | (tableID);
		
		rootBytes = intToBytes(root);
		
		end += resTop.size;
		
		end += pad32(end);
		
		indexes[URES_INDEX_LENGTH] = URES_INDEX_TOP;
		indexes[URES_INDEX_STRINGS_TOP] = usedOffset >>> 2;
		indexes[URES_INDEX_RESOURCES_TOP] = (end) >> 2;
		indexes[URES_INDEX_BUNDLE_TOP] = indexes[URES_INDEX_RESOURCES_TOP];
		indexes[URES_INDEX_MAX_TABLE_LENGTH] = ICUResourceWriter.maxTableLength;
		
		indexBytes = intArrayToBytes(indexes);
		
			
		
		try
		{
			//write the "root" object
			out.write(rootBytes);
			written += rootBytes.length;
			
			//write the indexes array
			out.write(indexBytes);
			written += indexBytes.length;
			
			//write the keyList and padding if nessicary
			keyBytes = keyList.getBytes(CHARSET8);
			out.write(keyBytes);
			written += keyBytes.length;
			
			if(padding != null)
			{
				out.write(padding);
				written += padding.length;
			}
		}
		catch (IOException e)
		{
			printError("Could not write key string to file. " + e.getMessage());
			System.exit(1);
		}
		
		return usedOffset; 
	}
	
	/**
	 * Recursively go through the whole tree and continue to add to the keyList. As this is done,
	 * set the keyStringOffset, numChildren, sizeOfChildren, and size variables.
	 * 
	 * @param keyList The current string of keys.
	 * @param resTop The resource whose keys shall be written to the keyList.
	 * @return
	 */
	private static String buildKeyList(String keyList, ICUResourceWriter.Resource resTop, int usedOffset)
	{
		ICUResourceWriter.Resource current = resTop.first;
		int x = 0;
		
		

		//add this resources key to the list unless it is the top resource or doesn't have a key
		if(!resTop.isTop && resTop.hasKey)
		{
			//clean up quotes if any
			if(resTop.name.indexOf("\"") >= 0)
			{
				resTop.name = removeQuotes(resTop.name);
			}
			//set the keyStringOffset
			resTop.keyStringOffset = usedOffset + (keyList.length() * BYTES_PER_UTF8_CHAR);
			keyList += (resTop.name + "\0");
			
			
		}
		
		
		
		//if it has children, call this method on them too
		while(current != null)
		{
			if(resTop instanceof ICUResourceWriter.ResourceArray || resTop instanceof ICUResourceWriter.ResourceIntVector)
			{
				current.hasKey = false;
			}
			
			
			keyList = buildKeyList(keyList, current, usedOffset);
			x++;
			
			//add the size of the current child to the parents sizeOfChildren
			
			current = current.next;
		}		
		
		//set the size of this object
		resTop.setSize();
		
		resTop.numChildren = x;
		return keyList;
	}
	
	/**
	 * Takes a 16 bit number and returns a two byte array. 0th element is lower byte, 1st element is upper byte.
	 * Ex: x = 28,000. In binary: 0110 1101 0110 0000. This method will return:
	 * [0] = 0110 0000 or 0x60
	 * [1] = 0110 1101 or 0x6D
	 */
	private static byte[] shortToBytes(short x)
	{
		byte[] b = new byte[2];
		b[1] = (byte)(x); //bitwise AND with the lower byte
		b[0] = (byte)(x >>> 8); //shift four bits to the right and fill with zeros, and then bitwise and with the lower byte
		return b;
	}
	
	/**
	 * Takes a 32 bit integer and returns an array of 4 bytes.
	 * 
	 */
	private static byte[] intToBytes(int x)
	{
		byte[] b = new byte[4];
		b[3] = (byte)(x); // just the last byte
		
		x = x >>> 8; //shift each byte over one spot.
		b[2] = (byte)(x); //just the last byte
		
		x = x >>> 8; //shift each byte over one spot.
		b[1] = (byte)(x); //just the last byte
		
		x = x >>> 8; //shift each byte over one spot.
		b[0] = (byte)(x); //just the last byte
		
		return b;
	}
	
	/**
	 * Takes an array of integers and returns a byte array of the memory representation.
	 * 
	 * @param x
	 * @return
	 */
	private static byte[] intArrayToBytes(int[] x)
	{
		byte[] b = new byte[x.length * 4];
		byte[] temp;
		int i, z;
		
		for(i = 0; i < x.length; i++)
		{
			temp = intToBytes(x[i]);
			for(z = 0; z < 4; z++)
			{
				b[(i*4)+z] = temp[z];
			}
		}
		return b;
	}
	
	/**
	 * calculate the padding to make things align with 32 bits (aka 4 bytes)
	 * @param x
	 * @return
	 */
	private static int pad32(int x)
	{
		return ((x % 4) == 0)? 0 : (4 - (x % 4));
	}
	
	private static int pad16Bytes(int x)
	{
		return ((x % 16) == 0)? 0 : (16 - (x % 16));
	}
	
	/**
	 * for printing errors.
	 */
	private static void printError(String message)
	{
        
		System.err.println("LDML2ICUBinaryWriter : ERROR : " + message);
    }
	private static byte[] createPadding(int length)
	{
		byte x = (byte)0x00;
		byte[] b = new byte[length];
		if(length == 0)
		{
			return null;
		}
		for(int z = 0; z < b.length; z++)
		{
			b[z] = x;
		}
		
		return b;
	}
	
	public static String removeQuotes(String s)
	{
		String temp = s;
		String temp2;
		int x;
		while(temp.indexOf("\"") >= 0)
		{
			x = temp.indexOf("\"");
			temp2 = temp.substring(0, x);
			temp2 += temp.substring(x+1, temp.length());
			temp = temp2;
		}
		
		return temp;
	}
	
}