using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.Hashfile;
using SabreTools.Hashing;

#pragma warning disable CA1822 // Mark members as static
namespace SabreTools.Serialization.Readers
{
    public class Hashfile : BaseBinaryReader<Data.Models.Hashfile.Hashfile>
    {
        #region IByteReader

        /// <inheritdoc/>
        public override Data.Models.Hashfile.Hashfile? Deserialize(byte[]? data, int offset)
            => Deserialize(data, offset, HashType.CRC32);

        /// <inheritdoc cref="Deserialize(byte[], int)"/>
        public Data.Models.Hashfile.Hashfile? Deserialize(byte[]? data, int offset, HashType hash)
        {
            // If the data is invalid
            if (data is null || data.Length == 0)
                return null;

            // If the offset is out of bounds
            if (offset < 0 || offset >= data.Length)
                return null;

            // Create a memory stream and parse that
            var dataStream = new MemoryStream(data, offset, data.Length - offset);
            return Deserialize(dataStream, hash);
        }

        #endregion

        #region IFileReader

        /// <inheritdoc/>
        public override Data.Models.Hashfile.Hashfile? Deserialize(string? path)
            => Deserialize(path, HashType.CRC32);

        /// <inheritdoc cref="Deserialize(string?)"/>
        public Data.Models.Hashfile.Hashfile? Deserialize(string? path, HashType hash)
        {
            try
            {
                // If we don't have a file
                if (string.IsNullOrEmpty(path) || !File.Exists(path))
                    return default;

                // Open the file for deserialization
                using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                return Deserialize(stream, hash);
            }
            catch
            {
                // TODO: Handle logging the exception
                return default;
            }
        }

        #endregion

        #region IStreamReader

