//*************************************************************
//  Filename:       XurWriter.cs
//  Author:         MaesterRowen & Wondro (TeamFSD)
//  Date:           August 10, 2013
//  Description:    Class used to write XURv8 binaries.
//*************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections;

namespace XuiWorkshop
{
    class XurWriter : BEBinaryWriter
    {
        //                               STRN         VECT         QUAT       CUST        FLOT       COLR         KEYP         KEYD        NAME       DATA
        public uint[] SectionNames = { 0x5354524E, 0x56454354, 0x51554154, 0x43555354, 0x464C4F54, 0x434F4C52, 0x4B455950, 0x4B455944, 0x4E414D45, 0x44415441 };
        public string[] SectionDispalyName = { "STRN", "VECT", "QUAT", "CUST", "FLOT", "COLR", "KEYP", "KEYD", "NAME", "DATA" };

        
        // Method to intializing our reader object
        public XurWriter(System.IO.Stream stream) : base(stream) { }

        // Define our private structures
        private class XUIBinaryTables
        {
            public LookupTable<string> StringTable;
            public LookupTable<XUIVECTOR> VectorTable;
            public LookupTable<XUIQUATERNION> QuaternionTable;
            public LookupTable<float> FloatTable;
            public LookupTable<XUICOLOR> ColorTable;
            public LookupTable<XUICUSTOM> CustomTable;

            // Methods
            public XUIBinaryTables()
            {
                StringTable = new LookupTable<string>();
                VectorTable = new LookupTable<XUIVECTOR>();
                QuaternionTable = new LookupTable<XUIQUATERNION>();
                FloatTable = new LookupTable<float>();
                ColorTable = new LookupTable<XUICOLOR>();
                CustomTable = new LookupTable<XUICUSTOM>();
            }
        }
        private class XUIAnimationTables
        {
            public LookupTable<XUINAMEDFRAMEDATA> NamedframeTable;
            public LookupTable<XUIKEYFRAMEDATA> KeyframeDataTable;
            public LookupTable<XUIKEYFRAMEPROPDATA> KeyframePropTable;

            public XUIAnimationTables()
            {
                NamedframeTable = new LookupTable<XUINAMEDFRAMEDATA>();
                KeyframeDataTable = new LookupTable<XUIKEYFRAMEDATA>();
                KeyframePropTable = new LookupTable<XUIKEYFRAMEPROPDATA>();
            }
        }
        private struct XUILoadContext
        {
            public uint ObjectCount;                    // 100% correct
            public uint PropertyCount;                  // 100% correct
            public uint PropertyArrayCount;             // 100% correct
            public uint CompoundObjectPropCount;        // 100% correct
            public uint CompoundObjectPropArrayCount;   // 100% correct   
            public uint PropPathDepthCount;             // 100% correct
            public uint TimelinePropPathCount;          // 100% correct
            public uint SubTimelineCount;               // 100% correct
            public uint KeyframePropCount;              // 100% correct
            public uint KeyframeDataCount;              // 100% correct
            public uint NamedFrameCount;                // 100% correct
            public uint ObjectsWithChildrenCount;       // 100% correct
        }
        
        // Define our private global vars
        XUIBinaryTables BinaryTables = new XUIBinaryTables();
        XUIAnimationTables AnimationTables = new XUIAnimationTables();
        XUILoadContext LoadContext = new XUILoadContext();

