using Cysharp.Threading.Tasks;
using System;

namespace UVC.Util
{
    /// <summary>
    /// Zipper 클래스는 압축 파일(Zip 및 7z)의 압축 해제 기능을 제공합니다.
    /// 7zip 라이브러리를 사용하며, 압축 해제 진행률을 실시간으로 추적할 수 있습니다.
    /// 7zip 사용. https://assetstore.unity.com/packages/tools/input-management/7zip-lzma-lz4-fastlz-zip-gzip-brotli-multiplatform-plugins-12674
    /// </summary>
    public class Zipper
    {

        // zip 보관 파일을 압축하지 않은 현재 파일 수로 변경하는 단일 항목 정수 배열입니다.
        // decompress_File 기능을 실행할 때 getTotalFiles 기능으로 반환된 총 파일 수와 이 int를 비교하여 zip에 여러 개의 파일이 포함된 경우 추출의 진행 상황을 파악한다.
        // 여러 개의 스레드를 사용하는 경우 다른 스레드에 다른 진행 정수를 사용해야 합니다.그렇지 않으면 공유 위반이 발생합니다.
        private int[] progress;

        /// <summary>
        /// 개별 파일의 압축 해제 진행률(바이트 단위)을 나타내는 배열입니다.
        /// </summary>
        private ulong[] progress2;
        /// <summary>
        /// 압축 파일의 경로를 저장합니다.
        /// </summary>
        private string zipFilePath;
        /// <summary>
        /// 압축 해제가 완료되었는지 여부를 나타냅니다.
        /// </summary>
        private bool isComplete = false;
        /// <summary>
        /// 압축 해제 진행률(0.0 ~ 1.0)을 나타냅니다.
        /// </summary>
        private float percent = 0;
        /// <summary>
        /// 압축 해제 진행 상황을 콜백으로 전달하는 액션입니다.
        /// </summary>
        private Action<long, long, float> OnProgress;


