using System;
using System.Collections.Generic;
using System.Linq;
using KitsuneCafe.Sys.Numerics;

namespace KitsuneCafe.Sys
{
  public record Index(int Value, bool IsFromEnd = false)
  {
    public static Index Start = new(0);
    public static Index End = new(0, true);
    public static Index NegativeInfinity = new(int.MinValue);
    public static Index Infinity = new(int.MaxValue);

    public static Index FromStart(int value) => new(value);
    public static Index FromEnd(int value) => new(value, true);

    public int GetOffset(int length) => IsFromEnd ? length - Value : Value;

    public static implicit operator int(Index value) => value.IsFromEnd ? -value.Value : value.Value;
    public static implicit operator Index(int value) => new(value, value < 0);
    public static implicit operator global::System.Index(Index index) => new(index.Value, index.IsFromEnd);
    public static implicit operator Index(global::System.Index index) => new(index.Value, index.IsFromEnd);
  }

  public record Range(Index Start, Index End)
  {
    public static Range All = new(Index.Start, Index.End);

    public static Range StartAt(Index start) => new(start, Index.End);
    public static Range EndAt(Index end) => new(Index.Start, end);
    public static Range OfLength(Index offset, int length) => new(offset, offset + length);

    public ClampingRange Clamp() => new(Start, End);
    public ShiftingRange Shift() => new(Start, End);
    public WrappingRange Wrap() => new(Start, End);

    public static implicit operator System.Range(Range range) => new(range.Start, range.End);
    public static implicit operator Range(System.Range range) => new(range.Start, range.End);

    public virtual (int Offset, int Length) GetOffsetAndLength(int length)
    {
      var start = Start.GetOffset(length);
      var end = End.GetOffset(length);
      return (start, end - start);
    }

    public virtual bool Contains(int length, Index index)
    {
      var start = Start.GetOffset(length);
      var end = End.GetOffset(length);
      return index >= start && index < end;
    }

    public virtual IEnumerable<int> Indices(int length)
    {
      var (start, len) = GetOffsetAndLength(length);
      UnityEngine.Debug.Log($"making indices: start {start}, length {len}");

      for (int i = start; i < start + len; i++)
      {
        yield return i;
      }
    }

    public virtual int[] ToArray(int length)
    {
      UnityEngine.Debug.Log($"making array of indices of length {length}");
      return Indices(length).ToArray();
    }

    public void Deconstruct(out Index start, out Index end)
    {
      start = Start;
      end = End;
    }
  }

  public record ClampingRange(Index Start, Index End) : Range(Start, End)
  {
    public override (int Offset, int Length) GetOffsetAndLength(int length)
    {
      var (start, len) = base.GetOffsetAndLength(length);
      return (start, Math.Clamp(len, 0, length - 1));
    }
  }

  public record ShiftingRange(Index Start, Index End) : Range(Start, End)
  {
    public override (int Offset, int Length) GetOffsetAndLength(int length)
    {

      var (start, len) = base.GetOffsetAndLength(length);

      int newStart = start;
      int newEnd = start + len;

      if (newEnd > length)
      {
        int offset = newEnd - length;
        newStart -= offset;
        newEnd -= offset;
      }

      if (newStart < 0)
      {
        int shiftAmount = 0 - newStart;
        newStart += shiftAmount;
        newEnd += shiftAmount;
      }

      int newLength = Math.Min(newEnd - newStart, length);

      int newOffset = newStart;

      return (newOffset, newLength);
    }
  };

  public record WrappingRange(Index Start, Index End) : Range(Start, End)
  {
    public static WrappingRange WrapFrom(Index start) => new(start, Index.Infinity);
    public static WrappingRange WrapUntil(Index end) => new(Index.NegativeInfinity, end);

    public override (int Offset, int Length) GetOffsetAndLength(int length)
    {
      var start = Start.GetOffset(length);
      var end = End.GetOffset(length);
      var len = start + Math.Abs(end - start);
      return (start, len);
    }

    public override bool Contains(int length, Index index)
    {
      if (Start <= End)
      {
        return base.Contains(length, index);
      }
      else
      {
        return (Start <= index && index < length) || (0 <= index && index < End);
      }
    }

    public override IEnumerable<int> Indices(int length)
    {
      var (start, len) = GetOffsetAndLength(length);

      for (int i = start; i < len; i++)
      {
        yield return i % length;
      }
    }
  }
}
