﻿using MHTools.CS;
using MHTools.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MHTools.FileSystem.Archive
{
    public static class ARCFile
    {
        /*
        C# Port of https://github.com/svanheulen/mhff/blob/master/n3ds/arc.py
        All Credits go to Svanheulen
        */

        public static List<string> ExtractArc(String FilePath, String Output = "Output")
        {
            byte[] FileData = File.ReadAllBytes(FilePath);
            UInt32 FileMagic = FileDataUtility.ReadUInt32(FileData, 0, false);
            if (FileMagic != 0x41524300) throw new Exception(String.Format("Invalid Magic: {0:X} != {1:X}", FileMagic, 0x41524300));
            UInt16 ArcVersion = FileDataUtility.ReadUInt16(FileData, 4, true); // 0x13 = MH4U   0x11 = MHX/MHGen
            UInt16 ArcFiles = FileDataUtility.ReadUInt16(FileData, 6, true);
            List<String> FileNames = new List<String>();

            //Debugging.LogInformation("ARCFile Header:\tMagic:{0:X}\tVersion:{1:X}\tFiles:{2}", FileMagic, ArcVersion, ArcFiles);
            Output += @"\";
            // Create the output directory
            DirectoryInfo OutputDirectory = new DirectoryInfo(Output);
            OutputDirectory.Create();

            // Read the File Headers
            byte[] toc = FileDataUtility.ReadChunk(FileData, 0x000C, (uint)(0x50 * ArcFiles), ReadMode.Length);
            for (int i = 0; i < ArcFiles; i++)
            {
                // Reading the Filename. ( 0x40 bytes | UTF-8 )
                List<byte> FileNameChunk = FileDataUtility.ReadChunk(toc, (uint)(0x0000 + (0x50 * i)), 0x40, ReadMode.Length).ToList<byte>();
                // Remove NUL Chars
                FileNameChunk.RemoveAll(b => b == 0);
                String FileName = Encoding.UTF8.GetString(FileNameChunk.ToArray());

                // Reading the other stuff
                UInt32 FileCode = FileDataUtility.ReadUInt32(toc, (uint)(0x0040 + (0x50 * i)), true);
                UInt32 CompressedSize = FileDataUtility.ReadUInt32(toc, (uint)(0x0044 + (0x50 * i)), true);
                UInt32 RealSize = FileDataUtility.ReadUInt32(toc, (uint)(0x0048 + (0x50 * i)), true);
                RealSize &= 0x1FFFFFFF;
                UInt32 Offset = FileDataUtility.ReadUInt32(toc, (uint)(0x004C + (0x50 * i)), true);
                
                // Create a Directory at that position. 
                String iFilePath = FileName + "." + MHFS.GetFileEnding(FileCode);
                FileNames.Add(iFilePath);
                FileInfo fi = new FileInfo(Output + iFilePath);
                fi.Directory.Create();

                //Debugging.LogInformation("Reading a new ARC File:");
                //Debugging.LogInformation("=> {0}", FileName);
                //Debugging.LogInformation("CD:{0:X}\tCS:{1:X}\tRS:{2:X}\tOF:{3:X}", FileCode, CompressedSize, RealSize, Offset);

                // decompressing the arc file. 
                byte[] CompressedData = FileDataUtility.ReadChunk(FileData, Offset, CompressedSize, ReadMode.Length);
                //Debugging.LogInformation("Decompressing chunk");
                byte[] DecompressedData = Compression.Decompress(CompressedData);
                if (DecompressedData.Length != RealSize) throw new Exception("Table Of Contents: Invalid decompressed size");

                // Write our file. Yay
                File.WriteAllBytes(Output + iFilePath, DecompressedData);
                Debugging.LogInformation("-> {0}", iFilePath);
                //Debugging.LogInformation();

            }

            return FileNames;

        }


        public static void CreateArc(String BasePath, String[] Files, String Output)
        {
            Debugging.LogInformation(Output);
            List<Byte> ArcBytes = new List<byte>();
            FileDataUtility.AddBytesToList(ArcBytes, "ARC", Encoding.UTF8, 4);
            FileDataUtility.AddBytesToList(ArcBytes, (UInt16)0x11);
            FileDataUtility.AddBytesToList(ArcBytes, (UInt16)Files.Length);
            FileDataUtility.AddBytesToList(ArcBytes, 0);
            List<Byte> DeflateData = new List<byte>();

            for (int i = 0; i<Files.Length; i++)
            {
                String FilePath = Files[i];
                FilePath = FilePath.Replace(@"/", @"\");
                String FileName = FilePath.Split('.')[0];
                Debugging.LogInformation(FileName);
                String FileEnding = FilePath.Split('.')[1];
                byte[] FileData = File.ReadAllBytes(BasePath + FilePath);
                UInt32 RealSize = (uint)FileData.Length | 0x40000000;
                FileData = Compression.Compress(FileData);
                UInt32 CompressedSize = (uint)FileData.Length;
                UInt32 Position = (uint) ((0x50 * Files.Length) + DeflateData.Count + 12);
                FileDataUtility.AddBytesToList(DeflateData, FileData);

                // Write all the stuff ( Adds up to 0x50 )

                FileDataUtility.AddBytesToList(ArcBytes, FileName, Encoding.UTF8, 0x40); // Add 0x40 Bytes of Path 
                FileDataUtility.AddBytesToList(ArcBytes, MHFS.GetCRC(FileEnding)); // Add CRC32
                FileDataUtility.AddBytesToList(ArcBytes, CompressedSize);
                FileDataUtility.AddBytesToList(ArcBytes, RealSize);
                FileDataUtility.AddBytesToList(ArcBytes, Position);

            }
            // Write ArcBytes to file Output

            FileDataUtility.AddBytesToList(ArcBytes, DeflateData.ToArray());

            FileInfo fi = new FileInfo(Output);
            fi.Directory.Create();
            File.WriteAllBytes(Output, ArcBytes.ToArray());

        }

    }
}
