using Ionic.Zlib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using General;
using System.IO;
using System.Text;
using System.Xml;


namespace CXML
{

    enum AttributeType
    {
        TYPE_NONE,
        TYPE_INT,
        TYPE_FLOAT,
        TYPE_STRING,
        TYPE_CHAR,
        TYPE_HASH_ID,
        TYPE_INTEGER_ARRAY,
        TYPE_FLOAT_ARRAY,
        TYPE_FILE,
        TYPE_ID_STRING_LOOPBACK,
        TYPE_ID_STRING,
        TYPE_ID_INT_LOOPBACK,
        TYPE_ID_INT
    };

    class CXMLParser
    {

        private static bool _checkMagicNumber()
        {

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

            if (Magic.StartsWith("PSMA"))
            {
                return true;
            }
            else if(Magic.StartsWith("RCOF"))
            {
                return true;
            }
            else if (Magic.StartsWith("RCSF"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private static string _lastFileName = "";

        static String MainDir = "";
        static String FileDir = "";
        static String XMLFilename = "";
        
        static FileStream InfoFile;

        static MemoryStream TreeTable;
        static MemoryStream StringIDTable;
        static MemoryStream IntIDTable;
        static MemoryStream StringTable;
        static MemoryStream CharTable;
        static MemoryStream HashIDTable;
        static MemoryStream IntArrayTable;
        static MemoryStream FloatArrayTable;
        static MemoryStream FileTable;
        

        static BinaryReader bTreeTable;
        static BinaryReader bIntIDTable;
        static BinaryReader bFloatArrayTable;
        static BinaryReader bIntArrayTable;
        static BinaryReader bHashIDTable;
        static BinaryReader bStringIDTable;

        static XmlWriter XMLFile;

        public static void Init(string path, bool CheckMagic = true)
        {
            InfoFile = File.Open(path, FileMode.Open, FileAccess.Read);
            if(CheckMagic)
            {
                if (!_checkMagicNumber())
                {
                    throw new Exception("Incorrect magic number.");
                }

            }

            
            TreeTable = Tools.ByteToStream(GetTreeTable());
            StringIDTable = Tools.ByteToStream(GetStringIDTable());
            IntIDTable = Tools.ByteToStream(GetIntIDTable());
            StringTable = Tools.ByteToStream(GetStringTable());
            CharTable = Tools.ByteToStream(GetCharTable());
            HashIDTable = Tools.ByteToStream(GetHashIDTable());
            IntArrayTable = Tools.ByteToStream(GetIntArrayTable());
            FloatArrayTable = Tools.ByteToStream(GetFloatArrayTable());
            FileTable = Tools.ByteToStream(GetFileTable());

            bTreeTable = new BinaryReader(TreeTable);
            bIntIDTable = new BinaryReader(IntIDTable);
            bFloatArrayTable = new BinaryReader(FloatArrayTable);
            bIntArrayTable = new BinaryReader(IntArrayTable);
            bHashIDTable = new BinaryReader(HashIDTable);
            bStringIDTable = new BinaryReader(StringIDTable);

            MainDir = Path.GetFileNameWithoutExtension(path);
            FileDir = Path.Combine(MainDir, "files");
            XMLFilename = Path.GetFileNameWithoutExtension(path) + ".xml";

            if (Directory.Exists(MainDir))
                Directory.Delete(MainDir, true);

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

            return;
        }

        public static void Term()
        {
            InfoFile.Close();
            TreeTable.Close();
            StringIDTable.Close();
            IntIDTable.Close();
            StringTable.Close();
            CharTable.Close();
            HashIDTable.Close();
            IntArrayTable.Close();
            FloatArrayTable.Close();
            FileTable.Close();

            bTreeTable.Close();
            bIntIDTable.Close();
            bFloatArrayTable.Close();
            bIntArrayTable.Close();
            bHashIDTable.Close();
        }

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

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

        public static int GetIDStringTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x10);
        }

        public static int GetIDStringTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x14);
        }

