// /***************************************************************************
// 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;
using System.IO;
using System.Linq;
using System.Text;

namespace Aaru.Archives;

public sealed partial class Symbian
{
    ConditionalExpression ParseConditionalExpression(BinaryReader   br, uint maxOffset, StringBuilder sb,
                                                     ref Attribute? attribute)
    {
        if(br.BaseStream.Position >= maxOffset) return null;

        var type           = (ConditionalType)br.ReadUInt32();
        var operatorString = "";

        SubConditionalExpression     subExpression;
        TwoSubsConditionalExpression twoSubsConditionalExpression;

        switch(type)
        {
            case ConditionalType.Equals:
                if(type == ConditionalType.Equals) operatorString = " == ";

                twoSubsConditionalExpression = new TwoSubsConditionalExpression
                {
                    type = type
                };

                sb.Append("(");
                twoSubsConditionalExpression.leftOperand = ParseConditionalExpression(br, maxOffset, sb, ref attribute);
                sb.Append(operatorString);

                twoSubsConditionalExpression.rightOperand =
                    ParseConditionalExpression(br, maxOffset, sb, ref attribute);

                sb.Append(")");

                attribute = null;

                return twoSubsConditionalExpression;
            case ConditionalType.Differs:
                operatorString = " != ";
                goto case ConditionalType.Equals;
            case ConditionalType.GreaterThan:
                operatorString = " > ";
                goto case ConditionalType.Equals;
            case ConditionalType.LessThan:
                operatorString = " < ";
                goto case ConditionalType.Equals;
            case ConditionalType.GreaterOrEqualThan:
                operatorString = " >= ";
                goto case ConditionalType.Equals;
            case ConditionalType.LessOrEqualThan:
                operatorString = " <= ";
                goto case ConditionalType.Equals;
            case ConditionalType.And:
                operatorString = " && ";
                goto case ConditionalType.Equals;
            case ConditionalType.Or:
                operatorString = " || ";
                goto case ConditionalType.Equals;
            case ConditionalType.Exists:
                sb.Append("exists(");

                subExpression = new SubConditionalExpression
                {
                    type          = type,
                    subExpression = ParseConditionalExpression(br, maxOffset, sb, ref attribute)
                };

                sb.Append(")");

                attribute = null;

                return subExpression;
            case ConditionalType.DeviceCapability:
                sb.Append("devcap(");

                subExpression = new SubConditionalExpression
                {
                    type          = type,
                    subExpression = ParseConditionalExpression(br, maxOffset, sb, ref attribute)
                };

                sb.Append(')');

                attribute = null;

                return subExpression;
            case ConditionalType.ApplicationCapability:
                twoSubsConditionalExpression = new TwoSubsConditionalExpression
                {
                    type = type
                };

                sb.Append("appcap(");
                twoSubsConditionalExpression.leftOperand = ParseConditionalExpression(br, maxOffset, sb, ref attribute);
                sb.Append(", ");

                twoSubsConditionalExpression.rightOperand =
                    ParseConditionalExpression(br, maxOffset, sb, ref attribute);

                sb.Append(')');

                attribute = null;

                return twoSubsConditionalExpression;
            case ConditionalType.Not:
                sb.Append('!');

                subExpression = new SubConditionalExpression
                {
                    type          = type,
                    subExpression = ParseConditionalExpression(br, maxOffset, sb, ref attribute)
                };

                attribute = null;

                return subExpression;
            case ConditionalType.String:
                var stringExpression = new StringConditionalExpression
                {
                    type    = type,
                    length  = br.ReadUInt32(),
                    pointer = br.ReadUInt32()
                };

                long position = br.BaseStream.Position;

                br.BaseStream.Seek(stringExpression.pointer, SeekOrigin.Begin);
                byte[] buffer = br.ReadBytes((int)stringExpression.length);
                stringExpression.@string = _encoding.GetString(buffer);

                br.BaseStream.Seek(position, SeekOrigin.Begin);

                sb.Append($"\"{stringExpression.@string}\"");

                attribute = null;

                break;
            case ConditionalType.Attribute:
                var attributeExpression = new AttributeConditionalExpression
                {
                    type      = type,
                    attribute = (Attribute)br.ReadUInt32(),
                    unused    = br.ReadUInt32()
                };

                if((int)attributeExpression.attribute > 0x2000)
                {
                    int optionIndex = (int)attributeExpression.attribute - 0x2000;

                    if(optionIndex <= _options.Count)
                    {
                        OptionRecord option = _options[optionIndex - 1];
                        option.names.TryGetValue("EN", out string optionName);
                        optionName ??= option.names.Values.FirstOrDefault() ?? optionIndex.ToString();
                        sb.Append($"option(\"{optionName}\", ENABLED)");
                    }
                    else
                        sb.Append($"option({optionIndex}, ENABLED");
                }
                else
                    sb.Append($"{attributeExpression.attribute}");

                attribute = attributeExpression.attribute;

                return attributeExpression;
            case ConditionalType.Number:
                var numberExpression = new NumberConditionalExpression
                {
                    type   = type,
                    number = br.ReadUInt32(),
                    unused = br.ReadUInt32()
                };

                if(attribute is null)
                    sb.Append($"0x{numberExpression.number:X8}");
                else if((uint)attribute.Value < 0x2000)
                {
                    switch(attribute)
                    {
                        case Attribute.Manufacturer:
                            sb.Append($"{(ManufacturerCode)numberExpression.number}");

                            break;
                        case Attribute.ManufacturerHardwareRev:
                        case Attribute.ManufacturerSoftwareRev:
                        case Attribute.DeviceFamilyRev:
                            sb.Append($"{numberExpression.number >> 8}.{numberExpression.number & 0xFF}");

                            break;
                        case Attribute.MachineUid:
                            sb.Append($"{DecodeMachineUid(numberExpression.number)}");

                            break;
                        case Attribute.DeviceFamily:
                            sb.Append($"{(DeviceFamilyCode)numberExpression.number}");

                            break;
                        case Attribute.CPU:
                            sb.Append($"{(CpuCode)numberExpression.number}");

                            break;
                        case Attribute.CPUArch:
                            sb.Append($"{(CpuArchitecture)numberExpression.number}");

                            break;
                        case Attribute.CPUABI:
                            sb.Append($"{(CpuAbiCode)numberExpression.number}");

                            break;
                        case Attribute.CPUSpeed:
                            sb.Append($"{numberExpression.number / 1024}MHz");

                            break;
                        case Attribute.SystemTickPeriod:
                            sb.Append($"{numberExpression.number}μs");

                            break;
                        case Attribute.MemoryRAM:
                        case Attribute.MemoryRAMFree:
                        case Attribute.MemoryROM:
                        case Attribute.MemoryPageSize:
                        case Attribute.Keyboard:
                        case Attribute.KeyboardDeviceKeys:
                        case Attribute.KeyboardAppKeys:
                        case Attribute.KeyboardClickVolumeMax:
                        case Attribute.DisplayXPixels:
                        case Attribute.DisplayYPixels:
                        case Attribute.DisplayXTwips:
                        case Attribute.DisplayYTwips:
                        case Attribute.DisplayColors:
                        case Attribute.DisplayContrastMax:
                        case Attribute.PenX:
                        case Attribute.PenY:
                        case Attribute.PenClickVolumeMax:
                        case Attribute.MouseX:
                        case Attribute.MouseY:
                        case Attribute.MouseButtons:
                        case Attribute.LEDs:
                        case Attribute.DisplayBrightnessMax:
                        case Attribute.KeyboardBacklightState:
                        case Attribute.AccessoryPower:
                        case Attribute.NumHalAttributes:
                        case Attribute.Language:
                            sb.Append($"{numberExpression.number}");

                            break;
                        case Attribute.PowerBackup:
                        case Attribute.KeyboardClick:
                        case Attribute.Backlight:
                        case Attribute.Pen:
                        case Attribute.PenDisplayOn:
                        case Attribute.PenClick:
                        case Attribute.Mouse:
                        case Attribute.CaseSwitch:
                        case Attribute.IntegratedPhone:
                        case Attribute.RemoteInstall:
                            sb.Append(numberExpression.number == 0 ? "false" : "true");

                            break;
                        default:
                            sb.Append($"0x{numberExpression.number:X8}");

                            break;
                    }
                }
                else
                {
                    int optionIndex = (int)attribute - 0x2000;

                    if(optionIndex <= _options.Count)
                    {
                        OptionRecord option = _options[optionIndex - 1];
                        option.names.TryGetValue("EN", out string optionName);
                        optionName ??= option.names.Values.FirstOrDefault() ?? optionIndex.ToString();
                        sb.Append($"option(\"{optionName}\", {(numberExpression.number == 0 ? "DISABLED" : "ENABLED")})");
                    }
                    else
                        sb.Append($"option({optionIndex}, {(numberExpression.number == 0 ? "DISABLED" : "ENABLED")}");
                }

                attribute = null;

                return numberExpression;
            default:
                throw new ArgumentOutOfRangeException();
        }

        return null;
    }
}