        public void WriteObjectsToBinary(XUIOBJECTDATA rootObject)
        {
            // First let's set up any special table information here
            BinaryTables.StringTable.Add("");

            // Tracks whether or not a section is present
            ByteArray[] tableData = new ByteArray[10];

            // Next want to do is traverse our object data that will ultimately
            // create all of our tables and required data
            ByteArray objectByteArray = new ByteArray();
            WriteObjectToBinary(rootObject, ref objectByteArray);

            // Set up our quantity of animation objects
            LoadContext.NamedFrameCount = (uint)AnimationTables.NamedframeTable.Count;
            LoadContext.KeyframeDataCount = (uint)AnimationTables.KeyframeDataTable.Count;
            LoadContext.KeyframePropCount = (uint)AnimationTables.KeyframePropTable.Count;

            // Animation data has to be created first, as it can add strings and data to our binary tables
            // We write them backwards to the each table can build on the next
            if (objectByteArray.Count > 0)                          // DATA
                tableData[9] = objectByteArray;
            if (AnimationTables.NamedframeTable.Count > 0)          // NAME
                tableData[8] = WriteNamedFramesTable(AnimationTables.NamedframeTable);
            if (AnimationTables.KeyframeDataTable.Count > 0)        // KEYD
                tableData[7] = WriteKeyframeDataTable(AnimationTables.KeyframeDataTable);
            if (AnimationTables.KeyframePropTable.Count > 0)        // KEYP
                tableData[6] = WriteKeyframePropTable(AnimationTables.KeyframePropTable);
            // Write our binary tables- after everything has been added to them
            if (BinaryTables.ColorTable.Count > 0)                  // COLR
                tableData[5] = WriteColorTable(BinaryTables.ColorTable);
            if (BinaryTables.FloatTable.Count > 0)                  // FLOT
                tableData[4] = WriteFloatTable(BinaryTables.FloatTable);
            if (BinaryTables.CustomTable.Count > 0)                 // CUST
                tableData[3] = WriteCustomTable(BinaryTables.CustomTable);
            if (BinaryTables.QuaternionTable.Count > 0)            // QUAT
                tableData[2] = WriteQuaternionTable(BinaryTables.QuaternionTable);
            if (BinaryTables.VectorTable.Count > 0)                 // VECT
                tableData[1] = WriteVectorTable(BinaryTables.VectorTable);
            if (BinaryTables.StringTable.Count > 0)                 // STRN
                tableData[0] = WriteStringTable(BinaryTables.StringTable);

            // Write our very important header Info data
            XUR8_HEADER_INFO headerInfo = new XUR8_HEADER_INFO();
            headerInfo.ObjectCount = LoadContext.ObjectCount;
            headerInfo.PropertyCount = LoadContext.PropertyCount;
            headerInfo.PropertyArrayCount = LoadContext.PropertyArrayCount;
            headerInfo.CompoundObjectPropCount = LoadContext.CompoundObjectPropCount;
            headerInfo.CompoundObjectPropArrayCount = LoadContext.CompoundObjectPropArrayCount;
            headerInfo.PropPathDepthCount = LoadContext.PropPathDepthCount;
            headerInfo.TimelinePropPathCount = LoadContext.TimelinePropPathCount;
            headerInfo.SubTimelineCount = LoadContext.SubTimelineCount;
            headerInfo.KeyframePropCount = LoadContext.KeyframePropCount;
            headerInfo.KeyframeDataCount = LoadContext.KeyframeDataCount;
            headerInfo.NamedFrameCount = LoadContext.NamedFrameCount;
            headerInfo.ObjectsWithChildrenCount = LoadContext.ObjectsWithChildrenCount;
            ByteArray headerInfoData = WriteHeaderInfo(headerInfo);

            // Next we need to create all of our sections
            XUR8_SECTION [] sectionData = new XUR8_SECTION[10];
            ushort NumSections = 0;
            for( int sectionIdx = 0; sectionIdx < 10; sectionIdx++ ) 
            {
                if (tableData[sectionIdx] != null)
                {
                    sectionData[sectionIdx].Name = SectionNames[sectionIdx];
                    sectionData[sectionIdx].Offset = 0;
                    sectionData[sectionIdx].Size = (uint)tableData[sectionIdx].Count;
                    NumSections++;

                }
            }

            // Determine our base offset, which is the headerSize + headerInfoSize + numSections * sectionSize
            ByteArray sectionHeaderData = new ByteArray();
            uint fileSize = (uint)(0x14 + headerInfoData.Count + NumSections * 0xC);
            for( int sectionIdx = 0; sectionIdx < 10; sectionIdx++ )
            {
                if( tableData[sectionIdx] != null )
                {
                    sectionData[sectionIdx].Offset = fileSize;
                    fileSize+=sectionData[sectionIdx].Size;
                    
                    // Add this section to our byte array
                    sectionHeaderData.AddByteARRAY(WriteSectionHeader( sectionData[sectionIdx] ));
                }
            }

            // Create our header now
            XUR8_HEADER header = new XUR8_HEADER();
            header.Magic = 0x58554942;
            header.Version = 0x8;
            header.Flags = 0x0;
            header.XuiVersion = 0xE;
            header.BinSize = fileSize;
            header.NumSections = NumSections;
            ByteArray headerData = WriteHeader( header );

            // Write our file to disk
            headerData.WriteToFile(BaseStream);
            headerInfoData.WriteToFile(BaseStream);
            sectionHeaderData.WriteToFile(BaseStream);
            foreach (ByteArray array in tableData)
                if (array != null) array.WriteToFile(BaseStream);

            // Close our file
            Close();
        }