        /// <summary>
        /// 압축 파일을 해제합니다.
        /// 파일 형식(Zip 또는 7z)에 따라 적절한 메서드를 호출합니다.
        /// </summary>
        /// <param name="zipFilePath">압축 파일 경로</param>
        /// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
        /// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
        /// <returns>압축 해제 결과 메시지</returns>
        /// <example>
        /// Zipper zipper = new Zipper();
        /// string result = await zipper.Decompress("path/to/file.zip", "path/to/output", (current, total, percent) =>
        /// {
        ///     Debug.Log($"Progress: {percent * 100}%");
        /// });
        /// if (result == null)
        /// {
        ///     Debug.Log("Decompression successful!");
        /// }
        /// else
        /// {
        ///     Debug.LogError($"Decompression failed: {result}");
        /// }
        /// </example>
        public async UniTask<string> Decompress(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
        {
            if (zipFilePath.ToLower().Contains(".zip"))
            {
                return await DecompressZip(zipFilePath, decompressFolderPath, OnProgress);
            }
            else if (zipFilePath.ToLower().Contains(".7z"))
            {
                return await Decompress7Zip(zipFilePath, decompressFolderPath, OnProgress);
            }
            return "Unsupported compressed file";
        }

        /// <summary>
        /// Zip 파일을 압축 해제합니다.
        /// </summary>
        /// <param name="zipFilePath">압축 파일 경로</param>
        /// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
        /// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
        /// <returns>압축 해제 결과 메시지</returns>
        /// <example>
        /// Zipper zipper = new Zipper();
        /// string result = await zipper.DecompressZip("path/to/file.zip", "path/to/output", (current, total, percent) =>
        /// {
        ///     Debug.Log($"Progress: {percent * 100}%");
        /// });
        /// if (result == null)
        /// {
        ///     Debug.Log("Decompression successful!");
        /// }
        /// else
        /// {
        ///     Debug.LogError($"Decompression failed: {result}");
        /// }
        /// </example>
        public async UniTask<string> DecompressZip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
        {
            progress = new int[1];
            progress2 = new ulong[1];
            isComplete = false;
            this.zipFilePath = zipFilePath;
            this.OnProgress = OnProgress;

            if (lzip.validateFile(zipFilePath))
            {
                //getFileInfo zip 내의 콘텐츠의 총 압축되지 않은 바이트를 반환한다.
                ulong totalBytes = lzip.getFileInfo(zipFilePath);
                CountPercentZipAsync(totalBytes).Forget();
                int result = await UniTask.RunOnThreadPool(() =>
                {
                    return lzip.decompress_File(zipFilePath, decompressFolderPath, progress, null, progress2);
                });
                isComplete = true;
                if (result == 1) //success
                {
                    return null;
                }
                else
                {
                    //-1 = could not initialize zip archive.
                    //-2 = failed extraction
                    //- 6 = IntPtr buffer had no valid buffer length parameter passed.
                    string errorMessage = "Unknown error";
                    if (result == -1) errorMessage = "could not initialize zip archive.";
                    else if (result == -2) errorMessage = "failed extraction.";
                    else if (result == -6) errorMessage = "IntPtr buffer had no valid buffer length parameter passed.";
                    return errorMessage;
                }
            }

            return "invalidate File";
        }

        /// <summary>
        /// Zip 파일의 압축 해제 진행률을 계산합니다.
        /// </summary>
        /// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
        private async UniTaskVoid CountPercentZipAsync(ulong totalBytes)
        {

            percent = (float)progress2[0] / (float)totalBytes;

            //Debug.Log($"CountPercentAsync totalBytes:{totalBytes} progress:{progress[0]} progress2:{progress2[0]} {percent}");
            if (OnProgress != null) OnProgress((long)progress2[0], (long)totalBytes, percent);
            if (percent == 1) isComplete = true;
            if (!isComplete)
            {
                await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
                CountPercentZipAsync(totalBytes).Forget();
            }
        }

        /// <summary>
        /// 7z 파일을 압축 해제합니다.
        /// </summary>
        /// <param name="zipFilePath">압축 파일 경로</param>
        /// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
        /// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
        /// <returns>압축 해제 결과 메시지</returns>
        /// <example>
        /// Zipper zipper = new Zipper();
        /// string result = await zipper.Decompress7Zip("path/to/file.7z", "path/to/output", (current, total, percent) =>
        /// {
        ///     Debug.Log($"Progress: {percent * 100}%");
        /// });
        /// if (result == null)
        /// {
        ///     Debug.Log("Decompression successful!");
        /// }
        /// else
        /// {
        ///     Debug.LogError($"Decompression failed: {result}");
        /// }
        /// </example>
        public async UniTask<string> Decompress7Zip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
        {
            progress = new int[1] { 0 };
            isComplete = false;
            this.zipFilePath = zipFilePath;
            this.OnProgress = OnProgress;

            // 참조된 progress int는 압축 해제 중인 파일의 현재 인덱스를 나타냅니다. 별도의 스레드에서 사용하여 실시간으로 표시합니다.
            // (thread에서) 실시간 바이트 레벨 압축해제 진행을 얻기 위해서는 두 가지 방법이 있습니다.
            // 1. lzma.get7zSize 함수를 사용하여 파일의 총 압축되지 않은 크기를 구하고 lzma.getBytesWritten 함수를 호출하여 실시간으로 작성된 바이트들과 비교한다.
            // 2. lzma.getFileSize(또는 FileBuffers의 버퍼 길이)를 사용하여 파일 크기를 구하고 lzma.getBytesRead 기능을 호출하여 실시간으로 읽는 바이트들과 비교한다.
            //FileInfo fileInfo = new(zipFilePath);
            //FileSecurity fsecurity = fileInfo.GetAccessControl();
            //var id = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Trim();
            //var name = id.Split("\\");
            //fsecurity.AddAccessRule(new FileSystemAccessRule(new NTAccount(name[1]), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow));
            //fileInfo.SetAccessControl(fsecurity);
            long totalBytes = lzma.getFileSize(zipFilePath);
            CountPercent7ZipAsync(totalBytes, true).Forget();
            int result = await UniTask.RunOnThreadPool(() =>
            {
                return lzma.doDecompress7zip(zipFilePath, decompressFolderPath, progress, true, true);
            });
            isComplete = true;
            if (result == 1) //success
            {
                return null;
            }
            else
            {
                //1 : OK
                //2 : Could not find requested file in archive
                //- 1 : Could not open input(7z) file
                //- 2 : Decoder doesn't support this archive
                //- 3 : Can not allocate memory
                //-5 : Unknown error
                //-6 : File IO error
                string errorMessage = "Unknown error";
                if (result == 2) errorMessage = "Could not find requested file in archive";
                else if (result == -1) errorMessage = "Could not open input(7z) file";
                else if (result == -2) errorMessage = "Decoder doesn't support this archive";
                else if (result == -3) errorMessage = "Can not allocate memory";
                else if (result == -6) errorMessage = "File IO error";
                return errorMessage;
            }
        }

        /// <summary>
        /// 7z 파일의 압축 해제 진행률을 계산합니다.
        /// </summary>
        /// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
        /// <param name="isFirst">첫 번째 호출 여부</param>
        private async UniTaskVoid CountPercent7ZipAsync(long totalBytes, bool isFirst = false)
        {
            if (!isFirst)
            {
                long readByted = (long)lzma.getBytesRead();
                percent = (float)readByted / (float)totalBytes;
                //Debug.Log($"CountPercent7ZipAsync totalBytes:{totalBytes} progress:{progress}");
                if (OnProgress != null) OnProgress(readByted, totalBytes, percent);
                if (percent == 1) isComplete = true;
            }
            if (!isComplete)
            {
                await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
                CountPercent7ZipAsync(totalBytes).Forget();
            }
        }
    }
}
