using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace V2GProtocol
{
    class Program
    {
        static void Main(string[] args)
        {
            // 헤더는 도움말 또는 분석 모드에서만 표시 (인코드/디코드 작업에서는 표시하지 않음)
            bool showHeader = false;
            
            if (args.Length == 0)
            {
                showHeader = true;
            }
            else if (args.Length > 0)
            {
                string firstArg = args[0].ToLower();
                
                // 도움말 명령인지 확인
                if (firstArg == "--help" || firstArg == "-h")
                {
                    showHeader = true;
                }
                // 분석 모드용 파일인지 확인 (옵션 없이 .dump/.txt만)
                else if (File.Exists(args[0]) && args.Length == 1)
                {
                    string extension = Path.GetExtension(args[0]).ToLower();
                    
                    // 분석 모드에서만 헤더 표시 (.dump/.txt)
                    // 자동 변환에서는 표시하지 않음 (.hex, .xml)
                    if (extension == ".dump" || extension == ".txt")
                    {
                        showHeader = true;
                    }
                    // .hex and .xml are auto-conversion, no header
                }
            }

            if (showHeader)
            {
                Console.WriteLine("=== V2G Transfer Protocol Decoder/Encoder");
                Console.WriteLine("=== ISO 15118-2 / DIN SPEC 70121 / SAP Protocol");
                Console.WriteLine("=== made by [tindevil82@gmail.com]");
                Console.WriteLine();
            }

            try
            {
                if (args.Length == 0)
                {
                    ShowUsage();
                    return;
                }

                var command = args[0].ToLower();
                
                // 첫 번째 인자가 옵션인 경우 (--decode, --encode, --help)
                if (command.StartsWith("--") || command.StartsWith("-"))
                {
                    switch (command)
                    {
                        case "--decode":
                        case "-d":
                            HandleDecodeCommand(args);
                            break;
                        case "--encode":
                        case "-e":
                            HandleEncodeCommand(args);
                            break;
                        case "--help":
                        case "-h":
                            ShowUsage();
                            break;
                        default:
                            Console.WriteLine($"Error: Unknown option: {args[0]}");
                            ShowUsage();
                            break;
                    }
                }
                // 두 번째 인자가 옵션인 경우 (filename --decode, filename --encode)
                else if (args.Length >= 2 && (args[1].ToLower() == "--decode" || args[1].ToLower() == "-d"))
                {
                    // filename --decode 형태 - 나머지 인자들도 전달
                    var newArgs = new List<string> { args[1], args[0] };
                    for (int i = 2; i < args.Length; i++)
                        newArgs.Add(args[i]);
                    HandleDecodeCommand(newArgs.ToArray());
                }
                else if (args.Length >= 2 && (args[1].ToLower() == "--encode" || args[1].ToLower() == "-e"))
                {
                    // filename --encode 형태 - 나머지 인자들도 전달
                    var newArgs = new List<string> { args[1], args[0] };
                    for (int i = 2; i < args.Length; i++)
                        newArgs.Add(args[i]);
                    HandleEncodeCommand(newArgs.ToArray());
                }
                // 파일명만 전달된 경우 (분석 모드)
                else if (File.Exists(args[0]))
                {
                    // 파일 확장자를 확인하여 기본 동작 결정
                    string extension = Path.GetExtension(args[0]).ToLower();
                    
                    if (extension == ".dump" || extension == ".txt")
                    {
                        // 헥스 덤프 파일 - 전체 분석 모드 사용
                        AnalyzeFile(args[0]);
                    }
                    else if (extension == ".hex")
                    {
                        // .hex 파일 - 자동으로 XML로 디코드
                        var autoArgs = new List<string> { "--decode", args[0] };
                        // 나머지 인자 추가 (-out 등)
                        for (int i = 1; i < args.Length; i++)
                            autoArgs.Add(args[i]);
                        HandleDecodeCommand(autoArgs.ToArray());
                    }
                    else if (extension == ".xml")
                    {
                        // .xml 파일 - 자동으로 EXI로 인코드
                        var autoArgs = new List<string> { "--encode", args[0] };
                        // 나머지 인자 추가 (-out 등)
                        for (int i = 1; i < args.Length; i++)
                            autoArgs.Add(args[i]);
                        HandleEncodeCommand(autoArgs.ToArray());
                    }
                    else
                    {
                        // 알 수 없는 확장자 - 명시적 옵션 필요
                        Console.WriteLine($"Error: Unknown file extension '{extension}'. Please specify operation:");
                        Console.WriteLine($"  {Path.GetFileName(args[0])} --decode   # To decode as EXI to XML");
                        Console.WriteLine($"  {Path.GetFileName(args[0])} --encode   # To encode as XML to EXI");
                        Console.WriteLine($"  OR use: --decode {args[0]} / --encode {args[0]}");
                        return;
                    }
                }
                else
                {
                    Console.WriteLine($"Error: Unknown option or file not found: {args[0]}");
                    //ShowUsage();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
        
        static void ShowUsage()
        {
            Console.WriteLine("Usage:");
            Console.WriteLine("  V2GDecoder.exe [file.dump/.txt]               # Analyze hex dump file");
            Console.WriteLine("  V2GDecoder.exe [file.hex]                     # Auto-decode hex to XML");
            Console.WriteLine("  V2GDecoder.exe [file.xml]                     # Auto-encode XML to EXI");
            Console.WriteLine("  V2GDecoder.exe --decode <exi_hex_or_file>      # Decode EXI to XML");
            Console.WriteLine("  V2GDecoder.exe --encode <xml_file_or_string>   # Encode XML to EXI");
            Console.WriteLine("  V2GDecoder.exe <file> --decode [-out file]     # Force decode operation");
            Console.WriteLine("  V2GDecoder.exe <file> --encode [-out file]     # Force encode operation");
            Console.WriteLine("  V2GDecoder.exe --help                          # Show this help");
            Console.WriteLine();
            Console.WriteLine("File Extension Auto-Processing:");
            Console.WriteLine("  .dump, .txt    → Full analysis mode (network + V2G message)");
            Console.WriteLine("  .hex           → Auto-decode to XML");
            Console.WriteLine("  .xml           → Auto-encode to EXI hex");
            Console.WriteLine("  Other          → Requires --decode or --encode option");
            Console.WriteLine();
            Console.WriteLine("Pipe Usage:");
            Console.WriteLine("  type file.xml | V2GDecoder.exe --encode        # Pipe XML file to encode");
            Console.WriteLine("  echo \"hex_string\" | V2GDecoder.exe --decode   # Pipe hex string to decode");
            Console.WriteLine("Example:");
            Console.WriteLine("V2GDecoder.exe 01FE80010000001E809802104142423030303831908C0C0C0E0C51E020256968C0C0C0C0C080 --decode");
            Console.WriteLine();
        }
        
        static string GetOutputFilename(string[] args)
        {
            for (int i = 0; i < args.Length - 1; i++)
            {
                if (args[i].ToLower() == "-out" || args[i].ToLower() == "-o")
                {
                    return args[i + 1];
                }
            }
            return null;
        }
        
        static void HandleDecodeCommand(string[] args)
        {
            string input;
            byte[] exiBytes;
            string outputFile = GetOutputFilename(args);
            
            // 파일/문자열 입력을 위한 충분한 인자가 있는지 확인
            if (args.Length >= 2)
            {
                // 명령줄 인자가 있으므로 사용
                input = args[1];
            }
            else if (Console.IsInputRedirected)
            {
                // 충분한 인자가 없으므로 stdin(파이프)에서 입력이 오는지 확인
                input = Console.In.ReadToEnd().Trim();
                if (string.IsNullOrEmpty(input))
                {
                    Console.WriteLine("Error: No input data received from stdin");
                    return;
                }
                outputFile = null; // stdin의 경우 콘솔 출력 강제
            }
            else
            {
                Console.WriteLine("Error: EXI hex string or file required for decode operation");
                Console.WriteLine("Usage: V2GDecoder.exe --decode <exi_hex_string_or_file> [-out output_file]");
                return;
            }
            
            // 입력이 파일인지 확인
            if (File.Exists(input))
            {
                // 다양한 파일 확장자 처리
                string extension = Path.GetExtension(input).ToLower();
                
                if (extension == ".dump")
                {
                    // 화살표(→)가 있는 헥스 덤프 파일 형식
                    byte[] rawData = V2GDecoder.ParseHexFile(input);
                    
                    // 헥스 덤프에서 V2G 메시지 찾기
                    var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
                    if (v2gMessage != null)
                    {
                        exiBytes = v2gMessage;
                    }
                    else
                    {
                        // 원시 데이터를 직접 디코드 시도 (V2G 헤더 없는 순수 EXI일 수 있음)
                        exiBytes = rawData;
                    }
                }
                else if (extension == ".hex")
                {
                    // 바이너리 hex 파일
                    exiBytes = File.ReadAllBytes(input);
                }
                else if (extension == ".txt")
                {
                    // .txt 파일에 대한 레거시 지원 - 컨텐츠 형식 확인
                    string fileContent = File.ReadAllText(input).Trim();
                    if (fileContent.Contains("→"))
                    {
                        // 헥스 덤프 형식
                        byte[] rawData = V2GDecoder.ParseHexFile(input);
                        var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
                        if (v2gMessage != null)
                        {
                            exiBytes = v2gMessage;
                        }
                        else
                        {
                            exiBytes = rawData;
                        }
                    }
                    else
                    {
                        // 순수 헥스
                        fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
                        exiBytes = Helper.FromHexString(fileContent);
                    }
                }
                else
                {
                    // 알 수 없는 확장자, 순수 헥스로 시도
                    string fileContent = File.ReadAllText(input).Trim();
                    fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
                    exiBytes = Helper.FromHexString(fileContent);
                }
            }
            else
            {
                // 직접 헥스 문자열 입력
                string exiHexString = input.Replace(" ", "").Replace("-", "").Replace("0x", "");
                
                if (exiHexString.Length % 2 != 0)
                {
                    Console.WriteLine("Error: Invalid hex string (odd number of characters)");
                    return;
                }
                
                exiBytes = Helper.FromHexString(exiHexString);
            }
            
            try
            {
                // EXI를 XML로 디코드 - XML만 출력
                var decodedXml = V2GDecoder.DecodeEXIToXML(exiBytes);
                
                if (outputFile != null)
                {
                    // 파일로 저장
                    File.WriteAllText(outputFile, decodedXml);
                    Console.WriteLine($"Decoded XML saved to: {outputFile}");
                }
                else
                {
                    // 콘솔로 출력
                    Console.WriteLine(decodedXml);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error decoding EXI: {ex.Message}");
            }
        }
        
        static byte[] ExtractV2GMessageFromHexDump(byte[] data)
        {
            // V2G Transfer Protocol 시그니처 찾기 (0x01FE)
            for (int i = 0; i < data.Length - 8; i++)
            {
                if (data[i] == 0x01 && data[i + 1] == 0xFE)
                {
                    // 페이로드 길이 가져오기
                    uint payloadLength = (uint)((data[i + 4] << 24) | (data[i + 5] << 16) | (data[i + 6] << 8) | data[i + 7]);
                    
                    // EXI 페이로드 추출
                    if (i + 8 + payloadLength <= data.Length)
                    {
                        byte[] exiPayload = new byte[payloadLength];
                        Array.Copy(data, i + 8, exiPayload, 0, (int)payloadLength);
                        return exiPayload;
                    }
                }
            }
            return null;
        }
        
        static void HandleEncodeCommand(string[] args)
        {
            string xmlInput;
            string xmlContent;
            string outputFile = GetOutputFilename(args);
            
            // 파일/문자열 입력을 위한 충분한 인자가 있는지 확인
            if (args.Length >= 2)
            {
                // 명령줄 인자가 있으므로 사용
                xmlInput = args[1];
                
                // 입력이 파일인지 확인
                if (File.Exists(xmlInput))
                {
                    xmlContent = File.ReadAllText(xmlInput);
                }
                else
                {
                    xmlContent = xmlInput;
                }
            }
            else if (Console.IsInputRedirected)
            {
                // 충분한 인자가 없으므로 stdin(파이프)에서 입력이 오는지 확인
                xmlContent = Console.In.ReadToEnd().Trim();
                if (string.IsNullOrEmpty(xmlContent))
                {
                    Console.WriteLine("Error: No input data received from stdin");
                    return;
                }
                outputFile = null; // stdin의 경우 콘솔 출력 강제
            }
            else
            {
                Console.WriteLine("Error: XML string or file required for encode operation");
                Console.WriteLine("Usage: V2GDecoder.exe --encode <xml_file_or_string> [-out output_file]");
                return;
            }
            
            try
            {
                // XML을 EXI로 인코드
                var exiBytes = V2GDecoder.EncodeXMLToEXI(xmlContent);
                
                if (outputFile != null)
                {
                    // Save as binary file
                    File.WriteAllBytes(outputFile, exiBytes);
                    Console.WriteLine($"Encoded binary saved to: {outputFile} ({exiBytes.Length} bytes)");
                    
                    // Also show hex string on console for reference
                    var exiHexString = Helper.ToHexString(exiBytes);
                    Console.WriteLine($"Hex: {exiHexString}");
                }
                else
                {
                    // Output hex string to console
                    var exiHexString = Helper.ToHexString(exiBytes);
                    Console.WriteLine(exiHexString);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error encoding XML: {ex.Message}");
            }
        }
        
        static void AnalyzeFile(string dataFilePath)
        {
            if (!File.Exists(dataFilePath))
            {
                Console.WriteLine($"Error: Data file not found: {dataFilePath}");
                return;
            }

            Console.WriteLine($"Analyzing file: {dataFilePath}");
            
            // 헥스 파일에서 바이너리 데이터 파싱
            byte[] rawData = V2GDecoder.ParseHexFile(dataFilePath);
            
            Console.WriteLine($"Parsed {rawData.Length} bytes from hex file");
            Console.WriteLine();
            
            // 전체 데이터 헥스 덤프
            Console.WriteLine("=== Raw Data Hex Dump ===");
            Console.WriteLine(V2GDecoder.BytesToHex(rawData));
            Console.WriteLine();
            
            // V2G 메시지 디코딩 시도
            Console.WriteLine("=== V2G Message Analysis ===");
            
            // 전체 데이터에서 V2G 메시지 찾기
            AnalyzeV2GMessages(rawData);
            
            // 네트워크 패킷 분석 (이더넷/IPv6/TCP 헤더 포함인 경우)
            AnalyzeNetworkPacket(rawData);
        }
        
        static void AnalyzeV2GMessages(byte[] data)
        {
            // V2G Transfer Protocol 시그니처 찾기
            for (int i = 0; i < data.Length - 8; i++)
            {
                if (data[i] == 0x01 && data[i + 1] == 0xFE) // V2G TP Header
                {
                    Console.WriteLine($"Potential V2G message found at offset 0x{i:X4}");
                    
                    try
                    {
                        byte[] messageData = new byte[data.Length - i];
                        Array.Copy(data, i, messageData, 0, messageData.Length);
                        
                        var message = V2GDecoder.DecodeMessage(messageData);
                        
                        Console.WriteLine($"  Version: 0x{message.Version:X2}");
                        Console.WriteLine($"  Inverse Version: 0x{message.InverseVersion:X2}");
                        Console.WriteLine($"  Payload Type: 0x{(ushort)message.PayloadType:X4} ({message.PayloadType})");
                        Console.WriteLine($"  Payload Length: {message.PayloadLength} bytes");
                        Console.WriteLine($"  Valid: {message.IsValid}");
                        Console.WriteLine();
                        
                        if (message.IsValid && message.Payload != null)
                        {
                            Console.WriteLine("=== Decoded Message Content ===");
                            Console.WriteLine(message.DecodedContent);
                            Console.WriteLine();
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"  Error decoding message: {ex.Message}");
                    }
                }
            }
        }
        
        static void AnalyzeNetworkPacket(byte[] data)
        {
            Console.WriteLine("=== Network Packet Analysis ===");
            
            if (data.Length < 54) // Minimum Ethernet + IPv6 + TCP header size
            {
                Console.WriteLine("Data too short for network packet analysis");
                return;
            }
            
            try
            {
                // 이더넷 헤더 분석 (14 bytes)
                Console.WriteLine("Ethernet Header:");
                Console.WriteLine($"  Destination MAC: {string.Join(":", data.Take(6).Select(b => b.ToString("X2")))}");
                Console.WriteLine($"  Source MAC: {string.Join(":", data.Skip(6).Take(6).Select(b => b.ToString("X2")))}");
                Console.WriteLine($"  EtherType: 0x{(data[12] << 8 | data[13]):X4}");
                
                ushort etherType = (ushort)(data[12] << 8 | data[13]);
                
                if (etherType == 0x86DD) // IPv6
                {
                    Console.WriteLine("\nIPv6 Header:");
                    int ipv6Offset = 14;
                    byte versionTrafficClass = data[ipv6Offset];
                    Console.WriteLine($"  Version: {(versionTrafficClass >> 4) & 0xF}");
                    Console.WriteLine($"  Traffic Class: 0x{((versionTrafficClass & 0xF) << 4 | (data[ipv6Offset + 1] >> 4)):X2}");
                    
                    ushort payloadLength = (ushort)(data[ipv6Offset + 4] << 8 | data[ipv6Offset + 5]);
                    byte nextHeader = data[ipv6Offset + 6];
                    byte hopLimit = data[ipv6Offset + 7];
                    
                    Console.WriteLine($"  Payload Length: {payloadLength}");
                    Console.WriteLine($"  Next Header: 0x{nextHeader:X2} ({(nextHeader == 6 ? "TCP" : "Other")})");
                    Console.WriteLine($"  Hop Limit: {hopLimit}");
                    
                    // IPv6 주소는 16바이트씩
                    byte[] srcAddr = new byte[16];
                    byte[] dstAddr = new byte[16];
                    Array.Copy(data, ipv6Offset + 8, srcAddr, 0, 16);
                    Array.Copy(data, ipv6Offset + 24, dstAddr, 0, 16);
                    
                    Console.WriteLine($"  Source Address: {string.Join(":", Enumerable.Range(0, 8).Select(i => $"{srcAddr[i*2]:X2}{srcAddr[i*2+1]:X2}"))}");
                    Console.WriteLine($"  Destination Address: {string.Join(":", Enumerable.Range(0, 8).Select(i => $"{dstAddr[i*2]:X2}{dstAddr[i*2+1]:X2}"))}");
                    
                    if (nextHeader == 6) // TCP
                    {
                        int tcpOffset = ipv6Offset + 40;
                        if (data.Length > tcpOffset + 20)
                        {
                            Console.WriteLine("\nTCP Header:");
                            ushort srcPort = (ushort)(data[tcpOffset] << 8 | data[tcpOffset + 1]);
                            ushort dstPort = (ushort)(data[tcpOffset + 2] << 8 | data[tcpOffset + 3]);
                            
                            Console.WriteLine($"  Source Port: {srcPort}");
                            Console.WriteLine($"  Destination Port: {dstPort}");
                            
                            // V2G는 일반적으로 포트 15118을 사용
                            if (srcPort == 15118 || dstPort == 15118)
                            {
                                Console.WriteLine("  -> V2G Communication detected (port 15118)!");
                            }
                            
                            // TCP 데이터 시작 위치 계산
                            byte dataOffset = (byte)((data[tcpOffset + 12] >> 4) * 4);
                            int tcpDataOffset = tcpOffset + dataOffset;
                            
                            if (tcpDataOffset < data.Length)
                            {
                                Console.WriteLine($"\nTCP Payload (starting at offset 0x{tcpDataOffset:X4}):");
                                byte[] tcpPayload = new byte[data.Length - tcpDataOffset];
                                Array.Copy(data, tcpDataOffset, tcpPayload, 0, tcpPayload.Length);
                                
                                // TCP 페이로드에서 V2G 메시지 찾기
                                if (tcpPayload.Length > 8 && tcpPayload[0] == 0x01 && tcpPayload[1] == 0xFE)
                                {
                                    Console.WriteLine("V2G Message found in TCP payload!");
                                    var v2gMessage = V2GDecoder.DecodeMessage(tcpPayload);
                                    Console.WriteLine(v2gMessage.DecodedContent);
                                }
                                else
                                {
                                    Console.WriteLine("TCP Payload hex dump:");
                                    Console.WriteLine(V2GDecoder.BytesToHex(tcpPayload));
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error analyzing network packet: {ex.Message}");
            }
        }
    }
}