        public override Data.Models.Hashfile.Hashfile? Deserialize(Stream? data)
            => Deserialize(data, HashType.CRC32);

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? Deserialize(Stream? data, HashType hash)
        {
            if (hash == HashType.CRC32)
                return DeserializeSFV(data);
            else if (hash == HashType.MD2)
                return DeserializeMD2(data);
            else if (hash == HashType.MD4)
                return DeserializeMD4(data);
            else if (hash == HashType.MD5)
                return DeserializeMD5(data);
            else if (hash == HashType.RIPEMD128)
                return DeserializeRIPEMD128(data);
            else if (hash == HashType.RIPEMD160)
                return DeserializeRIPEMD160(data);
            else if (hash == HashType.SHA1)
                return DeserializeSHA1(data);
            else if (hash == HashType.SHA256)
                return DeserializeSHA256(data);
            else if (hash == HashType.SHA384)
                return DeserializeSHA384(data);
            else if (hash == HashType.SHA512)
                return DeserializeSHA512(data);
            else if (hash == HashType.SpamSum)
                return DeserializeSpamSum(data);
            else
                return null;
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSFV(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var sfvList = new List<SFV>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    var sfv = new SFV
                    {
                        File = string.Join(" ", lineParts, 0, lineParts.Length - 1),
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
                        Hash = lineParts[^1],
#else
                        Hash = lineParts[lineParts.Length - 1],
#endif
                    };
                    sfvList.Add(sfv);
                }

                // Assign the hashes to the hashfile and return
                if (sfvList.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SFV = [.. sfvList] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeMD2(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var md2List = new List<MD2>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var md2 = new MD2
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    md2List.Add(md2);
                }

                // Assign the hashes to the hashfile and return
                if (md2List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { MD2 = [.. md2List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeMD4(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var md4List = new List<MD4>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var md4 = new MD4
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    md4List.Add(md4);
                }

                // Assign the hashes to the hashfile and return
                if (md4List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { MD4 = [.. md4List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeMD5(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            // Setup the reader and output
            var reader = new StreamReader(data);
            var md5List = new List<MD5>();

            // Loop through the rows and parse out values
            while (!reader.EndOfStream)
            {
                // Read and split the line
                string? line = reader.ReadLine();
                string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                if (lineParts is null || lineParts.Length < 2)
                    continue;

                // Parse the line into a hash
                var md5 = new MD5
                {
                    Hash = lineParts[0],
                    File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                };
                md5List.Add(md5);
            }

            // Assign the hashes to the hashfile and return
            if (md5List.Count > 0)
                return new Data.Models.Hashfile.Hashfile { MD5 = [.. md5List] };

            return null;
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeRIPEMD128(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            // Setup the reader and output
            var reader = new StreamReader(data);
            var ripemd128List = new List<RIPEMD128>();

            // Loop through the rows and parse out values
            while (!reader.EndOfStream)
            {
                // Read and split the line
                string? line = reader.ReadLine();
                string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                if (lineParts is null || lineParts.Length < 2)
                    continue;

                // Parse the line into a hash
                var ripemd128 = new RIPEMD128
                {
                    Hash = lineParts[0],
                    File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                };
                ripemd128List.Add(ripemd128);
            }

            // Assign the hashes to the hashfile and return
            if (ripemd128List.Count > 0)
                return new Data.Models.Hashfile.Hashfile { RIPEMD128 = [.. ripemd128List] };

            return null;
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeRIPEMD160(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            // Setup the reader and output
            var reader = new StreamReader(data);
            var ripemd160List = new List<RIPEMD160>();

            // Loop through the rows and parse out values
            while (!reader.EndOfStream)
            {
                // Read and split the line
                string? line = reader.ReadLine();
                string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                if (lineParts is null || lineParts.Length < 2)
                    continue;

                // Parse the line into a hash
                var ripemd160 = new RIPEMD160
                {
                    Hash = lineParts[0],
                    File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                };
                ripemd160List.Add(ripemd160);
            }

            // Assign the hashes to the hashfile and return
            if (ripemd160List.Count > 0)
                return new Data.Models.Hashfile.Hashfile { RIPEMD160 = [.. ripemd160List] };

            return null;
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSHA1(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var sha1List = new List<SHA1>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var sha1 = new SHA1
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    sha1List.Add(sha1);
                }

                // Assign the hashes to the hashfile and return
                if (sha1List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SHA1 = [.. sha1List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSHA256(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var sha256List = new List<SHA256>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var sha256 = new SHA256
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    sha256List.Add(sha256);
                }

                // Assign the hashes to the hashfile and return
                if (sha256List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SHA256 = [.. sha256List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSHA384(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var sha384List = new List<SHA384>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var sha384 = new SHA384
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    sha384List.Add(sha384);
                }

                // Assign the hashes to the hashfile and return
                if (sha384List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SHA384 = [.. sha384List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSHA512(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return null;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var sha512List = new List<SHA512>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var sha512 = new SHA512
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    sha512List.Add(sha512);
                }

                // Assign the hashes to the hashfile and return
                if (sha512List.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SHA512 = [.. sha512List] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        /// <inheritdoc cref="Deserialize(Stream)"/>
        public Data.Models.Hashfile.Hashfile? DeserializeSpamSum(Stream? data)
        {
            // If the data is invalid
            if (data is null || !data.CanRead)
                return default;

            try
            {
                // Setup the reader and output
                var reader = new StreamReader(data);
                var spamsumList = new List<SpamSum>();

                // Loop through the rows and parse out values
                while (!reader.EndOfStream)
                {
                    // Read and split the line
                    string? line = reader.ReadLine();
                    string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
                    if (lineParts is null || lineParts.Length < 2)
                        continue;

                    // Parse the line into a hash
                    var spamSum = new SpamSum
                    {
                        Hash = lineParts[0],
                        File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
                    };
                    spamsumList.Add(spamSum);
                }

                // Assign the hashes to the hashfile and return
                if (spamsumList.Count > 0)
                    return new Data.Models.Hashfile.Hashfile { SpamSum = [.. spamsumList] };

                return null;
            }
            catch
            {
                // Ignore the actual error
                return null;
            }
        }

        #endregion
    }
}