        public void WriteObjectToBinary(XUIOBJECTDATA objectData, ref ByteArray byteArray)
        {
            // Add this object
            LoadContext.ObjectCount++;

            // First thing we need to do for this object is write its string index
            int classNameIndex = BinaryTables.StringTable.GetIndex(objectData.ClassName);
            Debug.Assert(classNameIndex > -1, "Error obtaining index");
            byteArray.AddPackedDWORD((uint)classNameIndex);

            // Next we need to add the flags of this object
            byte objectFlags = 0x0;
            if (objectData.PropertyArray.Count > 0)     objectFlags |= 0x1;
            if (objectData.ChildrenArray.Count > 0)     objectFlags |= 0x2;
            if (objectData.SubTimelines.Count > 0)      objectFlags |= 0x4;
            if (objectData.NamedFrameArray.Count > 0)   objectFlags |= 0x4;
            byteArray.AddBYTE(objectFlags);

            // Write our objects properties
            if (objectData.PropertyArray.Count > 0)
            {
                // Count all the properties without flag 0x100
                uint propCount = 0;
                foreach (XUIPROPERTYDATA propData in objectData.PropertyArray)
                {
                    if ((propData.Flags & 0x100) == 0x100) 
                        continue;
                    propCount++;
                    LoadContext.PropertyCount++;
                }

                // Write the number of properties to our array
                byteArray.AddPackedDWORD((uint)propCount);
                LoadContext.PropertyArrayCount++;
                
                // Retrieve all of the classes for our object and add our properties
                List<IList> classList = (List<IList>)XuiClass.Instance.GetHierarchy(objectData.ClassName);
                foreach (List<XUIELEM_PROP_DEF> propDefList in classList)
                    WriteObjectPropsToBinary( objectData.PropertyArray, propDefList, ref byteArray);

            }

            // Write our objects Children
            if (objectData.ChildrenArray.Count > 0)
            {
                // Write the number of childresn to our array
                byteArray.AddPackedDWORD((uint)objectData.ChildrenArray.Count);
                LoadContext.ObjectsWithChildrenCount++;

                // Recursively write all of this object's children
                foreach (XUIOBJECTDATA objectChild in objectData.ChildrenArray)
                    WriteObjectToBinary(objectChild, ref byteArray);
            }

            // Write our timelines
            if (objectData.SubTimelines.Count > 0 || objectData.NamedFrameArray.Count > 0 )
            {
                // Handle any timelines this object may have
                WriteTimelineDataToBinary(objectData, ref byteArray);
            }
        }

        private void WriteObjectPropsToBinary(List<XUIPROPERTYDATA> propList, List<XUIELEM_PROP_DEF> propDefList, ref ByteArray byteArray)
        {
            //  Build our property mask
            List<XUIPROPERTYDATA> subPropList = new List<XUIPROPERTYDATA>();
            uint nPropertyMask = 0;
            byte nFlagIdx = 0;
            foreach (XUIELEM_PROP_DEF propDef in propDefList)
            {
                uint nFlag = (uint)1 << nFlagIdx;
                    
                // Loop through each property this object has and build mask
                foreach (XUIPROPERTYDATA propData in propList)
                {
                    // If 0x100 flag is set, then this property was added by program and doesnt need to be written to xur
                    if ((propData.Flags & 0x100) == 0x100) continue;

                    // If we have a match, let's add to our property mask
                    if (propData.PropDef.PropName == propDef.PropName)
                    {
                        subPropList.Add(propData);

                        // Only add the flag for the first index of this property
                        if (propData.Index == 0)
                        {
                            nPropertyMask += nFlag;
                        }
                    }
                }

                // Increment our index for our flag data
                nFlagIdx++;
            }

            // Save our property mask for this class
            byteArray.AddPackedDWORD(nPropertyMask);

            // Write our properties
            if( nPropertyMask > 0 )
                WritePropertiesToBinary(subPropList, propDefList, ref byteArray);
        }

