using System.Collections.Generic;
using System.Text;
using System.Linq;
using System;

namespace Lomont.Binary
{
    /// <summary>
    /// Provide a place to put binary and bitwise utility functions
    /// </summary>
    sealed public class BitUtils
    {
        /// <summary>
        /// Parse a N byte number, N=1 to 8 common.
        /// Little endian.
        /// </summary>
        /// <param name="data">The data to read from</param>
        /// <param name="offset">The offset to start, updated</param>
        /// <param name="n">The number of bytes</param>
        /// <returns>The decoded value</returns>
        public static ulong ReadN(List<byte> data, ref int offset, int n)
        {
            ulong val = 0;
            for (int i = 0; i < n; ++i)
                val += ((ulong)data[i + offset]) << (i * 8);
            offset += n;
            return val;
        }

        /// <summary>
        /// Parse a 2 byte number
        /// Little endian.
        /// </summary>
        /// <param name="data">The data to read from</param>
        /// <param name="offset">The offset to start, updated</param>
        /// <returns>The decoded value</returns>
        public static ushort Read2(List<byte> data, ref int offset)
        {
            offset += 2;
            return (ushort)(data[offset-2] + data[offset - 1] * 256);
        }

        /// <summary>
        /// Parse a 4 byte number, update index
        /// Little endian.
        /// </summary>
        /// <param name="data">The data to read from</param>
        /// <param name="offset">The offset to start, updated</param>
        /// <returns>The decoded value</returns>
        public static uint Read4(List<byte> data, ref int offset)
        {
            return (uint)ReadN(data, ref offset, 4);
        }

        /// <summary>
        /// Parse an 8 byte number, update index
        /// Little endian.
        /// </summary>
        /// <param name="data">The data to read from</param>
        /// <param name="offset">The offset to start, updated</param>
        /// <returns>The decoded value</returns>
        public static uint Read8(List<byte> data, ref int offset)
        {
            return (uint)ReadN(data, ref offset, 8);
        }

        /// <summary>
        /// Parse out an ASCII string of the given length.
        /// </summary>
        /// <param name="data">The data bytes to read from</param>
        /// <param name="offset">The start offset, updated</param>
        /// <param name="length">The number of bytes</param>
        /// <returns>The decoded string</returns>
        public static string ReadASCII(List<byte> data, ref int offset, int length)
        {
            byte[] text = new byte[length];
            data.CopyTo(offset, text, 0, length);
            offset += length;
            string s = System.Text.ASCIIEncoding.ASCII.GetString(text).Trim('\0');
            return s;
        }

        /// <summary>
        /// Parse out a null terminated ASCII string.
        /// </summary>
        /// <param name="data">The data bytes to read from</param>
        /// <param name="offset">The start offset, updated</param>
        /// <returns>the decoded string</returns>
        public static string ReadNullTerminatedASCII(List<byte> data, ref int offset)
        {
            List<byte> text = new List<byte>();
            while (data[offset] != 0)
                text.Add(data[offset++]);
            ++offset; // skip final 0
            string s = System.Text.ASCIIEncoding.ASCII.GetString(text.ToArray()).Trim('\0');
            return s;
        }

        /// <summary>
        /// Format data as a hex string
        /// </summary>
        /// <param name="data">The data</param>
        /// <param name="startOffset">Starting location for formatting</param>
        /// <param name="length">Number of bytes to format</param>
        /// <returns>Formatted hex string</returns>
        public static string FormatHex(List<byte> data, int startOffset, int length)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = startOffset; i < startOffset + length; ++i)
                sb.AppendFormat("{0:X2} ", data[i]);
            return sb.ToString();
        }

        /// <summary>
        /// Convert a string of hex to a list of bytes
        /// </summary>
        /// <param name="hex">The hex string to convert</param>
        /// <returns>The list of converted bytes</returns>
        public static List<byte> ConvertHexStringToBytes(string hex)
            {
            return Enumerable.Range(0, hex.Length).
             Where(x => 0 == x % 2).
             Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).
             ToList();
            }

    }
}
