using NUnit.Framework;
using static Corlib.Tests.NUnit.TestUtilities;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;

namespace System;

[TestFixture]
internal class SpanTests {


  private static IEnumerable<int> LengthGenerator(bool allowZero) {
    var min = allowZero ? 0 : 1;
    var max = 256;
    int[] others = [512, 1024, 2048, 4096, 8192, 16384, 32768, 65536];

    for (var i = min; i <= max; ++i)
      yield return i;

    foreach (var i in others) {
      yield return i - 1;
      yield return i;
      yield return i + 1;
    }
  }

  [Test]
  [TestCaseSource(nameof(SpanTests.LengthGenerator), [true])]
  public void CopyTo_CopiesCorrectly_ManagedInt(int length) {
    var sourceArray = Enumerable.Range(0, length).Select(i => i + 1).ToArray();
    var destinationArray = new int[length];

    var sourceSpan = sourceArray.AsSpan();
    var destinationSpan = destinationArray.AsSpan();

    sourceSpan.CopyTo(destinationSpan);

    for (var i = 0; i < length; ++i)
      Assert.AreEqual(sourceArray[i], destinationArray[i]);
  }

  [Test]
  [TestCaseSource(nameof(SpanTests.LengthGenerator), [false])]
  public unsafe void CopyTo_CopiesCorrectly_UnmanagedByte(int length) {
    var sourceArray = Enumerable.Range(0, length).Select(i => (byte)(i % 255 + 1)).ToArray();
    var destinationArray = new byte[length];


    fixed (byte* sourcePointer = &sourceArray[0])
    fixed (byte* destinationPointer = &destinationArray[0]) {
      var sourceSpan = new Span<byte>(sourcePointer, length);
      var destinationSpan = new Span<byte>(destinationPointer, length);
      sourceSpan.CopyTo(destinationSpan);
    }

    for (var i = 0; i < length; ++i)
      Assert.AreEqual(sourceArray[i], destinationArray[i]);
  }

  [Test]
  public unsafe void CopyTo_ShouldThrow_OnZeroLength() {
    int[] sourceArray = [1, 2, 3, 4, 5];
    var destinationArray = new int[5];

    fixed (int* sourcePtr = sourceArray)
    fixed (int* destinationPtr = destinationArray) {
      var sourceSpan = new Span<int>(sourcePtr, 5);
      var destinationSpan = new Span<int>(destinationPtr, 5);

      try {
        sourceSpan.CopyTo(destinationSpan[..0]);
        Assert.Fail("Expected exception");
      } catch (ArgumentException) {
        Assert.Pass();
      } catch (Exception e) {
        Assert.Fail($"Got exception: {e}");
      }

    }
  }

  [Test]
  public unsafe void CopyTo_ThrowsArgumentException_OnLengthMismatch() {
    int[] sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var destinationArray = new int[5];

    fixed (int* sourcePtr = sourceArray)
    fixed (int* destinationPtr = destinationArray) {
      var sourceSpan = new Span<int>(sourcePtr, 10);
      var destinationSpan = new Span<int>(destinationPtr, 5);

      try {
        sourceSpan.CopyTo(destinationSpan);
        Assert.Fail($"Should throw exception");
      } catch (ArgumentException) {
        Assert.Pass();
      } catch (Exception e) {
        Assert.Fail($"Got exception: {e}");
      }

    }
  }

  [Test]
  public unsafe void CopyTo_HandlesDifferentTypes() {
    double[] sourceArray = [1.1, 2.2, 3.3, 4.4, 5.5];
    var destinationArray = new double[5];

    fixed (double* sourcePtr = sourceArray)
    fixed (double* destinationPtr = destinationArray) {
      var sourceSpan = new Span<double>(sourcePtr, 5);
      var destinationSpan = new Span<double>(destinationPtr, 5);

      sourceSpan.CopyTo(destinationSpan);

      for (var i = 0; i < 5; i++)
        Assert.AreEqual(sourceArray[i], destinationArray[i]);
    }
  }

