// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename       : Symbian.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Symbian plugin.
//
// --[ Description ] ----------------------------------------------------------
//
//     Identifies Symbian installer (.sis) packages and shows information.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/

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

namespace Aaru.Archives;

public sealed partial class Symbian
{
#region IArchive Members

    /// <inheritdoc />
    public ErrorNumber Open(IFilter filter, Encoding encoding)
    {
        // Already opened!
        if(Opened) return ErrorNumber.InvalidArgument;

        var languages = new List<string>();
        _stream = filter.GetDataForkStream();

        if(_stream.Length < Marshal.SizeOf<SymbianHeader>()) return ErrorNumber.InvalidArgument;

        var buffer = new byte[Marshal.SizeOf<SymbianHeader>()];

        _stream.Seek(0, SeekOrigin.Begin);
        _stream.EnsureRead(buffer, 0, buffer.Length);
        SymbianHeader sh = Marshal.ByteArrayToStructureLittleEndian<SymbianHeader>(buffer);

        AaruLogging.Debug(MODULE_NAME, "sh.uid1 = {0}",         sh.uid1);
        AaruLogging.Debug(MODULE_NAME, "sh.uid2 = {0}",         sh.uid2);
        AaruLogging.Debug(MODULE_NAME, "sh.uid3 = {0}",         sh.uid3);
        AaruLogging.Debug(MODULE_NAME, "sh.uid4 = {0}",         sh.uid4);
        AaruLogging.Debug(MODULE_NAME, "sh.crc16 = {0}",        sh.crc16);
        AaruLogging.Debug(MODULE_NAME, "sh.languages = {0}",    sh.languages);
        AaruLogging.Debug(MODULE_NAME, "sh.files = {0}",        sh.files);
        AaruLogging.Debug(MODULE_NAME, "sh.requisites = {0}",   sh.requisites);
        AaruLogging.Debug(MODULE_NAME, "sh.inst_lang = {0}",    sh.inst_lang);
        AaruLogging.Debug(MODULE_NAME, "sh.inst_files = {0}",   sh.inst_files);
        AaruLogging.Debug(MODULE_NAME, "sh.inst_drive = {0}",   sh.inst_drive);
        AaruLogging.Debug(MODULE_NAME, "sh.capabilities = {0}", sh.capabilities);
        AaruLogging.Debug(MODULE_NAME, "sh.inst_version = {0}", sh.inst_version);
        AaruLogging.Debug(MODULE_NAME, "sh.options = {0}",      sh.options);
        AaruLogging.Debug(MODULE_NAME, "sh.type = {0}",         sh.type);
        AaruLogging.Debug(MODULE_NAME, "sh.major = {0}",        sh.major);
        AaruLogging.Debug(MODULE_NAME, "sh.minor = {0}",        sh.minor);
        AaruLogging.Debug(MODULE_NAME, "sh.variant = {0}",      sh.variant);
        AaruLogging.Debug(MODULE_NAME, "sh.lang_ptr = {0}",     sh.lang_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.files_ptr = {0}",    sh.files_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.reqs_ptr = {0}",     sh.reqs_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.certs_ptr = {0}",    sh.certs_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.comp_ptr = {0}",     sh.comp_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.sig_ptr = {0}",      sh.sig_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.caps_ptr = {0}",     sh.caps_ptr);
        AaruLogging.Debug(MODULE_NAME, "sh.instspace = {0}",    sh.instspace);
        AaruLogging.Debug(MODULE_NAME, "sh.maxinsspc = {0}",    sh.maxinsspc);
        AaruLogging.Debug(MODULE_NAME, "sh.reserved1 = {0}",    sh.reserved1);
        AaruLogging.Debug(MODULE_NAME, "sh.reserved2 = {0}",    sh.reserved2);

        _release6 = false;

        if(sh.uid1 == SYMBIAN9_MAGIC)
        {
            AaruLogging.Error("Symbian Installation Files from Symbian OS 9 or later are not yet supported.");

            return ErrorNumber.NotSupported;
        }

        if(sh.uid3 == SYMBIAN_MAGIC)
        {
            switch(sh.uid2)
            {
                case EPOC_MAGIC:
                    break;
                case EPOC6_MAGIC:
                    _release6 = true;

                    break;
            }
        }

        if(sh.options.HasFlag(SymbianOptions.IsUnicode))
            _encoding = Encoding.Unicode;
        else
            _encoding = encoding ?? Encoding.GetEncoding("windows-1252");

        var br = new BinaryReader(_stream);

        // Go to enumerate languages
        br.BaseStream.Seek(sh.lang_ptr, SeekOrigin.Begin);
        for(var i = 0; i < sh.languages; i++) languages.Add(((LanguageCodes)br.ReadUInt16()).ToString("G"));

        _files      = [];
        _conditions = [];
        _options    = [];

        uint currentFile    = 0;
        uint offset         = sh.files_ptr;
        var  conditionLevel = 0;

        // Get only the options records
        do
        {
            Parse(br, ref offset, ref currentFile, sh.files, languages, ref conditionLevel, true);
        } while(currentFile < sh.files);

        // Get all other records
        offset         = sh.files_ptr;
        currentFile    = 0;
        conditionLevel = 0;

        do
        {
            Parse(br, ref offset, ref currentFile, sh.files, languages, ref conditionLevel, false);
        } while(currentFile < sh.files);

        // Files appear on .sis in the reverse order they should be processed
        _files.Reverse();

        List<DecodedFileRecord> filesWithFixedFilenames = [];

        foreach(DecodedFileRecord f in _files)
        {
            DecodedFileRecord file = f;

            if(file.destinationName.Length > 3 && file.destinationName[1] == ':' && file.destinationName[2] == '\\')
                file.destinationName = file.destinationName[3..];

            file.destinationName = file.destinationName.Replace('\\', '/');

            if(file.language != null) file.destinationName = $"{file.language}/{file.destinationName}";

            filesWithFixedFilenames.Add(file);
        }

        _files = filesWithFixedFilenames;

        _features = ArchiveSupportedFeature.SupportsFilenames | ArchiveSupportedFeature.SupportsSubdirectories;

        if(_release6 && !sh.options.HasFlag(SymbianOptions.NoCompress))
        {
            _features   |= ArchiveSupportedFeature.SupportsCompression;
            _compressed =  true;
        }

        if(_files.Any(static t => t.mime is not null)) _features |= ArchiveSupportedFeature.SupportsXAttrs;

        Opened = true;

        return ErrorNumber.NoError;
    }

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

        _stream?.Close();

        _stream = null;
        Opened  = false;
    }

#endregion
}