using Ionic.Zlib;
using General;
using CXML;

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Globalization;

namespace CXMLDecompiler
{
    class CXMLReader
    {
        private bool CheckMagicNumber()
        {

            String Magic = Tools.ReadStringAt(InfoFile, 0x00);
            MagicNumber = Magic.Substring(0,4);

            if (Magic.StartsWith("PSMA")) // PlayStation Mobile "app.info"
            {
                return true;
            }
            else if(Magic.StartsWith("RCOF")) // Vita/Ps4/Ps3 RCO
            {
                return true;
            }
            else if (Magic.StartsWith("RCSF")) // Found inside RCO
            {
                return true;
            }
            else if (Magic.StartsWith("P3TF")) // ps3 theme file
            {
                return true;
            }
            else if (Magic.StartsWith("CXML")) // cxml.py default
            {
                return true;
            }
            else if (Magic.StartsWith("TEST")) // ps3 cxml tools example
            {
                return true;
            }
            else
            {
                return false;
            }
        }


        String MainDir = "";
        String FileDir = "";
        String XMLFilename = "";
        String MagicNumber = "";

        List<TypingInformation> SilicaTypingInformationList = new List<TypingInformation>();

        String MagicReplacePattern = Tools.GenerateReplacePattern();
        String SilicaTypingInformation = "SilicaTypingInformation{{[[";
        FileStream InfoFile;

        MemoryStream TreeTable;
        MemoryStream IDTable;
        MemoryStream HashIDTable;
        MemoryStream StringTable;
        MemoryStream WStringTable;
        MemoryStream HashTable;
        MemoryStream IntArrayTable;
        MemoryStream FloatArrayTable;
        MemoryStream FileTable;

        Tools tools;

        Boolean IsInitalized = false;

        CXML.Version FileVersion;

        public Endianness FileEndainness = Endianness.LITTLE_ENDIAN;

        List<LoopbackHandler> FileList = new List<LoopbackHandler>();

        XmlWriter XMLWriter;

        public bool ps3 = false;
        public bool ProcessFiles = false;
        public bool NoRecursive = false;
        public void Init(string path, string outPath, bool CheckMagic = true, bool forcePs3 = false, Endianness forceEndainness = Endianness.UNCHANGED)
        {
            InfoFile = File.Open(path, FileMode.Open, FileAccess.Read);
            bool MagicValid = CheckMagicNumber();
            if (CheckMagic)
            {
                if (!MagicValid)
                {
                    throw new Exception("Incorrect magic number.");
                }

            }
            // This is a bit of a hack, but what can you do?
            FileVersion = ReadVersionEnum();
            if (FileVersion == CXML.Version.PS3)
            {
                ps3 = true;
                FileEndainness = Endianness.BIG_ENDIAN;
            }
            else
            {
                FileEndainness = Endianness.LITTLE_ENDIAN;
            }

            if (forcePs3)
                ps3 = true;

            if (forceEndainness != Endianness.UNCHANGED)
                FileEndainness = forceEndainness;

            tools = new Tools(FileEndainness == Endianness.BIG_ENDIAN);

            TreeTable = Tools.ByteToStream(GetTreeTable());
            StringTable = Tools.ByteToStream(GetStringTable());
            IntArrayTable = Tools.ByteToStream(GetIntArrayTable());
            FloatArrayTable = Tools.ByteToStream(GetFloatArrayTable());
            IDTable = Tools.ByteToStream(GetIDTable());
            FileTable = Tools.ByteToStream(GetFileTable());

            // psvita, ps4 exclusive
            if (!ps3)
            {
                HashTable = Tools.ByteToStream(GetHashTable());
                HashIDTable = Tools.ByteToStream(GetHashIDTable());
                WStringTable = Tools.ByteToStream(GetWStringTable());
            }
            else
            {
                HashTable = new MemoryStream();
                HashIDTable = new MemoryStream();
                WStringTable = new MemoryStream();
            }

            MainDir = Path.GetDirectoryName(outPath);
            FileDir = Path.Combine(MainDir, Path.GetFileNameWithoutExtension(outPath) + "_files");
            XMLFilename = Path.GetFileName(outPath);

            IsInitalized = true;

            return;
        }