        private void WritePropertiesToBinary(List<XUIPROPERTYDATA> propDataList, List<XUIELEM_PROP_DEF> propDefList, ref ByteArray byteArray)
        {
            // Loop through each property that this file has stored
            foreach (XUIPROPERTYDATA propData in propDataList)
            {
                // Check if this property is indexed (and the first in the series)
                if( propData.Index == 0 && (uint)(propData.Flags & 0x1) == 0x1)
                {
                    // We need to figure out how many properties we have indices for
                    uint numIndices = 0;
                    foreach (XUIPROPERTYDATA idxProp in propDataList)
                    {
                        if (idxProp.PropDef.PropName == propData.PropDef.PropName)
                            numIndices++;
                    }

                    // Write this number to file
                    byteArray.AddPackedDWORD(numIndices);
                }

                // Now write each property
                if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_STRING)
                {
                    int index = BinaryTables.StringTable.GetIndex(propData.PropValue.ToString());
                    byteArray.AddPackedDWORD((uint)index);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_COLOR)
                {
                    int index = BinaryTables.ColorTable.GetIndex(propData.PropValue.ToString().ToXuiColor());
                    byteArray.AddPackedDWORD((uint)index);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_FLOAT)
                {
                    int index = BinaryTables.FloatTable.GetIndex(Single.Parse(propData.PropValue.ToString()));
                    byteArray.AddPackedDWORD((uint)index);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_INTEGER)
                {
                    byteArray.AddPackedDWORD((uint)Int32.Parse(propData.PropValue.ToString()));
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_UNSIGNED)
                {
                    byteArray.AddPackedDWORD(UInt32.Parse(propData.PropValue.ToString()));
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_BOOL)
                {
                    byteArray.AddBOOL(Convert.ToBoolean(propData.PropValue.ToString()));
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_VECTOR)
                {
                    int index = BinaryTables.VectorTable.GetIndex(propData.PropValue.ToString().ToXuiVector());
                    byteArray.AddPackedDWORD((uint)index);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_QUATERNION)
                {
                    int index = BinaryTables.QuaternionTable.GetIndex(propData.PropValue.ToString().ToXuiQuaternion());
                    byteArray.AddPackedDWORD((uint)index);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_CUSTOM)
                {
                   
                    int index = BinaryTables.CustomTable.GetIndex(propData.PropValue.ToString().ToXuiCustom());
                    // Get our offset
                    uint offset = 0;
                    for (int x = 0; x < index; x++)
                    {
                        XUICUSTOM custom = (XUICUSTOM)BinaryTables.CustomTable[x];
                        ByteArray custArray = custom.ToBinary();
                        offset += (uint)custArray.Count;
                    }
                    // Write our offset to the binary
                    byteArray.AddPackedDWORD((uint)offset);
                }
                else if (propData.PropType == XUIELEM_PROP_TYPE.XUI_EPT_OBJECT)
                {
                    // Write our current compound object count (acting as our id)
                    byteArray.AddPackedDWORD((uint)LoadContext.CompoundObjectPropArrayCount);

                    // Determine which compound prop this is
                    List<XUIELEM_PROP_DEF> subPropArray = new List<XUIELEM_PROP_DEF>();
                    switch (propData.PropDef.PropName)
                    {
                        case "Fill":
                            subPropArray = XuiClass.Instance.GetFillPropArray();
                            break;
                        case "Gradient":
                            subPropArray = XuiClass.Instance.GetGradientPropArray();
                            break;
                        case "Stroke":
                            subPropArray = XuiClass.Instance.GetStrokePropArray();
                            break;
                    }

                    // Now we need to figure out how many properties we have
                    List<XUIPROPERTYDATA> compoundProps = (List<XUIPROPERTYDATA>)propData.PropValue;

                    // Count all the properties without flag 0x100
                    uint propCount = 0;
                    foreach (XUIPROPERTYDATA compoundPropData in compoundProps)
                    {
                        if ((compoundPropData.Flags & 0x100) == 0x100) continue;
                        propCount++;
                        LoadContext.CompoundObjectPropCount++;
                    }

                    // Write the number of props to our byte array
                    byteArray.AddPackedDWORD((uint)propCount);
                    // Increment our compoundProp count(id)
                    LoadContext.CompoundObjectPropArrayCount++;

                    // Write the properties for this object
                    WriteObjectPropsToBinary(compoundProps, subPropArray, ref byteArray);
                }
                else
                {
                    Debug.Assert(false, "Unknown Property Type");
                }
            }
        }