        public static int GetIDIntTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x18);
        }

        public static int GetIDIntTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x1C);
        }

        public static int GetStringTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x20);
        }

        public static int GetStringTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x24);
        }

        public static int GetCharTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x28);
        }

        public static int GetCharTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x2C);
        }

        public static int GetHashIDTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x30);
        }

        public static int GetHashIDTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x34);
        }

        public static int GetIntArrayTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x38);
        }

        public static int GetIntArrayTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x3C);
        }

        public static int GetFloatArrayTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x40);
        }

        public static int GetFloatArrayTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x44);
        }

        public static int GetFileTableOffset()
        {
            return Tools.ReadIntAt(InfoFile, 0x48);
        }

        public static int GetFileTableSize()
        {
            return Tools.ReadIntAt(InfoFile, 0x4C);
        }

        public static 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 static byte[] GetStringIDTable()
        {
            int IDStrTableOffset = GetIDStringTableOffset();
            int IDStrTableSize = GetIDStringTableSize();
            InfoFile.Seek(IDStrTableOffset, SeekOrigin.Begin);
            byte[] IDStringTable = new byte[IDStrTableSize];
            InfoFile.Read(IDStringTable, 0x00, IDStrTableSize);
            return IDStringTable;
        }

        public static byte[] GetIntIDTable()
        {
            int IDIntTableOffset = GetIDIntTableOffset();
            int IDIntTableSize = GetIDIntTableSize();
            InfoFile.Seek(IDIntTableOffset, SeekOrigin.Begin);
            byte[] IDIntTable = new byte[IDIntTableSize];
            InfoFile.Read(IDIntTable, 0x00, IDIntTableSize);
            return IDIntTable;
        }

        public static 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 static byte[] GetCharTable()
        {
            int CharTableOffset = GetCharTableOffset();
            int CharTableSize = GetCharTableSize();
            InfoFile.Seek(CharTableOffset, SeekOrigin.Begin);
            byte[] CharTable = new byte[CharTableSize];
            InfoFile.Read(CharTable, 0x00, CharTableSize);
            return CharTable;
        }

        public static 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 static 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 static 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 static 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 static void DecompileCXML(String CXMLFile, bool force = false)
        {

            Directory.CreateDirectory(MainDir);

            Term();
            Init(CXMLFile,force);

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

            XMLFile = XmlWriter.Create(Path.Combine(MainDir, XMLFilename), XMLSettings);
            XMLFile.WriteStartDocument();

            ReadElements();

            XMLFile.WriteEndDocument();
            XMLFile.Flush();

            Term();
        }

        public static void ProcessFile(String FileName)
        {
            String Extension = Path.GetExtension(FileName);
            
            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";
                    
                if (!Directory.Exists(FileDir+"/converted/VAGtoWAV"))
                    Directory.CreateDirectory(FileDir + "/converted/VAGtoWAV");

                File.WriteAllBytes(FileDir + "/converted/VAGtoWAV/" + WaveName, WaveData);
                Console.WriteLine("Decoded file written to: " + WaveName);
            }
            if (Extension == ".gim") //Remove this IF statement if you dont want GIM Conversion.
            {
                if (File.Exists("GimConv/GimConv.exe"))
                {
                    if (!Directory.Exists(FileDir + "/converted/GIMtoPNG"))
                        Directory.CreateDirectory(FileDir + "/converted/GIMtoPNG");

                    Console.WriteLine("Decoding GIM.");

                    Process Proc = new Process();
                    Proc.StartInfo.FileName = "GimConv/GimConv.exe";
                    Proc.StartInfo.Arguments = Path.GetFileName(FileName) + " -o " + "../converted/GIMtoPNG/" + Path.GetFileName(Path.ChangeExtension(FileName, "png"));
                    Proc.StartInfo.RedirectStandardOutput = true;
                    Proc.StartInfo.RedirectStandardError = true;
                    Proc.StartInfo.UseShellExecute = false;
                    Proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(FileName);
                    Proc.Start();
                    Proc.WaitForExit();
                    Console.WriteLine(Proc.StandardOutput.ReadToEnd());

                    Console.WriteLine("Done!");
                }

            }
            if (Extension == ".zlib")
            {
                Console.WriteLine("Decompressing " + FileName);
                Byte[] FileData = File.ReadAllBytes(FileName);
                Byte[] DecompressedData = ZlibStream.UncompressBuffer(FileData);
                Extension = Tools.GetFileExtension(DecompressedData);

                if (!Directory.Exists(FileDir + "/decompressed"))
                    Directory.CreateDirectory(FileDir + "/decompressed");

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

                ProcessFile(DecompressedFilename);
            }
        }
        

        public static void ReadAttribute(String ElementName = "")
        {
            int AttributePtr = bTreeTable.ReadInt32();
            AttributeType Type = (AttributeType)bTreeTable.ReadInt32();

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

            Console.WriteLine("AttributeType: " + Type.ToString() + " - "+ TreeTable.Position.ToString());
            switch (Type)
            {
                case AttributeType.TYPE_NONE:
                    Console.WriteLine("UNSUPPORTED TYPE @ " + TreeTable.Position);
                    Console.ReadKey();
                    break;
                case AttributeType.TYPE_INT:
                    AttributeValue = bTreeTable.ReadInt32();
                    TreeTable.Seek(4, SeekOrigin.Current);
                    break;
                case AttributeType.TYPE_FLOAT:
                    float FloatValue = bTreeTable.ReadSingle();
                    AttributeValue = FloatValue.ToString()+"f";
                    TreeTable.Seek(4, SeekOrigin.Current);
                    break;
                case AttributeType.TYPE_STRING:
                    int StringOffset = bTreeTable.ReadInt32();
                    int StringLen = bTreeTable.ReadInt32();

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

                    AttributeValue = Encoding.UTF8.GetString(StringBytes);
                    break;
                case AttributeType.TYPE_CHAR:
                    int CharOffset = bTreeTable.ReadInt32() * 2;
                    int CharLen = bTreeTable.ReadInt32();

                    byte[] CharBytes = new byte[CharLen];
                    CharTable.Seek(CharOffset, 0x00);
                    CharTable.Read(CharBytes, 0x00, CharLen);

                    AttributeValue = Encoding.Unicode.GetString(CharBytes);
                    break;
                case AttributeType.TYPE_HASH_ID:
                    int HashTableOffset = bTreeTable.ReadInt32();
                    int HashTableSize = bTreeTable.ReadInt32();
                    HashIDTable.Seek(HashTableOffset*4, SeekOrigin.Begin);

                    int HashID = bHashIDTable.ReadInt32();
                    AttributeValue = HashID.ToString("X8");
                    break;
                case AttributeType.TYPE_INTEGER_ARRAY:
                    int IntArrayOffset = bTreeTable.ReadInt32();
                    int IntArraySize = bTreeTable.ReadInt32();
                    IntArrayTable.Seek(IntArrayOffset*4, SeekOrigin.Begin);

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

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

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

                    for(int i = 0; i < FloatArraySize; i++)
                    {
                        FloatValue = bFloatArrayTable.ReadSingle();
                        StrList.Add(FloatValue.ToString()+"f");
                    }
                    string[] StrArray = StrList.ToArray();
                    AttributeValue = "[" + String.Join(", ", StrArray) + "]";
                    break;
                case AttributeType.TYPE_FILE:
                    int FilePtr = bTreeTable.ReadInt32();
                    int FileSz = bTreeTable.ReadInt32();
                    
                    Byte[] FileData = new Byte[FileSz];
                    FileTable.Seek(FilePtr, SeekOrigin.Begin);
                    FileTable.Read(FileData, 0, FileSz);
                    string FileHash = Tools.GenerateHash(FileData);

                    String Extension = Tools.GetFileExtension(FileData);
                    String FileName = Path.Combine(FileDir, ElementName, FileHash + Extension);
                    if(!File.Exists(FileName))
                    {
                        Console.WriteLine("Writing: " + FileName);

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

                        File.WriteAllBytes(FileName, FileData);
                        ProcessFile(FileName);
                    }
                    else
                    {
                        Console.WriteLine("File allready extracted \n(theres a VERY low chance that it acturally is a different file that has the same hash.)");
                    }
                    
                    AttributeValue = Path.Combine("files", ElementName, FileHash + Extension); 
                    break;
                case AttributeType.TYPE_ID_STRING_LOOPBACK:
                    int StringIdTableOffset = bTreeTable.ReadInt32();
                    StringIDTable.Seek(StringIdTableOffset, SeekOrigin.Begin);

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

                    Console.WriteLine("Loopback: " + LoopbackAttribute);

                    AttributeValue = Tools.ReadString(StringIDTable);

                    TreeTable.Seek(4, SeekOrigin.Current);
                    break;
                case AttributeType.TYPE_ID_STRING:
                    Console.WriteLine("UNSUPPORTED TYPE @ " + TreeTable.Position);
                    Console.ReadKey();
                    break;
                case AttributeType.TYPE_ID_INT_LOOPBACK:
                    int IntIdTableOffset = bTreeTable.ReadInt32();
                    IntIDTable.Seek(IntIdTableOffset, SeekOrigin.Begin);

                    LoopbackPtr = bIntIDTable.ReadInt32();
                    StringPtr = Tools.ReadIntAt(TreeTable, LoopbackPtr);

                    int IDValue = bIntIDTable.ReadInt32();

                    
                    LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);
                    Console.WriteLine("Loopback: " + LoopbackAttribute);

                    AttributeValue = IDValue.ToString("X8");

                    TreeTable.Seek(4, SeekOrigin.Current);
                    break;
                case AttributeType.TYPE_ID_INT:
                    IntIdTableOffset = bTreeTable.ReadInt32();
                    IntIDTable.Seek(IntIdTableOffset + 4, SeekOrigin.Begin);
                    IDValue = bIntIDTable.ReadInt32();

                    AttributeValue = IDValue.ToString("X8");
                    TreeTable.Seek(4, SeekOrigin.Current);
                    break;
                default:
                    Console.WriteLine("UNKNOWN TYPE @ " + TreeTable.Position);
                    break;
            };
            

            Console.WriteLine(AttributeName + "=" + AttributeValue.ToString());
            XMLFile.WriteAttributeString(AttributeName, AttributeValue.ToString());
            XMLFile.Flush();
        }

        public static void ReadElements()
        {

            int ElementPtr = bTreeTable.ReadInt32();
            int NumAttributes = bTreeTable.ReadInt32();

            int ParentPtr = bTreeTable.ReadInt32();
            int PrevSibling = bTreeTable.ReadInt32();
            int NextSibling = bTreeTable.ReadInt32();

            int FirstChild = bTreeTable.ReadInt32();
            int LastChild = bTreeTable.ReadInt32();

            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);

            XMLFile.WriteStartElement(ElementName);

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

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


            XMLFile.WriteEndElement();
            XMLFile.Flush();

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

        }

    }
}