        public void Term()
        {
            InfoFile.Close();
            TreeTable.Close();
            IDTable.Close();
            HashIDTable.Close();
            StringTable.Close();
            WStringTable.Close();
            HashTable.Close();
            IntArrayTable.Close();
            FloatArrayTable.Close();
            FileTable.Close();

            FileList.Clear();
            IsInitalized = false;
        }

        public int GetTreeTableOffset()
        {
            return tools.ReadIntAt(InfoFile,0x8);
        }

        public int GetTreeTableSize()
        {
            return tools.ReadIntAt(InfoFile, 0xC);
        }

        public int GetIDTableOffset()
        {
            return tools.ReadIntAt(InfoFile, 0x10);
        }

        public int GetIDTableSize()
        {
            return tools.ReadIntAt(InfoFile, 0x14);
        }

        public int GetHashIDTableOffset()
        {
            return tools.ReadIntAt(InfoFile, 0x18);
        }

        public int GetHashIDTableSize()
        {
            return tools.ReadIntAt(InfoFile, 0x1C);
        }

        public int GetStringTableOffset()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x18);
            else
                return tools.ReadIntAt(InfoFile, 0x20);
        }

        public int GetStringTableSize()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x1C);
            else
                return tools.ReadIntAt(InfoFile, 0x24);
        }

        public int GetWStringTableOffset()
        {
            return tools.ReadIntAt(InfoFile, 0x28);
        }

        public int GetWStringTableSize()
        {
            return tools.ReadIntAt(InfoFile, 0x2C);
        }

        public int GetHashTableOffset()
        {
            return tools.ReadIntAt(InfoFile, 0x30);
        }

        public int GetHashTableSize()
        {
            return tools.ReadIntAt(InfoFile, 0x34);
        }
        public int GetIntArrayTableOffset()
        {
            if(ps3)
                return tools.ReadIntAt(InfoFile, 0x20);
            else
                return tools.ReadIntAt(InfoFile, 0x38);
        }

        public int GetIntArrayTableSize()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x24);
            else
                return tools.ReadIntAt(InfoFile, 0x3C);
        }

        public int GetFloatArrayTableOffset()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x28);
            else
                return tools.ReadIntAt(InfoFile, 0x40);
        }

        public int GetFloatArrayTableSize()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x2C);
            else
                return tools.ReadIntAt(InfoFile, 0x44);
        }

        public int GetFileTableOffset()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x30);
            else
                return tools.ReadIntAt(InfoFile, 0x48);
        }

        public int GetFileTableSize()
        {
            if (ps3)
                return tools.ReadIntAt(InfoFile, 0x34);
            else
                return tools.ReadIntAt(InfoFile, 0x4C);
        }

        public byte[] GetTreeTable()
        {
            int TableOffset = GetTreeTableOffset();
            int TableSize = GetTreeTableSize();
            InfoFile.Seek(TableOffset, SeekOrigin.Begin);
            byte[] Table = new byte[TableSize];
            InfoFile.Read(Table, 0x00, TableSize);
            return Table;
        }

        public byte[] GetIDTable()
        {
            int IDOffset = GetIDTableOffset();
            int IDTableSize = GetIDTableSize();
            InfoFile.Seek(IDOffset, SeekOrigin.Begin);
            byte[] IDTable = new byte[IDTableSize];
            InfoFile.Read(IDTable, 0x00, IDTableSize);
            return IDTable;
        }

        public byte[] GetHashIDTable()
        {
            int HashIDTableOffset = GetHashIDTableOffset();
            int HashIDTableSize = GetHashIDTableSize();
            InfoFile.Seek(HashIDTableOffset, SeekOrigin.Begin);
            byte[] HashIDTable = new byte[HashIDTableSize];
            InfoFile.Read(HashIDTable, 0x00, HashIDTableSize);
            return HashIDTable;
        }

        public byte[] GetStringTable()
        {
            int StringTableOffset = GetStringTableOffset();
            int StringTableSize = GetStringTableSize();
            InfoFile.Seek(StringTableOffset, SeekOrigin.Begin);
            byte[] StringTable = new byte[StringTableSize];
            InfoFile.Read(StringTable, 0x00, StringTableSize);
            return StringTable;
        }

        public byte[] GetWStringTable()
        {
            int CharTableOffset = GetWStringTableOffset();
            int CharTableSize = GetWStringTableSize();
            InfoFile.Seek(CharTableOffset, SeekOrigin.Begin);
            byte[] CharTable = new byte[CharTableSize];
            InfoFile.Read(CharTable, 0x00, CharTableSize);
            return CharTable;
        }

        public byte[] GetHashTable()
        {
            int HashTableOffset = GetHashTableOffset();
            int HashTableSize = GetHashTableSize();  
            InfoFile.Seek(HashTableOffset, SeekOrigin.Begin);
            byte[] HashTable = new byte[HashTableSize];
            InfoFile.Read(HashTable, 0x00, HashTableSize);
            return HashTable;
        }

        public byte[] GetIntArrayTable()
        {
            int IntArrayTableOffset = GetIntArrayTableOffset();
            int IntArrayTableSize = GetIntArrayTableSize();
            InfoFile.Seek(IntArrayTableOffset, SeekOrigin.Begin);
            byte[] IntArrayTable = new byte[IntArrayTableSize];
            InfoFile.Read(IntArrayTable, 0x00, IntArrayTableSize);
            return IntArrayTable;
        }

        public byte[] GetFloatArrayTable()
        {
            int FloatArrayTableOffset = GetFloatArrayTableOffset();
            int FloatArrayTableSize = GetFloatArrayTableSize();
            InfoFile.Seek(FloatArrayTableOffset, SeekOrigin.Begin);
            byte[] FloatArrayTable = new byte[FloatArrayTableSize];
            InfoFile.Read(FloatArrayTable, 0x00, FloatArrayTableSize);
            return FloatArrayTable;
        }

        public byte[] GetFileTable()
        {
            int DataOffset = GetFileTableOffset();
            int DataLength = GetFileTableSize();
            InfoFile.Seek(DataOffset, SeekOrigin.Begin);
            byte[] FileTable = new byte[DataLength];
            InfoFile.Read(FileTable, 0x00, DataLength);
            return FileTable;
        }
        public CXML.Version ReadVersionEnum()
        {
            try
            {
                return (CXML.Version)ReadVersionInt();
            }
            catch (Exception)
            {
                return CXML.Version.UNKNOWN;
            }
        }
        public int ReadVersionInt()
        {
            return Tools.ReadLittleEndainIntAt(InfoFile, 0x4);
        }
        public void AddTypingInfo(string key, object value)
        {
            foreach(TypingInformation tInfo in SilicaTypingInformationList)
            {
                if(tInfo.key == key)
                {
                    if (tInfo.value.ToString() != value.ToString())
                        throw new Exception("Very Weird CXML");
                    return;
                }    
            }
            TypingInformation typingInfo = new TypingInformation();
            typingInfo.key = key;
            typingInfo.value = value.ToString();
            SilicaTypingInformationList.Add(typingInfo);
        }
        public void Align(Stream s, int align)
        {

            int size = Convert.ToInt32(s.Position);
            if (size % align != 0)
            {
                int totalAlign = (align - (size % align));
                for (int i = 0; i < totalAlign; i++)
                {
                    s.Seek(0x1, SeekOrigin.Current);
                }
            }
        }
        public void GotoEnd()
        {
            InfoFile.Seek(GetFileTableOffset(), SeekOrigin.Begin);
            InfoFile.Seek(GetFileTableSize(), SeekOrigin.Current);
            Align(InfoFile, 0x10);
        }

        public void DecompileCXML(String CXMLFile, String XMLOutputFile)
        {

            if (!IsInitalized)
                Init(CXMLFile, XMLOutputFile);

            XmlWriterSettings XMLSettings = new XmlWriterSettings();
            XMLSettings.Indent = true;
            XMLSettings.Encoding = Encoding.UTF8;

            string XMLPath = Path.Combine(MainDir, XMLFilename);

            StreamWriter sw = new StreamWriter(new MemoryStream(), Encoding.UTF8);


            XMLWriter = XmlWriter.Create(sw, XMLSettings);
            XMLWriter.WriteStartDocument();
            XMLWriter.WriteComment(MagicReplacePattern);
            AddTypingInfo("MAGIC", MagicNumber);
            AddTypingInfo("VERSION", "0x"+ReadVersionInt().ToString("X"));
            AddTypingInfo("ENDAINESS", FileEndainness.ToString());
            AddTypingInfo("PS3", ps3 ? "true" : "false");
            ReadElements();
            XMLWriter.WriteEndDocument();
            XMLWriter.Flush();
            XMLWriter.Close();

            GotoEnd();
            int bytesRemaining = Convert.ToInt32(InfoFile.Length - InfoFile.Position);
            if (bytesRemaining > 0) // Some RCS files have unexplainable random XML Data at the end of them, Dont ask me. im just the messanger
            {
                Console.WriteLine(bytesRemaining.ToString("X8", CultureInfo.InvariantCulture) + " Excess Bytes Found, Shoving it in SilicaTypingInformation.");
                byte[] ExcessData = new byte[bytesRemaining];
                InfoFile.Read(ExcessData, 0x00, bytesRemaining);
                byte[] Compressed = ZlibStream.CompressBuffer(ExcessData);
                string base64 = Convert.ToBase64String(Compressed).Replace("=", "*");
                AddTypingInfo("EXCESS", base64);
            }
            for(int i = 0; i < SilicaTypingInformationList.Count; i++)
            {
                TypingInformation tinfo = SilicaTypingInformationList[i];
                SilicaTypingInformation += tinfo.key + "=" + tinfo.value;
                if(i+1 != SilicaTypingInformationList.Count)
                {
                    SilicaTypingInformation += ",";
                }
            }

            SilicaTypingInformation += "]]}}SilicaTypingInformation";
            byte[] XMLBytes = new byte[sw.BaseStream.Length];

            sw.Flush();
            sw.BaseStream.Seek(0x00, SeekOrigin.Begin);
            sw.BaseStream.Read(XMLBytes, 0x00, XMLBytes.Length);
            sw.Close();

            string XMLData = Encoding.UTF8.GetString(XMLBytes);

            // Resolve Replace Patterns
            Console.WriteLine("Resolving Patterns ...");
            XMLData = XMLData.Replace(MagicReplacePattern, SilicaTypingInformation);
            
            foreach (LoopbackHandler lpHandler in FileList)
            {
                if (lpHandler.ReplacePattern != null && lpHandler.OldFileName != null)
                {

                    string replacePattern = lpHandler.ReplacePattern;
                    string oldName = lpHandler.OldFileName;
                    string extension = Path.GetExtension(oldName);
                    string folderPath = Path.GetDirectoryName(oldName);
                    byte[] fileData = lpHandler.FileData;

                    if (lpHandler.FileName != null)
                    {

                        string newPath = Path.ChangeExtension(Path.Combine(folderPath, lpHandler.FileName), extension);

                        if (!Directory.Exists(Path.GetDirectoryName(newPath)))
                            Directory.CreateDirectory(Path.GetDirectoryName(newPath));

                        File.WriteAllBytes(newPath, fileData);
                        string XMLRelNewPath = Path.Combine(Path.GetFileName(FileDir), "original", Path.GetFileName(newPath));
                        Console.WriteLine("Resolved " + replacePattern + " => " + XMLRelNewPath);
                        XMLData = XMLData.Replace(replacePattern, XMLRelNewPath);

                        if (ProcessFiles)
                            ProcessFile(newPath);
                    }
                    else
                    {
                        if (!Directory.Exists(Path.GetDirectoryName(oldName)))
                            Directory.CreateDirectory(Path.GetDirectoryName(oldName));

                        File.WriteAllBytes(oldName, fileData);

                        string xmlRelNewPath = Path.Combine(Path.GetFileName(FileDir), "original", Path.GetFileName(oldName));
                        Console.WriteLine("Resolved " + replacePattern + " => " + xmlRelNewPath);
                        XMLData = XMLData.Replace(replacePattern, xmlRelNewPath);
                        

                        if (ProcessFiles)
                            ProcessFile(oldName);
                    }
                }
                
            }

            File.WriteAllText(XMLPath, XMLData);
            Term();
        }

        public void ProcessFile(String FileName)
        {
            String Extension = Path.GetExtension(FileName);

            if (Extension == ".rcs") // Recursive Decompile, remove this if statement to remove RCS processing
            {
                if (NoRecursive)
                    return;

                Console.WriteLine("Decompiling " + Path.GetFileName(FileName));
                string DirectoryName = Path.Combine(FileDir, "converted", "RCStoXML");
                if (!Directory.Exists(DirectoryName))
                    Directory.CreateDirectory(DirectoryName);

                try
                {
                    string outputXML = Path.ChangeExtension(Path.GetFileNameWithoutExtension(FileName), ".xml");
                    CXMLReader cxmlParser = new CXMLReader();
                    cxmlParser.Init(FileName, Path.Combine(DirectoryName, outputXML));
                    cxmlParser.ProcessFiles = this.ProcessFiles;
                    cxmlParser.DecompileCXML(FileName, outputXML);
                }
                catch (Exception) { };
            }
            if (Extension == ".vag") //Remove this IF statment if you dont want VAG Conversion.
            {
                Console.WriteLine("Decoding: " + Path.GetFileName(FileName));
                byte[] WaveData = VAG.VAGAudio.Vag2Wav(FileName);
                String WaveName = Path.GetFileNameWithoutExtension(FileName) + "-" + VAG.VAGAudio.GetFilename(FileName) + ".wav";
                String DirectoryName = Path.Combine(FileDir , "converted", "VAGtoWAV");

                if (!Directory.Exists(DirectoryName))
                    Directory.CreateDirectory(DirectoryName);

                File.WriteAllBytes(Path.Combine(DirectoryName, WaveName), WaveData);
                Console.WriteLine("Decoded file written to: " + WaveName);
            }
            if (Extension == ".gim")
            { 
                String DirectoryName = Path.Combine(FileDir, "converted", "GIMtoPNG");

                if (!Directory.Exists(DirectoryName))
                    Directory.CreateDirectory(DirectoryName);

                String OutputGim = Path.Combine(DirectoryName, Path.GetFileName(Path.ChangeExtension(FileName, "png")));
                GimConv.ConvertGimToPng(FileName, OutputGim);  
            }
            if (Extension == ".z")
            {
                Console.WriteLine("Decompressing " + FileName);
                Byte[] FileData = File.ReadAllBytes(FileName);
                Byte[] DecompressedData = ZlibStream.UncompressBuffer(FileData);
                Extension = Tools.GetFileExtension(DecompressedData);
                String DirectoryName = Path.Combine(FileDir, "decompressed");

                if (!Directory.Exists(DirectoryName))
                    Directory.CreateDirectory(DirectoryName);

                String DecompressedFilename = Path.Combine(DirectoryName, Path.ChangeExtension(Path.GetFileNameWithoutExtension(FileName), Extension));
                Console.WriteLine("Decompressed file written to: " + DecompressedFilename);
                File.WriteAllBytes(DecompressedFilename, DecompressedData);

                ProcessFile(DecompressedFilename);
            }
        }
        
        public void RegisterFile(Int64 ElementPtr, String NewName, Boolean FileEntry, String ReplacePattern=null, byte[] FileData=null, String IdealName=null)
        {
            Console.WriteLine("Adding Entry for Loopback: 0x" + ElementPtr.ToString("X8", CultureInfo.InvariantCulture) + " (" + NewName + ")");
            for(int i = 0; i < FileList.Count; i++)
            {
                if(FileList[i].FilePointer == ElementPtr)
                {
                    if (FileEntry)
                    {
                        if (FileList[i].OldFileName != null)
                            break;
                        FileList[i].OldFileName = NewName;
                        FileList[i].FileName = IdealName;
                        return;
                    }
                    else
                    {
                        FileList[i].FileName = NewName;
                        return;
                    }
                }
            }


            LoopbackHandler lpHandler = new LoopbackHandler();
            if (FileEntry)
            {
                lpHandler.OldFileName = NewName;
                lpHandler.FileName = IdealName;
                lpHandler.FileData = FileData;
                lpHandler.ReplacePattern = ReplacePattern;
            }
            else
            {
                lpHandler.FileName = NewName;
            }
            lpHandler.FilePointer = ElementPtr;
            FileList.Add(lpHandler);
            return;
        }
        public AttributeType ReadAttributeType(Stream ms)
        {
            if(!ps3)
            {
                AttributeType type = (AttributeType)tools.ReadInt32(ms);
                return type;
            }
            else
            {
                AttributeTypePs3 type = (AttributeTypePs3)tools.ReadInt32(ms);
                return (AttributeType)Enum.Parse(typeof(AttributeType), type.ToString());
            }
        }

        public void ReadAttribute(String ElementName = "", Int64 ElementPtr = 0)
        {
            int AttributePtr = tools.ReadInt32(TreeTable);
            AttributeType Type = ReadAttributeType(TreeTable);

            String AttributeName = Tools.ReadStringAt(StringTable, AttributePtr);
            object AttributeValue = "";

            Console.WriteLine("AttributeType: " + Type.ToString() + " - "+ TreeTable.Position.ToString());
            switch (Type)
            {
                case AttributeType.TYPE_INT:
                    AttributeValue = tools.ReadInt32(TreeTable);

                    int sz = tools.ReadInt32(TreeTable);
                    Console.WriteLine("Int - Value: " + AttributeValue.ToString() + " sz:" + sz);
                    break;
                case AttributeType.TYPE_FLOAT:
                    Double FloatValue = (Double)tools.ReadSingle(TreeTable);
                    string FloatStr = FloatValue.ToString("G9", CultureInfo.InvariantCulture);
                    if (!FloatStr.Contains("."))
                        FloatStr += ".0";
                    AttributeValue = FloatStr;
                    sz = tools.ReadInt32(TreeTable);
                    Console.WriteLine("Float: " + AttributeValue.ToString() + " sz:" + sz);
                    break;
                case AttributeType.TYPE_STRING:
                    int StringOffset = tools.ReadInt32(TreeTable);
                    int StringLen = tools.ReadInt32(TreeTable);

                    byte[] StringBytes = new byte[StringLen];
                    StringTable.Seek(StringOffset, 0x00);
                    StringTable.Read(StringBytes, 0x00, StringLen);

                    AttributeValue = Encoding.UTF8.GetString(StringBytes);
                    Console.WriteLine("String: " + AttributeValue);
                    break;
                case AttributeType.TYPE_WSTRING:
                    int WStrOffset = tools.ReadInt32(TreeTable) * 2;
                    int WStrLen = tools.ReadInt32(TreeTable);

                    byte[] WStrBytes = new byte[WStrLen];
                    WStringTable.Seek(WStrOffset, 0x00);
                    WStringTable.Read(WStrBytes, 0x00, WStrLen);

                    AttributeValue = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(Encoding.Unicode.GetString(WStrBytes)));
                    Console.WriteLine("WString: " + AttributeValue);
                    break;
                case AttributeType.TYPE_HASH:
                    int HashTableOffset = tools.ReadInt32(TreeTable);
                    int HashTableSize = tools.ReadInt32(TreeTable);
                    HashTable.Seek(HashTableOffset * 4, SeekOrigin.Begin);

                    int Hash = tools.ReadInt32(HashTable);

                    AttributeValue = CXMLSymbols.LookupKey(Hash.ToString("X8", CultureInfo.InvariantCulture));

                    Console.WriteLine("Hash: " + AttributeValue.ToString() + " sz: " + HashTableSize);
                    break;
                case AttributeType.TYPE_INTEGER_ARRAY:
                    int IntArrayOffset = tools.ReadInt32(TreeTable);
                    int IntArraySize = tools.ReadInt32(TreeTable);
                    IntArrayTable.Seek(IntArrayOffset*4, SeekOrigin.Begin);

                    List<int> IntList = new List<int>();

                    for (int i = 0; i < IntArraySize; i++)
                    {
                        int IntValue = tools.ReadInt32(IntArrayTable);
                        IntList.Add(IntValue);
                    }
                    int[] IntArray = IntList.ToArray();
                    AttributeValue =  String.Join(",", IntArray);
                    Console.WriteLine("Int Array: " + AttributeValue.ToString());
                    break;
                case AttributeType.TYPE_FLOAT_ARRAY:
                    int FloatArrayOffset = tools.ReadInt32(TreeTable);
                    int FloatArraySize = tools.ReadInt32(TreeTable);
                    FloatArrayTable.Seek(FloatArrayOffset*4, SeekOrigin.Begin);

                    List<string> StrList = new List<string>();

                    for(int i = 0; i < FloatArraySize; i++)
                    {
                        FloatValue = (Double)tools.ReadSingle(FloatArrayTable);
                        FloatStr = FloatValue.ToString("G9", CultureInfo.InvariantCulture);
                        if (!FloatStr.Contains("."))
                            FloatStr += ".0";
                        StrList.Add(FloatStr);
                    }
                    string[] StrArray = StrList.ToArray();
                    AttributeValue = String.Join(", ", StrArray);
                    Console.WriteLine("Float Array: " + AttributeValue.ToString());
                    break;
                case AttributeType.TYPE_FILE:
                    int FilePtr = tools.ReadInt32(TreeTable);
                    int FileSz = tools.ReadInt32(TreeTable);
                    
                    Byte[] FileData = new Byte[FileSz];
                    FileTable.Seek(FilePtr, SeekOrigin.Begin);
                    FileTable.Read(FileData, 0, FileSz);

                    String FileHash = Tools.GenerateHash(FileData);
                    String FileSmallHash = Tools.GenerateShortHash(FileData);
                    String Extension = Tools.GetFileExtension(FileData);
                    String FileName = Path.Combine(FileDir, "original", FilePtr.ToString("X", CultureInfo.InvariantCulture) + "-" + FileHash + Extension);
                    String IdealName = ElementName + "-" + AttributeName + "-" + FileSmallHash + Extension;
                    String ReplacePattern = Tools.GenerateReplacePattern();

                    RegisterFile(ElementPtr, FileName, true, ReplacePattern, FileData, IdealName);

                    AttributeValue = ReplacePattern;
                    break;
                case AttributeType.TYPE_ID_REF:
                    int StringIdTableOffset = tools.ReadInt32(TreeTable);
                    IDTable.Seek(StringIdTableOffset, SeekOrigin.Begin);

                    int LoopbackPtr = tools.ReadInt32(IDTable);
                    int StringPtr = tools.ReadIntAt(TreeTable, LoopbackPtr);
                    string LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);

                    Console.WriteLine("Loopback: " + LoopbackAttribute +" "+ LoopbackPtr.ToString("X")+" ("+ElementPtr.ToString("X")+")");
                    
                    AttributeValue = Tools.ReadString(IDTable);
                    RegisterFile(LoopbackPtr, AttributeValue.ToString(), false);

                    sz = tools.ReadInt32(TreeTable);

                    Console.WriteLine("ID Ref: " + StringIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_ID:
                    StringIdTableOffset = tools.ReadInt32(TreeTable);
                    IDTable.Seek(StringIdTableOffset + 4, SeekOrigin.Begin);

                    AttributeValue = Tools.ReadString(IDTable);
                    sz = tools.ReadInt32(TreeTable);
                    Console.WriteLine("ID : " + StringIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_ID_HASH_REF:
                    int IntIdTableOffset = tools.ReadInt32(TreeTable);
                    HashIDTable.Seek(IntIdTableOffset, SeekOrigin.Begin);

                    LoopbackPtr = tools.ReadInt32(HashIDTable);
                    StringPtr = tools.ReadIntAt(TreeTable, LoopbackPtr);

                    int IDValue = tools.ReadInt32(HashIDTable);

                    LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);
                    Console.WriteLine("Int Loopback: " + LoopbackAttribute + " " + LoopbackPtr.ToString("X", CultureInfo.InvariantCulture) + " (" + ElementPtr.ToString("X") + ")");
                    
                    AttributeValue = CXMLSymbols.LookupKey(IDValue.ToString("X8", CultureInfo.InvariantCulture));
                    RegisterFile(LoopbackPtr, AttributeValue.ToString(), false);

                    sz = tools.ReadInt32(TreeTable);
                    Console.WriteLine("Hash Ref: " + IntIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_ID_HASH:
                    IntIdTableOffset = tools.ReadInt32(TreeTable);
                    HashIDTable.Seek(IntIdTableOffset + 4, SeekOrigin.Begin);
                    IDValue = tools.ReadInt32(HashIDTable);

                    AttributeValue = CXMLSymbols.LookupKey(IDValue.ToString("X8", CultureInfo.InvariantCulture));
                    sz = tools.ReadInt32(TreeTable);
                    Console.WriteLine("Hash Id: " + IntIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_NONE:
                default:
                    new NotImplementedException("UNKNOWN TYPE @ " + TreeTable.Position);
                    break;
            };

            AddTypingInfo(ElementName + ":" + AttributeName, Type.ToString());
            Console.WriteLine(AttributeName + "=" + AttributeValue.ToString());
            XMLWriter.WriteAttributeString(AttributeName, AttributeValue.ToString());
            XMLWriter.Flush();
        }

        public void ReadElements()
        {
            Int64 ElementLocation = TreeTable.Position;
            int ElementPtr = tools.ReadInt32(TreeTable);
            int NumAttributes = tools.ReadInt32(TreeTable);

            int ParentPtr = tools.ReadInt32(TreeTable);
            int PrevSibling = tools.ReadInt32(TreeTable);
            int NextSibling = tools.ReadInt32(TreeTable);

            int FirstChild = tools.ReadInt32(TreeTable);
            int LastChild = tools.ReadInt32(TreeTable);

            String ElementName = Tools.ReadStringAt(StringTable, ElementPtr);
            Console.WriteLine("Creating Element: " + ElementName);
            Console.WriteLine("Attribute Count: " + NumAttributes);

            Console.WriteLine("ParentPtr: " + ParentPtr);
            Console.WriteLine("PrevSibling: " + PrevSibling);
            Console.WriteLine("NextSibling: " + NextSibling);

            Console.WriteLine("FirstChild: " + FirstChild);
            Console.WriteLine("LastChild: " + LastChild);

            XMLWriter.WriteStartElement(ElementName);

            if(NumAttributes > 0)
            {
                for (int i = 0; i < NumAttributes; i++)
                {
                    ReadAttribute(ElementName, ElementLocation);
                }
            }

            if (FirstChild != -1)
            {
                TreeTable.Seek(FirstChild, SeekOrigin.Begin);
                ReadElements();
            }


            XMLWriter.WriteEndElement();
            XMLWriter.Flush();

            if (NextSibling != -1)
            {
                TreeTable.Seek(NextSibling, SeekOrigin.Begin);
                ReadElements();
            }

        }

    }
};