        private void BuildNamedFrameTable( XUIOBJECTDATA objectData, ref LookupTable<XUINAMEDFRAMEDATA> nfTable )
        {

            // Loop through this objects children
            foreach (XUIOBJECTDATA objectChild in objectData.ChildrenArray)
            {
                // Build named frame table from this object
                BuildNamedFrameTable(objectChild, ref nfTable);
            }
        }

        private void WriteTimelineDataToBinary(XUIOBJECTDATA objectData, ref ByteArray byteArray)
        {
            
            // Now we'll get our number of named frames and write to our byte array
            uint numFrames = objectData.NumNamedFrames;
            byteArray.AddPackedDWORD(numFrames);

            // Then we'll write our named frame index
            if (numFrames > 0)
            {
                int index = AnimationTables.NamedframeTable.GetIndex(objectData.NamedFrameArray);
                Debug.Assert(index >= 0, "Invalid Named Frame Index");
                byteArray.AddPackedDWORD((uint)index);
            }

            // Write the number of subtimelines
            uint numSubtimelines = objectData.NumSubTimelines;
            byteArray.AddPackedDWORD(numSubtimelines);

            LoadContext.SubTimelineCount += (uint)objectData.SubTimelines.Count;

            // Loop through each subtimeline and build our tables/data
            foreach( XUISUBTIMELINEDATA subData in objectData.SubTimelines )
            {
                // Look up our element id for this subtimeline
                int index = BinaryTables.StringTable.GetIndex(subData.ElementId);
                byteArray.AddPackedDWORD((uint)index);

                // Add the number of proppaths we are defining
                byteArray.AddPackedDWORD((uint)subData.NumPropPaths);

                // Create our PropPath data
                foreach (XUITIMELINEPROPPATH propPath in subData.PropPathArray)
                {
                    // Write our property data here
                    WriteSubtimelineDataToBinary(subData.ElementId, propPath, objectData, ref byteArray);

                    LoadContext.TimelinePropPathCount++;
                }

                // Now we write our keyframe data for this subtimeline
                byteArray.AddPackedDWORD((uint)subData.KeyframeDataArray.Count);

                // Loop through each keyframe - we're going to be building our KEYP table
                List<XUIKEYFRAMEDATA> kfArray = new List<XUIKEYFRAMEDATA>();
                foreach (XUIKEYFRAMEDATA kfData in subData.KeyframeDataArray)
                {
                    // Store our new kfData
                    XUIKEYFRAMEDATA newKfData = new XUIKEYFRAMEDATA();
                    newKfData = kfData;

                    // Loop through each namedframe prop
                    List<XUIKEYFRAMEPROPDATA> kpArray = new List<XUIKEYFRAMEPROPDATA>();
                    int propIdx = 0;
                    foreach (XUITIMELINEPROPPATH tlPath in subData.PropPathArray)
                    {
                        // Determine the definition for this prop item
                        XUIELEM_PROP_DEF propDef = tlPath.PropDefArray[(int)tlPath.PropDefArray.Count - 1];

                        // Create our prop Data
                        XUIKEYFRAMEPROPDATA propData = new XUIKEYFRAMEPROPDATA();

                        if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_BOOL )
                        {
                            bool val = Convert.ToBoolean(kfData.PropList[propIdx].PropValue);
                            propData.PropertyIndex = (uint)(val == true ? 1 : 0);
                        }
                        else if( propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_INTEGER )
                        {
                            int val = (int)kfData.PropList[propIdx].PropValue;
                            propData.PropertyIndex = (uint)val;
                        }
                        else if( propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_UNSIGNED)
                        {
                            uint val = (uint)kfData.PropList[propIdx].PropValue;
                            propData.PropertyIndex = (uint)val;
                        }
                        else if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_STRING)
                        {
                            string val = (string)kfData.PropList[propIdx].PropValue;
                            int idx = BinaryTables.StringTable.GetIndex(val);
                            propData.PropertyIndex = (uint)idx;
                        }
                        else if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_FLOAT)
                        {
                            float val = (float)kfData.PropList[propIdx].PropValue;
                            int idx = BinaryTables.FloatTable.GetIndex(val);
                            propData.PropertyIndex = (uint)idx;
                        }
                        else if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_VECTOR)
                        {
                            XUIVECTOR val = (XUIVECTOR)kfData.PropList[propIdx].PropValue;
                            int idx = BinaryTables.VectorTable.GetIndex(val);
                            propData.PropertyIndex = (uint)idx;
                        }
                        else if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_QUATERNION)
                        {
                            XUIQUATERNION val = (XUIQUATERNION)kfData.PropList[propIdx].PropValue;
                            int idx = BinaryTables.QuaternionTable.GetIndex(val);
                            propData.PropertyIndex = (uint)idx;
                        }
                        else if (propDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_COLOR)
                        {
                            XUICOLOR val = (XUICOLOR)kfData.PropList[propIdx].PropValue;
                            int idx = BinaryTables.ColorTable.GetIndex(val);
                            propData.PropertyIndex = (uint)idx;
                        }

                        // Now we add this property index to our kpList
                        kpArray.Add(propData);
                        propIdx++;
                    }

                    // Now we add this entire keyframeprop array to our keyframe prop table
                    int pIdx = AnimationTables.KeyframePropTable.GetIndex(kpArray);
                   
                    // Update our keyframedata property index then add this to our array
                    newKfData.KeyframePropIdx = (uint)pIdx;
                    kfArray.Add(newKfData);
                }


                // Add our keyframes to our table and grab an index
                int kfIndex = AnimationTables.KeyframeDataTable.GetIndex(kfArray);
                byteArray.AddPackedDWORD((uint)kfIndex);
            }               
        }

        private void WriteSubtimelineDataToBinary(string elementId, XUITIMELINEPROPPATH propPath, XUIOBJECTDATA objectData, ref ByteArray byteArray)
        {
            // First thing we need to do at this point is write our flags bit
            // bitFlag = Indexed | PathDepth

            byte pathDepth = (byte)(propPath.Depth);
            byte loadIndex = (byte)((propPath.PropDefArray[(int)propPath.PropDefArray.Count - 1].Flags & 0x1) << 7);
            byte bitFlag = (byte)((loadIndex | pathDepth));

            // Write this bitflag
            byteArray.AddBYTE(bitFlag);

            LoadContext.PropPathDepthCount += pathDepth;

            // Search for this object so we can find its class
            string className = "";
            bool foundChild = false;
            foreach (XUIOBJECTDATA objChild in objectData.ChildrenArray)
            {
                string childId = (string)objChild.GetPropVal("Id");
                if (childId == "") continue;

                if (childId == elementId)
                {
                    className = objChild.ClassName;
                    foundChild = true;
                    break;
                }
            }

            // Major problem if we cna't find the child holding the subtimeline
            Debug.Assert(foundChild == true, "Child containing subtimeline not found");

            // Verify we have this class loaded
            XUI_CLASS pClass = XuiClass.Instance.FindClass(className);
            Debug.Assert(pClass.szClassName != null, "Class Properties could not be found");

            // Now we need to start searching for this property so we can obtain its depth and its index
            List<IList> classList = (List<IList>)XuiClass.Instance.GetHierarchy(className);
            classList.Reverse();

            // Loop through the class list to try and find our property
            bool foundProp = false;
            uint propDepth = 0;
            uint propIndex = 0;

            XUIELEM_PROP_DEF targetDef = propPath.PropDefArray[0];

            foreach( List<XUIELEM_PROP_DEF> propList in classList )
            {
                foreach( XUIELEM_PROP_DEF propDef in propList )
                {
                    if (propDef.PropName == targetDef.PropName)
                    {
                        foundProp = true;
                        break;
                    }

                    // increment our index
                    propIndex++;
                }
                if( foundProp == true ) break;

                // increment our depth
                propIndex = 0;
                propDepth++;
                
            }

            // Add depth and prop index
            byteArray.AddBYTE((byte)(propDepth));
            byteArray.AddBYTE((byte)(propIndex));

            if (pathDepth > 1)
            {
                for (uint cpd = 1; cpd < pathDepth; cpd++)
                {
                    // This has to be type OBJECT
                    Debug.Assert(targetDef.Type == XUIELEM_PROP_TYPE.XUI_EPT_OBJECT, "Invalid Prop Type");

                    // Grab the new array list
                    List<XUIELEM_PROP_DEF> propList = targetDef.GetPropDefs();

                    XUIELEM_PROP_DEF midPropDef = propPath.PropDefArray[(int)cpd];
                    int indx = 0;
                    foreach (XUIELEM_PROP_DEF prop in propList)
                    {
                        if (prop.PropName == midPropDef.PropName)
                        {
                            break;
                        }
                        indx++;
                    }

                    // Write our prop index to our bytearray
                    byteArray.AddBYTE((byte)indx);

                    // Update our def
                    targetDef = midPropDef;
                }
            }

            // Write our index if we have one
            if (loadIndex > 0) byteArray.AddPackedDWORD(propPath.Index);
        }

        private ByteArray WriteHeader(XUR8_HEADER header)
        {
            // Create a new byte array object
            ByteArray byteArray = new ByteArray();

            // Start creating the array from our header struct
            byteArray.AddDWORD(header.Magic);
            byteArray.AddDWORD(header.Version);
            byteArray.AddDWORD(header.Flags);
            byteArray.AddWORD(header.XuiVersion);
            byteArray.AddDWORD(header.BinSize);
            byteArray.AddWORD(header.NumSections);

            return byteArray;
        }

        private ByteArray WriteHeaderInfo(XUR8_HEADER_INFO headerInfo)
        {
            ByteArray byteArray = new ByteArray();
            
            // Write our header info to file
            byteArray.AddPackedDWORD((uint)headerInfo.ObjectCount);
            byteArray.AddPackedDWORD((uint)headerInfo.PropertyCount);
            byteArray.AddPackedDWORD((uint)headerInfo.PropertyArrayCount);
            byteArray.AddPackedDWORD((uint)headerInfo.CompoundObjectPropCount);
            byteArray.AddPackedDWORD((uint)headerInfo.CompoundObjectPropArrayCount);
            byteArray.AddPackedDWORD((uint)headerInfo.PropPathDepthCount);
            byteArray.AddPackedDWORD((uint)headerInfo.TimelinePropPathCount);
            byteArray.AddPackedDWORD((uint)headerInfo.SubTimelineCount);
            byteArray.AddPackedDWORD((uint)headerInfo.KeyframePropCount);
            byteArray.AddPackedDWORD((uint)headerInfo.KeyframeDataCount);
            byteArray.AddPackedDWORD((uint)headerInfo.NamedFrameCount);
            byteArray.AddPackedDWORD((uint)headerInfo.ObjectsWithChildrenCount);

            return byteArray;
        }

        private ByteArray WriteSectionHeader(XUR8_SECTION sectionInfo)
        {
            ByteArray byteArray = new ByteArray();

            // Write our data
            byteArray.AddDWORD(sectionInfo.Name);
            byteArray.AddDWORD(sectionInfo.Offset);
            byteArray.AddDWORD(sectionInfo.Size);

            return byteArray;
        }

        // Binary Table Writing Methods
        private ByteArray WriteCustomTable(LookupTable<XUICUSTOM> customTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUICUSTOM customEntry in customTable)
            {
                byteArray.AddByteARRAY(customEntry.ToBinary());
            }

            return byteArray;
        }
        private ByteArray WriteFloatTable(LookupTable<float> floatTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (float floatEntry in floatTable)
                byteArray.AddFLOAT(floatEntry);

            return byteArray;
        }
        private ByteArray WriteColorTable(LookupTable<XUICOLOR> colorTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUICOLOR colorEntry in colorTable)
                byteArray.AddDWORD(colorEntry.argb);

            return byteArray;
        }
        private ByteArray WriteVectorTable(LookupTable<XUIVECTOR> vectorTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUIVECTOR vectorEntry in vectorTable)
            {
                byteArray.AddFLOAT(vectorEntry.x);
                byteArray.AddFLOAT(vectorEntry.y);
                byteArray.AddFLOAT(vectorEntry.z);
            }

            return byteArray;
        }
        private ByteArray WriteQuaternionTable(LookupTable<XUIQUATERNION> quatTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUIQUATERNION quatEntry in quatTable)
            {
                byteArray.AddFLOAT(quatEntry.x);
                byteArray.AddFLOAT(quatEntry.y);
                byteArray.AddFLOAT(quatEntry.z);
                byteArray.AddFLOAT(quatEntry.w);
            }

            return byteArray;
        }
        private ByteArray WriteStringTable(LookupTable<string> stringTable)
        {
            ByteArray stringData = new ByteArray();

            ushort nCount = 0;
            foreach (string s in stringTable)
            {
                if (s == "") continue;
                stringData.AddSTRING(s);
                nCount++;
            }

            uint nSize = (uint)stringData.Count;

            ByteArray byteArray = new ByteArray();

            byteArray.AddDWORD(nSize);
            byteArray.AddWORD(nCount);
            byteArray.AddByteARRAY(stringData);

            return byteArray;
        }

        // Animation Table Writing Methods
        private ByteArray WriteNamedFramesTable(LookupTable<XUINAMEDFRAMEDATA> nfTable )
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUINAMEDFRAMEDATA nfd in nfTable)
            {
                // Write our string index
                int nameStringIdx = BinaryTables.StringTable.GetIndex((string)nfd.Name);
                byteArray.AddPackedDWORD((uint)nameStringIdx);

                // Write our time
                byteArray.AddPackedDWORD(nfd.Time);

                // Write our command and target frame(if necessary)
                byteArray.AddBYTE((byte)nfd.Command);
                if (nfd.Command >= XUI_NAMEDFRAME_COMMAND.XUI_CMD_GOTO)
                {
                    int targetStringIdx = BinaryTables.StringTable.GetIndex((string)nfd.Target);
                    byteArray.AddPackedDWORD((uint)targetStringIdx);
                }
            }

            return byteArray;
        }
        private ByteArray WriteKeyframePropTable(LookupTable<XUIKEYFRAMEPROPDATA> kfpTable )
        {
            ByteArray byteArray = new ByteArray();
            foreach (XUIKEYFRAMEPROPDATA propData in kfpTable)
            {
                byteArray.AddPackedDWORD(propData.PropertyIndex); 
            }
            return byteArray;
        }
        private ByteArray WriteKeyframeDataTable(LookupTable<XUIKEYFRAMEDATA> kfdTable)
        {
            ByteArray byteArray = new ByteArray();

            foreach (XUIKEYFRAMEDATA kfData in kfdTable)
            {
                // Write our frame
                byteArray.AddPackedDWORD(kfData.Frame);
                
                // Determine our flag (we dont know the other flags, so just handle 0, 1, or 2)
                // TODO handle properly
                byteArray.AddBYTE((byte)kfData.InterpolateType);

                // Write our easing information
                if (kfData.InterpolateType == XUI_INTERPOLATE.XUI_INTERPOLATE_EASE)
                {
                    byteArray.AddBYTE((byte)kfData.EaseIn);
                    byteArray.AddBYTE((byte)kfData.EaseOut);
                    byteArray.AddBYTE((byte)kfData.EaseScale);
                }
                // TODO:  Handle other types here

                // Write our keyframe propIndex
                byteArray.AddPackedDWORD((uint)kfData.KeyframePropIdx);
            }
            return byteArray;
        }

        private void WriteUnpackedUlong(uint value)
        {
            // Check our upper range (65,279)
            if (value > (uint)0xEFF)
            {
                Write((byte)0xFF);
                Write((int)value);
            }
            else if (value >= (uint)0xF0)
            {
                uint highPart = value >> 8;
                highPart |= 0xF0;
                byte lowPart = (byte)(value & 0xFF);

                Write((byte)highPart);
                Write((byte)lowPart);
            }
            else
            {
                Write((byte)(value & 0xFF));
            }
        }
    }
}
