/******************************************************
 *     ROMVault2 is written by Gordon J.              *
 *     Contact gordon@romvault.com                    *
 *     Copyright 2014                                 *
 ******************************************************/

using System;
using System.ComponentModel;
using System.IO;
using ROMVault2.Properties;
using ROMVault2.RvDB;
using ROMVault2.Utils;

namespace ROMVault2.DatReaders
{
    public static class DatCmpReader
    {
        private static bool _cleanFileNames = true;


        public static bool ReadDat(ref RvDir tDat, string strFilename)
        {
            RvDir tNow = tDat;
            FileType thisFileType = FileType.ZipFile;

            int errorCode = DatFileLoader.LoadDat(strFilename);
            if (errorCode != 0)
            {
                DatUpdate.ShowDat(new Win32Exception(errorCode).Message, strFilename);
                return false;
            }

            DatFileLoader.Gn();
            if (DatFileLoader.EndOfStream())
                return false;
            if (DatFileLoader.Next.ToLower() == "clrmamepro")
            {
                _cleanFileNames = true;
                DatFileLoader.Gn();
                if (!LoadHeaderFromDat(ref tNow, ref thisFileType))
                    return false;
                DatFileLoader.Gn();
            }
            if (DatFileLoader.Next.ToLower() == "romvault")
            {
                _cleanFileNames = false;
                DatFileLoader.Gn();
                if (!LoadHeaderFromDat(ref tNow, ref thisFileType))
                    return false;
                DatFileLoader.Gn();
            }

            if (tNow.Dat == null)
            {
                tNow.Dat = new RvDat();
                string cleanedName = IO.Path.GetFileNameWithoutExtension(strFilename);
                tNow.Dat.AddData(RvDat.DatData.DatName, cleanedName);
                tNow.Dat.AddData(RvDat.DatData.Description, cleanedName);
            }


            while (!DatFileLoader.EndOfStream())
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "dir":
                        DatFileLoader.Gn();
                        if (!LoadDirFromDat(ref tNow, ref thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "game":
                        DatFileLoader.Gn();
                        if (!LoadGameFromDat(ref tNow, false, thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "resource":
                        DatFileLoader.Gn();
                        if (!LoadGameFromDat(ref tNow, true, thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "emulator":
                        DatFileLoader.Gn();
                        if (!LoadEmulator())
                            return false;
                        DatFileLoader.Gn();
                        break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + Resources.DatCmpReader_ReadDat_not_known, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }

            DatFileLoader.Close();

            return true;
        }


        private static bool LoadHeaderFromDat(ref RvDir tDir, ref FileType thisFileType)
        {

            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadHeaderFromDat_not_found_after_clrmamepro, DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            string forceZipping = "";

            RvDat tDat = new RvDat();
            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "name": tDat.AddData(RvDat.DatData.DatName, VarFix.CleanFileName(DatFileLoader.GnRest())); DatFileLoader.Gn(); break;
                    case "description": tDat.AddData(RvDat.DatData.Description, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "category": tDat.AddData(RvDat.DatData.Category, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "version": tDat.AddData(RvDat.DatData.Version, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "date": tDat.AddData(RvDat.DatData.Date, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "author": tDat.AddData(RvDat.DatData.Author, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "email": tDat.AddData(RvDat.DatData.Email, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "homepage": tDat.AddData(RvDat.DatData.HomePage, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "url": tDat.AddData(RvDat.DatData.URL, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;

                    case "comment": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "header": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "forcezipping": forceZipping = DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "forcepacking": DatFileLoader.GnRest(); DatFileLoader.Gn(); break; // incorrect usage
                    case "forcemerging": tDat.AddData(RvDat.DatData.MergeType, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "forcenodump": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "dir": tDat.AddData(RvDat.DatData.DirSetup, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + Resources.DatCmpReader_LoadHeaderFromDat_not_known_in_clrmamepro, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }

            thisFileType = forceZipping.ToLower() != "no" ? FileType.ZipFile : FileType.File;
            if (Settings.FixLevel == eFixLevel.Uncompressed)
                thisFileType = FileType.File;
            tDir.Dat = tDat;
            return true;

        }

        private static bool LoadEmulator()
        {
            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat("( not found after emulator", DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();
            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "name": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "version": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                }
            }
            return true;
        }



        private static bool LoadDirFromDat(ref RvDir tDat, ref FileType thisFileType)
        {
            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_not_found_after_game, DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            if (DatFileLoader.Next.ToLower() != "name")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_Name_not_found_as_first_object_in, DatFileLoader.Filename);
                return false;
            }

            RvDir parent = tDat;

            string fullname = VarFix.CleanFullFileName(DatFileLoader.GnRest());
            while (fullname.Contains("/"))
            {
                int firstSlash = fullname.IndexOf("/", StringComparison.Ordinal);
                string dir = fullname.Substring(0, firstSlash);
                dir = VarFix.CleanFileName(dir);

                fullname = fullname.Substring(firstSlash + 1);
                int index;
                if (parent.ChildNameSearch(new RvDir(FileType.Dir) { Name = dir }, out index) == 0)
                {
                    parent = (RvDir)parent.Child(index);
                }
                else
                {
                    RvDir tpDir = new RvDir(FileType.Dir)
                                      {
                                          Name = dir,
                                          DatStatus = DatStatus.InDatCollect,
                                          Dat = tDat.Dat,
                                          Tree = new RvTreeRow()
                                      };
                    parent.ChildAdd(tpDir, index);
                    parent = tpDir;
                }
            }

            RvDir tDir = new RvDir(FileType.Dir)
                             {
                                 Name = fullname
                             };

            DatFileLoader.Gn();
            tDir.DatStatus = DatStatus.InDatCollect;
            tDir.Dat = tDat.Dat;

            int index1;
            if (parent.ChildNameSearch(tDir, out index1) == 0)
                tDir = (RvDir)parent.Child(index1);
            else
                parent.ChildAdd(tDir, index1);

            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "dir":
                        DatFileLoader.Gn();
                        if (!LoadDirFromDat(ref tDir, ref thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "game":
                        DatFileLoader.Gn();
                        if (!LoadGameFromDat(ref tDir, false, thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "resource":
                        DatFileLoader.Gn();
                        if (!LoadGameFromDat(ref tDir, true, thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadDirFromDat_Error_Keyword + DatFileLoader.Next + Resources.DatCmpReader_LoadDirFromDat_not_know_in_dir, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }
            return true;
        }

        private static bool LoadGameFromDat(ref RvDir tDat, bool bolresource, FileType thisFileType)
        {
            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_not_found_after_game, DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            string snext = DatFileLoader.Next.ToLower();

            string pathextra = "";
            if (snext == "rebuildto")
            {
                pathextra = VarFix.CleanFullFileName(DatFileLoader.Gn()); DatFileLoader.Gn();
                snext = DatFileLoader.Next.ToLower();
            }

            if (snext != "name")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_Name_not_found_as_first_object_in, DatFileLoader.Filename);
                return false;
            }

            RvDir parent = tDat;

            string fullname = VarFix.CleanFullFileName(DatFileLoader.GnRest());
            if (_cleanFileNames)
                fullname = fullname.Replace("/", "-");

            if (!String.IsNullOrEmpty(pathextra))
                fullname = pathextra + "/" + fullname;

            while (fullname.Contains("/"))
            {
                int firstSlash = fullname.IndexOf("/", StringComparison.Ordinal);
                string dir = fullname.Substring(0, firstSlash);
                fullname = fullname.Substring(firstSlash + 1);
                int index;
                if (parent.ChildNameSearch(new RvDir(FileType.Dir) { Name = dir }, out index) == 0)
                {
                    parent = (RvDir)parent.Child(index);
                }
                else
                {
                    RvDir tpDir = new RvDir(FileType.Dir)
                                      {
                                          Name = dir,
                                          DatStatus = DatStatus.InDatCollect,
                                          Dat = tDat.Dat,
                                          Tree = new RvTreeRow()
                                      };
                    parent.ChildAdd(tpDir, index);
                    parent = tpDir;
                }
            }

            RvDir tDir = new RvDir(thisFileType == FileType.File ? FileType.Dir : FileType.Zip)
                             {
                                 Name = fullname,
                                 DatStatus = DatStatus.InDatCollect,
                                 Dat = tDat.Dat
                             };

            int index1;
            string testName = tDir.Name;
            int nameCount = 0;
            while (parent.ChildNameSearch(tDir, out index1) == 0)
            {
                tDir.Name = testName + "_" + nameCount;
                nameCount++;
            }

            DatFileLoader.Gn();
            tDir.Game = new RvGame();
            tDir.Game.AddData(RvGame.GameData.IsBios, bolresource ? "Yes" : "No");

            RvDir tDirCHD = new RvDir(FileType.Dir)
            {
                Name = tDir.Name,
                DatStatus = tDir.DatStatus,
                Dat = tDir.Dat,
                Game = tDir.Game
            };

            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "romof": tDir.Game.AddData(RvGame.GameData.RomOf, VarFix.CleanFileName(DatFileLoader.GnRest())); DatFileLoader.Gn(); break;
                    case "description": tDir.Game.AddData(RvGame.GameData.Description, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;

                    case "sourcefile": tDir.Game.AddData(RvGame.GameData.Sourcefile, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "cloneof": tDir.Game.AddData(RvGame.GameData.CloneOf, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "sampleof": tDir.Game.AddData(RvGame.GameData.SampleOf, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "board": tDir.Game.AddData(RvGame.GameData.Board, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "year": tDir.Game.AddData(RvGame.GameData.Year, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "manufacturer": tDir.Game.AddData(RvGame.GameData.Manufacturer, DatFileLoader.GnRest()); DatFileLoader.Gn(); break;
                    case "serial": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "rebuildto": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;

                    case "sample": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "biosset": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;

                    case "chip": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "video": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "sound": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "input": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "dipswitch": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;
                    case "driver": DatFileLoader.GnRest(); DatFileLoader.Gn(); break;


                    case "rom":
                        DatFileLoader.Gn();
                        if (!LoadRomFromDat(ref tDir, thisFileType))
                            return false;
                        DatFileLoader.Gn();
                        break;
                    case "disk":
                        DatFileLoader.Gn();
                        if (!LoadDiskFromDat(ref tDirCHD))
                            return false;
                        DatFileLoader.Gn();
                        break;

                    case "archive":
                        DatFileLoader.Gn();
                        if (!LoadArchiveFromDat())
                            return false;
                        DatFileLoader.Gn();
                        break;

                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + Resources.DatCmpReader_LoadGameFromDat_not_known_in_game, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }

            if (tDir.ChildCount > 0)
                parent.ChildAdd(tDir, index1);
            if (tDirCHD.ChildCount > 0)
                parent.ChildAdd(tDirCHD);

            return true;
        }

        private static bool LoadRomFromDat(ref RvDir tGame, FileType thisFileType)
        {

            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadRomFromDat_not_found_after_rom, DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            if (DatFileLoader.Next.ToLower() != "name")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_Name_not_found_as_first_object_in, DatFileLoader.Filename);
                return false;
            }


            string filename = VarFix.CleanFullFileName(DatFileLoader.Gn());
            RvFile tRom = new RvFile(thisFileType) { Name = filename };

            DatFileLoader.Gn();
            tRom.Dat = tGame.Dat;

            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "size": tRom.Size = VarFix.ULong(DatFileLoader.Gn()); DatFileLoader.Gn(); break;
                    case "crc": tRom.CRC = VarFix.CleanMD5SHA1(DatFileLoader.Gn(), 8); DatFileLoader.Gn(); break;
                    case "sha1": tRom.SHA1 = VarFix.CleanMD5SHA1(DatFileLoader.Gn(), 40); DatFileLoader.Gn(); break;
                    case "md5": tRom.MD5 = VarFix.CleanMD5SHA1(DatFileLoader.Gn(), 32); DatFileLoader.Gn(); break;
                    case "merge": tRom.Merge = VarFix.CleanFullFileName(DatFileLoader.Gn()); DatFileLoader.Gn(); break;
                    case "flags": tRom.Status = VarFix.ToLower(DatFileLoader.Gn()); DatFileLoader.Gn(); break;
                    case "date": DatFileLoader.Gn(); DatFileLoader.Gn(); break;
                    case "bios": DatFileLoader.Gn(); DatFileLoader.Gn(); break;
                    case "region": DatFileLoader.Gn(); DatFileLoader.Gn(); break;
                    case "offs": DatFileLoader.Gn(); DatFileLoader.Gn(); break;
                    case "nodump": tRom.Status = "nodump"; DatFileLoader.Gn(); break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + Resources.DatCmpReader_LoadRomFromDat_not_known_in_rom, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }

            if (tRom.Size != null) tRom.FileStatusSet(FileStatus.SizeFromDAT);
            if (tRom.CRC != null) tRom.FileStatusSet(FileStatus.CRCFromDAT);
            if (tRom.SHA1 != null) tRom.FileStatusSet(FileStatus.SHA1FromDAT);
            if (tRom.MD5 != null) tRom.FileStatusSet(FileStatus.MD5FromDAT);

            tGame.ChildAdd(tRom);

            return true;
        }

        private static bool LoadDiskFromDat(ref RvDir tGame)
        {

            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadRomFromDat_not_found_after_rom, DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            if (DatFileLoader.Next.ToLower() != "name")
            {
                DatUpdate.SendAndShowDat(Resources.DatCmpReader_LoadGameFromDat_Name_not_found_as_first_object_in, DatFileLoader.Filename);
                return false;
            }


            string filename = VarFix.CleanFullFileName(DatFileLoader.Gn());
            RvFile tRom = new RvFile(FileType.File) { Name = filename };

            DatFileLoader.Gn();
            tRom.Dat = tGame.Dat;

            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "sha1": tRom.SHA1CHD = VarFix.CleanMD5SHA1(DatFileLoader.Gn(), 40); DatFileLoader.Gn(); break;
                    case "md5": tRom.MD5CHD = VarFix.CleanMD5SHA1(DatFileLoader.Gn(), 32); DatFileLoader.Gn(); break;
                    case "merge": tRom.Merge = VarFix.CleanFullFileName(DatFileLoader.Gn()); DatFileLoader.Gn(); break;
                    case "flags": tRom.Status = VarFix.ToLower(DatFileLoader.Gn()); DatFileLoader.Gn(); break;
                    case "nodump": tRom.Status = "nodump"; DatFileLoader.Gn(); break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + Resources.DatCmpReader_LoadRomFromDat_not_known_in_rom, DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }

            if (tRom.SHA1CHD != null) tRom.FileStatusSet(FileStatus.SHA1CHDFromDAT);
            if (tRom.MD5CHD != null) tRom.FileStatusSet(FileStatus.MD5CHDFromDAT);

            tGame.ChildAdd(tRom);

            return true;
        }

        private static bool LoadArchiveFromDat()
        {

            if (DatFileLoader.Next != "(")
            {
                DatUpdate.SendAndShowDat("( not found after Archive", DatFileLoader.Filename);
                return false;
            }
            DatFileLoader.Gn();

            while (DatFileLoader.Next != ")")
            {
                switch (DatFileLoader.Next.ToLower())
                {
                    case "name": DatFileLoader.Gn(); DatFileLoader.Gn(); break;
                    default:
                        DatUpdate.SendAndShowDat(Resources.DatCmpReader_ReadDat_Error_keyword + DatFileLoader.Next + " not know in Archive", DatFileLoader.Filename);
                        DatFileLoader.Gn();
                        break;
                }
            }
            return true;
        }



        private static class DatFileLoader
        {
            public static String Filename { get; private set; }
            private static Stream _fileStream;
            private static StreamReader _streamReader;
            private static string _line = "";
            public static string Next;

            public static int LoadDat(string strFilename)
            {
                Filename = strFilename;
                _streamReader = null;
                int errorCode = IO.FileStream.OpenFileRead(strFilename, out _fileStream);
                if (errorCode != 0)
                    return errorCode;
                _streamReader = new StreamReader(_fileStream, Program.Enc);
                return 0;
            }
            public static void Close()
            {
                _streamReader.Close();
                _fileStream.Close();
                _streamReader.Dispose();
                _fileStream.Dispose();
            }

            public static bool EndOfStream()
            {
                return _streamReader.EndOfStream;
            }

            public static string GnRest()
            {
                string strret = _line.Replace("\"", "");
                _line = "";
                Next = strret;
                return strret;
            }

            public static string Gn()
            {
                string ret;
                while ((_line.Trim().Length == 0) && (!_streamReader.EndOfStream))
                {
                    _line = _streamReader.ReadLine();
                    _line = (_line??"").Replace("" + (char)9, " ");
                    if (_line.TrimStart().Length > 2 && _line.TrimStart().Substring(0, 2) == @"//") _line = "";
                    if (_line.TrimStart().Length > 1 && _line.TrimStart().Substring(0, 1) == @"#") _line = "";
                    if (_line.TrimStart().Length > 1 && _line.TrimStart().Substring(0, 1) == @";") _line = "";
                    _line = _line.Trim() + " ";
                }

                if (_line.Trim().Length > 0)
                {
                    int intS;
                    if (_line.Substring(0, 1) == "\"")
                    {
                        intS = (_line + "\"").IndexOf("\"", 1, StringComparison.Ordinal);
                        ret = _line.Substring(1, intS - 1);
                        _line = (_line + " ").Substring(intS + 1).Trim();
                    }
                    else
                    {
                        intS = (_line + " ").IndexOf(" ", StringComparison.Ordinal);
                        ret = _line.Substring(0, intS);
                        _line = (_line + " ").Substring(intS).Trim();
                    }
                }
                else
                {
                    ret = "";
                }

                Next = ret;
                return ret;
            }
        }

    }
}
