using System.Collections.Generic;
using System.IO;
using System.Text;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
using Aaru.Logging;

namespace Aaru.Archives;

public sealed partial class Arc
{
    List<Entry> _entries;

#region IArchive Members

    /// <inheritdoc />
    public ErrorNumber Open(IFilter filter, Encoding encoding)
    {
        if(filter.DataForkLength < Marshal.SizeOf<Header>()) return ErrorNumber.InvalidArgument;

        _stream          = filter.GetDataForkStream();
        _stream.Position = 0;

        var hdr = new byte[Marshal.SizeOf<Header>()];

        _stream.ReadExactly(hdr, 0, hdr.Length);

        Header header = Marshal.ByteArrayToStructureLittleEndian<Header>(hdr);

        // Not a valid marker
        if(header.marker != MARKER) return ErrorNumber.InvalidArgument;

        switch((int)header.method)
        {
            // Not a valid compression method
            case > 12 and < 20:
            // Not a valid informational item
            case > 22 and < 30:
            // Not a valid control item
            case > 31:
                return ErrorNumber.InvalidArgument;
        }

        // Compressed size is larger than file size
        // Hope for the best
        if(header.compressed >= _stream.Length && (int)header.method != 31) return ErrorNumber.InvalidArgument;

        Opened           = true;
        _encoding        = encoding ?? Encoding.ASCII;
        _stream.Position = 0;
        _entries         = [];

        _features = ArchiveSupportedFeature.SupportsCompression |
                    ArchiveSupportedFeature.SupportsFilenames   |
                    ArchiveSupportedFeature.HasEntryTimestamp;

        var    path       = "";
        string longname   = null;
        string comment    = null;
        string attributes = null;
        var    br         = new BinaryReader(_stream);
        byte   peekedByte;

        // Process headers
        while(true)
        {
            peekedByte = br.ReadByte();
            AaruLogging.Debug(MODULE_NAME, "[navy]peekedByte[/] = [teal]0x{0:X2}[/]", peekedByte);
            peekedByte = br.ReadByte();
            AaruLogging.Debug(MODULE_NAME, "[navy]peekedByte[/] = [teal]0x{0:X2}[/]", peekedByte);

            if((Method)peekedByte == Method.EndOfArchive) break;

            if((Method)peekedByte == Method.SubdirectoryEnd)
            {
                // Remove last directory from path
                int lastSlash = path.LastIndexOf(Path.DirectorySeparatorChar);

                path = lastSlash >= 0 ? path[..lastSlash] : "";

                continue;
            }

            _stream.Position -= 2;

            // Decode header
            _stream.ReadExactly(hdr, 0, hdr.Length);
            header = Marshal.ByteArrayToStructureLittleEndian<Header>(hdr);

            AaruLogging.Debug(MODULE_NAME, "[navy]header.marker[/] = [teal]0x{0:X2}[/]", header.marker);
            AaruLogging.Debug(MODULE_NAME, "[navy]header.method[/] = [teal]{0}[/]",      header.method);

            AaruLogging.Debug(MODULE_NAME,
                              "[navy]header.filename[/] = [green]\"{0}\"[/]",
                              StringHandlers.CToString(header.filename));

            AaruLogging.Debug(MODULE_NAME, "[navy]header.compressed[/] = [teal]{0}[/]",   header.compressed);
            AaruLogging.Debug(MODULE_NAME, "[navy]header.date[/] = [teal]{0}[/]",         header.date);
            AaruLogging.Debug(MODULE_NAME, "[navy]header.time[/] = [teal]{0}[/]",         header.time);
            AaruLogging.Debug(MODULE_NAME, "[navy]header.crc[/] = [teal]0x{0:X4}[/]",     header.crc);
            AaruLogging.Debug(MODULE_NAME, "[navy]header.uncompressed[/] = [teal]{0}[/]", header.uncompressed);

            if(header.method == Method.FileInformation)
            {
                int recordsSize = header.compressed;
                var recordsRead = 0;

                while(recordsRead < recordsSize)
                {
                    ushort len       = br.ReadUInt16();
                    var    finfoType = (FileInformationType)br.ReadByte();
                    byte[] info      = br.ReadBytes(len - 3);

                    recordsRead += len;

                    switch(finfoType)
                    {
                        case FileInformationType.Description:
                            comment = StringHandlers.CToString(info, _encoding);

                            break;
                        case FileInformationType.Attributes:
                            attributes = StringHandlers.CToString(info, _encoding);

                            break;
                        case FileInformationType.LongName:
                            longname = StringHandlers.CToString(info, _encoding);

                            break;
                    }
                }
            }

            string filename;

            Entry entry;

            if(header.method == Method.Subdirectory)
            {
                filename = StringHandlers.CToString(header.filename, _encoding);
                if(longname is not null) filename = longname;

                path = Path.Combine(path, filename);

                entry = new Entry
                {
                    Method        = header.method,
                    Filename      = path,
                    Compressed    = 0,
                    Uncompressed  = 0,
                    LastWriteTime = DateHandlers.DosToDateTime(header.date, header.time),
                    DataOffset    = 0,
                    Comment       = comment,
                    Attributes    = FileAttributes.Directory
                };

                _features |= ArchiveSupportedFeature.HasExplicitDirectories |
                             ArchiveSupportedFeature.SupportsSubdirectories;

                if(attributes is not null)
                {
                    if(attributes.Contains('A')) entry.Attributes |= FileAttributes.Archive;
                    if(attributes.Contains('R')) entry.Attributes |= FileAttributes.ReadOnly;
                    if(attributes.Contains('H')) entry.Attributes |= FileAttributes.Hidden;
                    if(attributes.Contains('S')) entry.Attributes |= FileAttributes.System;
                }

                longname   = null;
                comment    = null;
                attributes = null;

                _entries.Add(entry);

                continue;
            }

            filename = StringHandlers.CToString(header.filename, _encoding);
            if(longname is not null) filename = longname;

            if(header.method == Method.UnpackedOld) _stream.Position -= 4;

            entry = new Entry
            {
                Method        = header.method,
                Filename      = Path.Combine(path, filename),
                Compressed    = header.compressed,
                Uncompressed  = header.method == Method.UnpackedOld ? header.compressed : header.uncompressed,
                LastWriteTime = DateHandlers.DosToDateTime(header.date, header.time),
                DataOffset    = _stream.Position,
                Comment       = comment,
                Attributes    = FileAttributes.Normal
            };

            if(attributes is not null)
            {
                if(attributes.Contains('A')) entry.Attributes |= FileAttributes.Archive;
                if(attributes.Contains('R')) entry.Attributes |= FileAttributes.ReadOnly;
                if(attributes.Contains('H')) entry.Attributes |= FileAttributes.Hidden;
                if(attributes.Contains('S')) entry.Attributes |= FileAttributes.System;
            }

            longname   = null;
            comment    = null;
            attributes = null;

            _entries.Add(entry);

            _stream.Position += header.compressed;
        }

        Opened = true;

        return ErrorNumber.NoError;
    }

    /// <inheritdoc />
    public void Close()
    {
        // Already closed
        if(!Opened) return;

        _stream?.Close();
        _entries?.Clear();

        _stream = null;
        Opened  = false;
    }

#endregion
}