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

namespace V2GDecoderNet.EXI
{
    /// <summary>
    /// Byte Stream utilities for file operations
    /// </summary>
    public static class ByteStream
    {
        /// <summary>
        /// Write bytes to file
        /// </summary>
        /// <param name="data">byte array</param>
        /// <param name="filename">File name</param>
        /// <returns>Error-Code != 0 on failure</returns>
        public static int WriteBytesToFile(byte[] data, string filename)
        {
            try
            {
                if (data == null)
                    return EXIErrorCodes.EXI_ERROR_OUT_OF_BYTE_BUFFER;
                    
                if (string.IsNullOrEmpty(filename))
                    return EXIErrorCodes.EXI_ERROR_OUTPUT_FILE;

                File.WriteAllBytes(filename, data);
                return 0; // Success
            }
            catch (UnauthorizedAccessException)
            {
                return EXIErrorCodes.EXI_ERROR_OUTPUT_FILE;
            }
            catch (DirectoryNotFoundException)
            {
                return EXIErrorCodes.EXI_ERROR_OUTPUT_FILE;
            }
            catch (IOException)
            {
                return EXIErrorCodes.EXI_ERROR_OUTPUT_FILE;
            }
            catch
            {
                return EXIErrorCodes.EXI_ERROR_OUTPUT_FILE;
            }
        }

        /// <summary>
        /// Read bytes from file
        /// </summary>
        /// <param name="filename">File name</param>
        /// <param name="data">Output byte array</param>
        /// <param name="bytesRead">Number of bytes actually read</param>
        /// <returns>Error-Code != 0 on failure</returns>
        public static int ReadBytesFromFile(string filename, out byte[] data, out int bytesRead)
        {
            data = Array.Empty<byte>();
            bytesRead = 0;

            try
            {
                if (string.IsNullOrEmpty(filename))
                    return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;

                if (!File.Exists(filename))
                    return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;

                data = File.ReadAllBytes(filename);
                bytesRead = data.Length;
                return 0; // Success
            }
            catch (UnauthorizedAccessException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (DirectoryNotFoundException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (FileNotFoundException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (IOException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
        }

        /// <summary>
        /// Read bytes from file with buffer size limit
        /// </summary>
        /// <param name="filename">File name</param>
        /// <param name="maxSize">Maximum buffer size</param>
        /// <param name="data">Output byte array</param>
        /// <param name="bytesRead">Number of bytes actually read</param>
        /// <returns>Error-Code != 0 on failure</returns>
        public static int ReadBytesFromFile(string filename, int maxSize, out byte[] data, out int bytesRead)
        {
            data = Array.Empty<byte>();
            bytesRead = 0;

            try
            {
                if (string.IsNullOrEmpty(filename))
                    return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;

                if (!File.Exists(filename))
                    return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;

                using var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
                var fileSize = (int)fileStream.Length;
                
                if (fileSize > maxSize)
                    return EXIErrorCodes.EXI_ERROR_OUT_OF_BYTE_BUFFER;

                data = new byte[fileSize];
                bytesRead = fileStream.Read(data, 0, fileSize);
                
                return 0; // Success
            }
            catch (UnauthorizedAccessException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (DirectoryNotFoundException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (FileNotFoundException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch (IOException)
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
            catch
            {
                return EXIErrorCodes.EXI_ERROR_INPUT_FILE_HANDLE;
            }
        }

        /// <summary>
        /// Convert hex string to byte array
        /// </summary>
        /// <param name="hex">Hex string</param>
        /// <returns>Byte array</returns>
        public static byte[] HexStringToByteArray(string hex)
        {
            if (string.IsNullOrEmpty(hex))
                return Array.Empty<byte>();

            // Remove any whitespace or separators
            hex = hex.Replace(" ", "").Replace("-", "").Replace(":", "");
            
            if (hex.Length % 2 != 0)
                throw new ArgumentException("Hex string must have even number of characters");

            var result = new byte[hex.Length / 2];
            for (int i = 0; i < result.Length; i++)
            {
                if (!byte.TryParse(hex.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber, null, out result[i]))
                    throw new ArgumentException($"Invalid hex characters at position {i * 2}");
            }
            
            return result;
        }

        /// <summary>
        /// Convert byte array to hex string
        /// </summary>
        /// <param name="data">Byte array</param>
        /// <param name="uppercase">Use uppercase hex digits</param>
        /// <returns>Hex string</returns>
        public static string ByteArrayToHexString(byte[] data, bool uppercase = true)
        {
            if (data == null || data.Length == 0)
                return string.Empty;

            var format = uppercase ? "X2" : "x2";
            return string.Concat(data.Select(b => b.ToString(format)));
        }
    }
}