  [Test]
  public unsafe void CopyTo_PartialCopy() {
    int[] sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    var destinationArray = new int[10];

    fixed (int* sourcePtr = sourceArray)
    fixed (int* destinationPtr = destinationArray) {
      var sourceSpan = new Span<int>(sourcePtr, 10);
      var destinationSpan = new Span<int>(destinationPtr, 10);

      // Copy first 5 elements
      sourceSpan[..5].CopyTo(destinationSpan[..5]);

      for (var i = 0; i < 5; ++i)
        Assert.AreEqual(sourceArray[i], destinationArray[i]);

      // Remaining elements should be default value
      for (var i = 5; i < 10; ++i)
        Assert.AreEqual(0, destinationArray[i]);
    }
  }

  // User-defined struct
  [StructLayout(LayoutKind.Sequential)]
  private struct MyStruct {
    public int X;
    public float Y;
  }

  [Test]
  [TestCaseSource(nameof(SpanTests.LengthGenerator), [false])]
  public void CopyTo_CopiesCorrectly_UserDefinedStruct(int length) {
    var sourceArray = Enumerable.Range(0, length).Select(i => new MyStruct { X = i, Y = i + 0.5f }).ToArray();
    var destinationArray = new MyStruct[length];

    var sourceSpan = sourceArray.AsSpan();
    var destinationSpan = destinationArray.AsSpan();

    sourceSpan.CopyTo(destinationSpan);

    for (var i = 0; i < length; ++i) {
      Assert.AreEqual(sourceArray[i].X, destinationArray[i].X);
      Assert.AreEqual(sourceArray[i].Y, destinationArray[i].Y);
    }
  }

  [Test]
  [TestCaseSource(nameof(SpanTests.LengthGenerator), [false])]
  public void CopyTo_CopiesCorrectly_ReferenceTypes(int length) {
    var sourceArray = Enumerable.Range(0, length).Select(i => new string('a', i % 128)).ToArray();
    var destinationArray = new string[length];

    var sourceSpan = sourceArray.AsSpan();
    var destinationSpan = destinationArray.AsSpan();

    sourceSpan.CopyTo(destinationSpan);

    for (var i = 0; i < length; ++i) {
      Assert.AreEqual(sourceArray[i], destinationArray[i]);
    }
  }

  [Test]
  [TestCaseSource(nameof(SpanTests.LengthGenerator), [false])]
  public unsafe void CopyTo_CopiesCorrectly_BlittableTypes(int length) {
    var sourceArray = Enumerable.Range(0, length).Select(i => new MyStruct { X = i, Y = i + 0.5f }).ToArray();
    var destinationArray = new MyStruct[length];

    fixed (MyStruct* sourcePtr = sourceArray)
    fixed (MyStruct* destinationPtr = destinationArray) {
      var sourceSpan = new Span<MyStruct>(sourcePtr, length);
      var destinationSpan = new Span<MyStruct>(destinationPtr, length);

      sourceSpan.CopyTo(destinationSpan);
    }

    for (var i = 0; i < length; ++i) {
      Assert.AreEqual(sourceArray[i].X, destinationArray[i].X);
      Assert.AreEqual(sourceArray[i].Y, destinationArray[i].Y);
    }
  }

  [Test]
  public void CopyTo_StringToCharArray() {
    var source = "TEST";
    var sourceSpan = source.AsSpan();

    var targetChars = sourceSpan.ToArray();
    var target=new string(targetChars);
    
    Assert.AreEqual(source, target);
  }

  [Test]
  public void CopyTo_StringPartsToCharArray() {
    var source = "TEST";
    var sourceSpan = source.AsSpan()[1..2];

    var targetChars = sourceSpan.ToArray();
    var target = new string(targetChars);

    Assert.AreEqual(source[1..2], target);
  }

  [Test]
  public void CopyTo_PlainStringsShouldBeTheSameReference() {
    var source = "TEST";
    var sourceSpan = source.AsSpan();
    var target = sourceSpan.ToString();

#if SUPPORTS_SPAN
    Assert.AreEqual(source, target);
#else
    Assert.AreSame(source, target);
#endif
  }
}

