/*
 * 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.
 */

using System;
using V2GDecoderNet.EXI;

namespace V2GDecoderNet.V2G
{
    /// <summary>
    /// V2G Transfer Protocol constants and definitions
    /// </summary>
    public static class V2GProtocol
    {
        // Network protocol patterns
        public const ushort ETH_TYPE_IPV6 = 0x86DD;
        public const byte IPV6_NEXT_HEADER_TCP = 0x06;
        public const ushort TCP_V2G_PORT = 15118;

        // V2G Transfer Protocol patterns
        public const byte V2G_PROTOCOL_VERSION = 0x01;
        public const byte V2G_INV_PROTOCOL_VERSION = 0xFE;
        public const ushort V2G_PAYLOAD_ISO_DIN_SAP = 0x8001;
        public const ushort V2G_PAYLOAD_ISO2 = 0x8002;
        public const ushort EXI_START_PATTERN = 0x8098;

        /// <summary>
        /// Get payload type name for display
        /// </summary>
        /// <param name="payloadType">Payload type value</param>
        /// <returns>Human-readable payload type name</returns>
        public static string GetPayloadTypeName(ushort payloadType)
        {
            return payloadType switch
            {
                V2G_PAYLOAD_ISO_DIN_SAP => "ISO 15118-2/DIN/SAP",
                V2G_PAYLOAD_ISO2 => "ISO 15118-20",
                _ => "Unknown"
            };
        }

        /// <summary>
        /// Extract EXI body from V2G Transfer Protocol data
        /// </summary>
        /// <param name="inputData">Input data containing V2GTP header and EXI body</param>
        /// <returns>Extracted EXI body data</returns>
        public static byte[] ExtractEXIBody(byte[] inputData)
        {
            if (inputData == null || inputData.Length < 8)
            {
                // Too small for V2GTP header, assume it's pure EXI
                return inputData ?? Array.Empty<byte>();
            }

            // First, look for V2G Transfer Protocol header anywhere in the data
            // Pattern: 0x01 0xFE 0x80 0x01 (V2GTP header for ISO/DIN/SAP)
            for (int i = 0; i <= inputData.Length - 8; i++)
            {
                if (inputData[i] == V2G_PROTOCOL_VERSION && inputData[i + 1] == V2G_INV_PROTOCOL_VERSION)
                {
                    ushort payloadType = (ushort)((inputData[i + 2] << 8) | inputData[i + 3]);

                    if (payloadType == V2G_PAYLOAD_ISO_DIN_SAP || payloadType == V2G_PAYLOAD_ISO2)
                    {
                        // Valid V2GTP header found: skip 8-byte header to get EXI body
                        int exiStart = i + 8;
                        var exiBody = new byte[inputData.Length - exiStart];
                        Array.Copy(inputData, exiStart, exiBody, 0, exiBody.Length);
                        return exiBody;
                    }
                }
            }

            // If no V2GTP header found, look for EXI start pattern anywhere in the data
            for (int i = 0; i <= inputData.Length - 2; i++)
            {
                ushort pattern = (ushort)((inputData[i] << 8) | inputData[i + 1]);
                if (pattern == EXI_START_PATTERN)
                {
                    // Found EXI start pattern
                    var exiBody = new byte[inputData.Length - i];
                    Array.Copy(inputData, i, exiBody, 0, exiBody.Length);
                    return exiBody;
                }
            }

            // No pattern found, assume it's pure EXI
            return inputData;
        }

        /// <summary>
        /// Analyze complete packet structure
        /// </summary>
        /// <param name="data">Packet data</param>
        /// <returns>Analysis result</returns>
        public static PacketAnalysis AnalyzeDataStructure(byte[] data)
        {
            var analysis = new PacketAnalysis
            {
                TotalSize = data?.Length ?? 0,
                HasEthernetHeader = false,
                HasIPv6Header = false,
                HasTCPHeader = false,
                HasV2GTPHeader = false,
                V2GTPPayloadType = 0,
                EXIBodyOffset = 0,
                EXIBodyLength = 0
            };

            if (data == null || data.Length == 0)
                return analysis;

            int offset = 0;

            // Check for Ethernet header (at least 14 bytes)
            if (data.Length >= 14)
            {
                ushort etherType = (ushort)((data[12] << 8) | data[13]);
                if (etherType == ETH_TYPE_IPV6)
                {
                    analysis.HasEthernetHeader = true;
                    offset = 14;
                }
            }

            // Check for IPv6 header (40 bytes)
            if (analysis.HasEthernetHeader && data.Length >= offset + 40)
            {
                byte version = (byte)((data[offset] >> 4) & 0x0F);
                if (version == 6)
                {
                    analysis.HasIPv6Header = true;
                    byte nextHeader = data[offset + 6];
                    if (nextHeader == IPV6_NEXT_HEADER_TCP)
                    {
                        offset += 40;
                    }
                }
            }

            // Check for TCP header (at least 20 bytes)
            if (analysis.HasIPv6Header && data.Length >= offset + 20)
            {
                ushort destPort = (ushort)((data[offset + 2] << 8) | data[offset + 3]);
                if (destPort == TCP_V2G_PORT)
                {
                    analysis.HasTCPHeader = true;
                    byte headerLength = (byte)((data[offset + 12] >> 4) * 4);
                    offset += headerLength;
                }
            }

            // Check for V2GTP header
            if (data.Length >= offset + 8)
            {
                if (data[offset] == V2G_PROTOCOL_VERSION && data[offset + 1] == V2G_INV_PROTOCOL_VERSION)
                {
                    analysis.HasV2GTPHeader = true;
                    analysis.V2GTPPayloadType = (ushort)((data[offset + 2] << 8) | data[offset + 3]);
                    offset += 8;
                }
            }

            // Remaining data is EXI body
            analysis.EXIBodyOffset = offset;
            analysis.EXIBodyLength = Math.Max(0, data.Length - offset);

            return analysis;
        }
    }

    /// <summary>
    /// Packet analysis result
    /// </summary>
    public class PacketAnalysis
    {
        public int TotalSize { get; set; }
        public bool HasEthernetHeader { get; set; }
        public bool HasIPv6Header { get; set; }
        public bool HasTCPHeader { get; set; }
        public bool HasV2GTPHeader { get; set; }
        public ushort V2GTPPayloadType { get; set; }
        public int EXIBodyOffset { get; set; }
        public int EXIBodyLength { get; set; }

        public string GetPayloadTypeName()
        {
            return V2GProtocol.GetPayloadTypeName(V2GTPPayloadType);
        }

        public override string ToString()
        {
            var parts = new List<string>();
            if (HasEthernetHeader) parts.Add("Ethernet");
            if (HasIPv6Header) parts.Add("IPv6");
            if (HasTCPHeader) parts.Add("TCP");
            if (HasV2GTPHeader) parts.Add($"V2GTP ({GetPayloadTypeName()})");
            
            var structure = parts.Count > 0 ? string.Join(" → ", parts) : "Raw data";
            return $"{structure} | EXI: {EXIBodyLength} bytes @ offset {EXIBodyOffset}";
        }
    }
}