/*
 * Copyright (C) 2007-2024 C# Port
 * Original Copyright (C) 2007-2018 Siemens AG
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 program. If not, see <http://www.gnu.org/licenses/>.
 */

namespace V2GDecoderNet.EXI
{
    /// <summary>
    /// Basic type definitions and constants for EXI codec
    /// </summary>
    public static class EXIConstants
    {
        /// <summary>Number of bits for each byte</summary>
        public const int BITS_IN_BYTE = 8;

        /// <summary>EXI Date-Time offset for year</summary>
        public const int DATETIME_YEAR_OFFSET = 2000;
        
        /// <summary>EXI Date-Time number of bits for monthDay</summary>
        public const int DATETIME_NUMBER_BITS_MONTHDAY = 9;
        
        /// <summary>EXI Date-Time number of bits for time</summary>
        public const int DATETIME_NUMBER_BITS_TIME = 17;
        
        /// <summary>EXI Date-Time number of bits for timezone</summary>
        public const int DATETIME_NUMBER_BITS_TIMEZONE = 11;
        
        /// <summary>EXI Date-Time month multiplicator</summary>
        public const int DATETIME_MONTH_MULTIPLICATOR = 32;
        
        /// <summary>EXI Date-Time offset for timezone minutes</summary>
        public const int DATETIME_TIMEZONE_OFFSET_IN_MINUTES = 896;

        /// <summary>Maximum integer value for uint</summary>
        public const int UINT_MAX_VALUE = 65535;

        /// <summary>EXI Float exponent special values</summary>
        public const int FLOAT_EXPONENT_SPECIAL_VALUES = -16384;
        
        /// <summary>EXI Float mantissa infinity</summary>
        public const long FLOAT_MANTISSA_INFINITY = 1;
        
        /// <summary>EXI Float minus mantissa infinity</summary>
        public const long FLOAT_MANTISSA_MINUS_INFINITY = -1;
        
        /// <summary>EXI Float not a number</summary>
        public const long FLOAT_MANTISSA_NOT_A_NUMBER = 0;

        /// <summary>Maximum number of cascading elements, XML tree depth</summary>
        public const int EXI_ELEMENT_STACK_SIZE = 24;

        /// <summary>Default buffer size</summary>
        public const int BUFFER_SIZE = 4096;
    }

    /// <summary>
    /// EXI Events enumeration
    /// </summary>
    public enum EXIEvent
    {
        /// <summary>Start Document SD</summary>
        START_DOCUMENT,
        /// <summary>End Document ED</summary>
        END_DOCUMENT,
        /// <summary>Start Element SE(qname)</summary>
        START_ELEMENT,
        /// <summary>Start Element SE(uri:*)</summary>
        START_ELEMENT_NS,
        /// <summary>Start Element SE(*) generic</summary>
        START_ELEMENT_GENERIC,
        /// <summary>Start Element SE(*) generic undeclared</summary>
        START_ELEMENT_GENERIC_UNDECLARED,
        /// <summary>End Element EE</summary>
        END_ELEMENT,
        /// <summary>End Element EE undeclared</summary>
        END_ELEMENT_UNDECLARED,
        /// <summary>Characters CH</summary>
        CHARACTERS,
        /// <summary>Characters CH generic</summary>
        CHARACTERS_GENERIC,
        /// <summary>Attribute AT(qname)</summary>
        ATTRIBUTE,
        /// <summary>Attribute AT(uri:*)</summary>
        ATTRIBUTE_NS,
        /// <summary>Attribute AT(*) generic</summary>
        ATTRIBUTE_GENERIC,
        /// <summary>Attribute AT(*) generic undeclared</summary>
        ATTRIBUTE_GENERIC_UNDECLARED,
        /// <summary>Attribute AT(xsi:type)</summary>
        ATTRIBUTE_XSI_TYPE,
        /// <summary>Attribute AT(xsi:nil)</summary>
        ATTRIBUTE_XSI_NIL,
        /// <summary>Self Contained SC</summary>
        SELF_CONTAINED,
        /// <summary>Entity Reference ER</summary>
        ENTITY_REFERENCE,
        /// <summary>Comment CM</summary>
        COMMENT,
        /// <summary>Processing Instruction PI</summary>
        PROCESSING_INSTRUCTION,
        /// <summary>Document Type Definition DTD</summary>
        DOCTYPE_DECLARATION,
        /// <summary>Namespace Declaration NS</summary>
        NAMESPACE_DECLARATION
    }

