using System;
using UnityEngine;

namespace Marro.PacManUdon
{
    public static class ByteUtils
    {
        public static void Append(this byte[] target, byte[] value, ref int index)
        {
            //Debug.Log($"ByteUtils Append {nameof(value)}.Length: {value.Length}, {nameof(target)}.Length : {target.Length}, {nameof(index)}: {index}");
            Array.Copy(value, 0, target, index, value.Length);
            index += value.Length;
        }

        public static void Append(this byte[] target, byte value, ref int index) =>
            target[index++] = value;

        public static void Append(this byte[] target, sbyte  value, ref int index) =>
            target[index++] = (byte)value;

        public static void Append(this byte[] target, bool value, ref int index) =>
            target[index++] = value ? (byte)1 : (byte)0;

        public static void Append(this byte[] target, ushort value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, short value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, uint value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, int value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        /// <summary>
        /// Casts <paramref name="value"/> to a byte and then appends it.
        /// Safer than doing <c>Append((byte)value, ref index)</c>, which can crash the Udonbehaviour.
        /// </summary>
        public static void AppendAsByte(this byte[] target, int value, ref int index) =>
            target.Append(value.ToByte(), ref index);

        public static void Append(this byte[] target, ulong value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, long value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, float value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, double value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, char value, ref int index) =>
            target.Append(BitConverter.GetBytes(value), ref index);

        public static void Append(this byte[] target, Vector2 value, ref int index)
        {
            target.Append(value.x, ref index);
            target.Append(value.y, ref index);
        }

        public static void Append(this byte[] target, Vector3 value, ref int index)
        {
            target.Append(value.x, ref index);
            target.Append(value.y, ref index);
            target.Append(value.z, ref index);
        }

        public static byte ReadByte(this byte[] source, ref int index) =>
            source[index++];

        public static bool ReadBool(this byte[] source, ref int index) =>
            source[index++] > 0;

        public static ushort ReadUShort(this byte[] source, ref int index)
        {
            var value = BitConverter.ToUInt16(source, index);
            index += 2;
            return value;
        }

        public static short ReadShort(this byte[] source, ref int index)
        {
            var value = BitConverter.ToInt16(source, (int)index);
            index += 2;
            return value;
        }

        public static uint ReadUInt(this byte[] source, ref int index)
        {
            var value = BitConverter.ToUInt32(source, index);
            index += 4;
            return value;
        }

        public static int ReadInt(this byte[] source, ref int index)
        {
            var value = BitConverter.ToInt32(source, index);
            index += 4;
            return value;
        }

        public static ulong ReadULong(this byte[] source, ref int index)
        {
            var value = BitConverter.ToUInt64(source, index);
            index += 8;
            return value;
        }

        public static long ReadLong(this byte[] source, ref int index)
        {
            var value = BitConverter.ToInt64(source, index);
            index += 8;
            return value;
        }

        public static float ReadFloat(this byte[] source, ref int index)
        {
            var value = BitConverter.ToSingle(source, index);
            index += 4;
            return value;
        }

        public static double ReadDouble(this byte[] source, ref int index)
        {
            var value = BitConverter.ToDouble(source, index);
            index += 8;
            return value;
        }

        public static char ReadChar(this byte[] source, ref int index)
        {
            var value = BitConverter.ToChar(source, index);
            index += 2;
            return value;
        }

        public static Vector2 ReadVector2(this byte[] source, ref int index)
        {
            var value = new Vector2(BitConverter.ToSingle(source, index), BitConverter.ToSingle(source, index + 4));
            index += 8;
            return value;
        }

        public static Vector3 ReadVector3(this byte[] source, ref int index)
        {
            var value = new Vector3(BitConverter.ToSingle(source, index), BitConverter.ToSingle(source, index + 4), BitConverter.ToSingle(source, index + 8));
            index += 12;
            return value;
        }

        /// <summary>
        /// Casts an Int32 to a byte.
        /// Doing this inline sometimes causes an udon exception, but doing it via a separate method seems to work fine.
        /// </summary>
        public static byte ToByte(this int value) =>
            (byte)value;
    }
}