    /// <summary>
    /// EXI Integer types
    /// </summary>
    public enum EXIIntegerType
    {
        UNSIGNED_INTEGER_8,
        UNSIGNED_INTEGER_16,
        UNSIGNED_INTEGER_32,
        UNSIGNED_INTEGER_64,
        INTEGER_8,
        INTEGER_16,
        INTEGER_32,
        INTEGER_64,
        UNSIGNED_INTEGER_BIG
    }

    /// <summary>
    /// EXI String types
    /// </summary>
    public enum EXIStringType
    {
        ASCII,
        UTF8,
        UTF16
    }

    /// <summary>
    /// Configuration settings for EXI processing
    /// </summary>
    public class EXIConfig
    {
        /// <summary>Stream type configuration</summary>
        public enum StreamType
        {
            BYTE_ARRAY = 1,
            FILE_STREAM = 2
        }

        /// <summary>Memory allocation mode</summary>
        public enum MemoryAllocation
        {
            STATIC_ALLOCATION = 1,
            DYNAMIC_ALLOCATION = 2
        }

        /// <summary>String representation mode</summary>
        public enum StringRepresentation
        {
            ASCII = 1,
            UCS = 2
        }

        public StreamType Stream { get; set; } = StreamType.BYTE_ARRAY;
        public MemoryAllocation Memory { get; set; } = MemoryAllocation.DYNAMIC_ALLOCATION;
        public StringRepresentation Strings { get; set; } = StringRepresentation.UCS;
    }

    /// <summary>
    /// EXI Integer value holder
    /// </summary>
    public class EXIInteger
    {
        public EXIIntegerType Type { get; set; }
        public ulong Value { get; set; }

        public EXIInteger(EXIIntegerType type, ulong value)
        {
            Type = type;
            Value = value;
        }
    }

    /// <summary>
    /// EXI String value holder
    /// </summary>
    public class EXIString
    {
        public EXIStringType Type { get; set; }
        public byte[] Data { get; set; }
        public int Length { get; set; }

        public EXIString(byte[] data, EXIStringType type = EXIStringType.UTF8)
        {
            Data = data ?? throw new ArgumentNullException(nameof(data));
            Length = data.Length;
            Type = type;
        }

        public override string ToString()
        {
            return Type switch
            {
                EXIStringType.ASCII => System.Text.Encoding.ASCII.GetString(Data, 0, Length),
                EXIStringType.UTF8 => System.Text.Encoding.UTF8.GetString(Data, 0, Length),
                EXIStringType.UTF16 => System.Text.Encoding.Unicode.GetString(Data, 0, Length),
                _ => System.Text.Encoding.UTF8.GetString(Data, 0, Length)
            };
        }
    }

    /// <summary>
    /// Bitstream for EXI encoding/decoding operations
    /// </summary>
    public class Bitstream
    {
        public byte[] Buffer { get; set; }
        public int Position { get; set; }
        public int BitPosition { get; set; }
        public int Size { get; set; }

        public Bitstream(int size = EXIConstants.BUFFER_SIZE)
        {
            Buffer = new byte[size];
            Size = size;
            Position = 0;
            BitPosition = 0;
        }

        public Bitstream(byte[] data)
        {
            Buffer = data ?? throw new ArgumentNullException(nameof(data));
            Size = data.Length;
            Position = 0;
            BitPosition = 0;
        }

        public void Reset()
        {
            Position = 0;
            BitPosition = 0;
        }

        public byte[] ToArray()
        {
            var result = new byte[Position + (BitPosition > 0 ? 1 : 0)];
            Array.Copy(Buffer, result, result.Length);
            return result;
        }
    }
}