//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;

namespace EhLib
{

  public delegate void InternalArrayValueChanged(int index, int value);

  public class AxisValuesArray
  {
    internal int[] Array;
    readonly InternalArrayValueChanged _valueChangedEvent;

    public AxisValuesArray(InternalArrayValueChanged valueChangedEvent)
    {
      Array = new int[] { };
      _valueChangedEvent += valueChangedEvent;
    }

    public int this[int index]
    {
      get { return Array[index]; }
      set
      {
        int oldVal;
        if (Array[index] != value)
        {
          oldVal = Array[index];
          Array[index] = value;
          if (_valueChangedEvent != null)
            _valueChangedEvent(index, oldVal);
        }
      }
    }
  }

  public class RollLocCellPosInternalArray
  {
    internal int[] Array;
    private readonly GridAxisData _gridAxis;

    public RollLocCellPosInternalArray(GridAxisData gridAxis)
    {
      Array = new int[] { };
      _gridAxis = gridAxis;
    }

    public int this[int index]
    {
      get 
      { 
        _gridAxis.CheckUpdateRollCellPosArray();
        return Array[index]; 
      }

      set { Array[index] = value; }
    }
  }

  public class CellLensAccessClass
  {
    private readonly GridAxisData _gridAxis;

    public CellLensAccessClass(GridAxisData gridAxis)
    {
      _gridAxis = gridAxis;
    }

    public int this[int index]
    {
      get
      {
        return _gridAxis.GetCellLens(index);
      }
      set
      {
         _gridAxis.SetCellLens(index, value);
      }
    }
  }

  public class GridAxisData
  {
    #region private consts
    private readonly BaseGridControl _grid;
    private bool _rollLocCellPosArrObsolete;
    private int _contraStart;
    private int _contraCellCount;
    private int _fixedCellCount;
    private int _gridClientStop;
    private int _gridClientStart;
    private int _fixedBoundary;
    private int _rolCellCount;
    private long _rollStartVisPos;
    private int _rollStartVisCell;
    private int _rollStartVisCellOffset;
    private int _winClientBoundStop;
    private int _winClientBoundStart;
    private int _defaultCellLen;
    private int _frozenCellCount;
    private int _frozenLen;
    private int _rolLastFullVisCel;
    private int _rolLastVisCel;
    private int _contraLen;

    private readonly AxisValuesArray _fixedCellLens;
    private readonly AxisValuesArray _rollCellLens;
    private readonly AxisValuesArray _contraCellLens;
    private readonly RollLocCellPosInternalArray _rollLocCellPosArr;

    private readonly CellLensAccessClass _cellLensAccess;
    #endregion

    // Constructor
    public GridAxisData(BaseGridControl grid)
    {
      _grid = grid;

      _fixedCellLens = new AxisValuesArray(FixedCellLensChangedHandler);
      _rollCellLens = new AxisValuesArray(RolCellLensChangedHandler);
      _contraCellLens = new AxisValuesArray(ContraCellLensChangedHandler);

      _rollLocCellPosArr = new RollLocCellPosInternalArray(this);
      _cellLensAccess = new CellLensAccessClass(this);
    }

    #region public properties
    public BaseGridControl Grid
    {
      get { return _grid; }
    }

    public int FixedCellCount
    {
      get { return _fixedCellCount; }
      set { SetFixedColCount(value); }
    }

    public int FrozenCellCount
    {
      get { return _frozenCellCount; }
      set { SetFrozenCellCount(value); }
    }

    public int RollCellCount
    {
      get { return _rolCellCount; }
      set { SetRollCellCount(value); }
    }

    public int ContraCellCount
    {
      get { return _contraCellCount; }
      set { SetContraCellCount(value); }
    }

    public int DefaultCellLen
    {
      get { return _defaultCellLen; }
      set { SetDefaultCellLen(value); }
    }

    public int CellCount
    {
      get { return GetCellCount(); }
    }

    public int FullCellCount
    {
      get { return FixedCellCount + RollCellCount + ContraCellCount; }
    }

    public int WinClientBoundStart
    {
      get { return _winClientBoundStart; }
      internal set { _winClientBoundStart = value; }
    }

    public int WinClientBoundStop
    {
      get { return _winClientBoundStop; }
      internal set { _winClientBoundStop = value; }
    }

    public int GridClientStart
    {
      get
      {
        CheckUpdateAxises();
        return _gridClientStart;
      }

      internal set
      {
        _gridClientStart = value;
      }
    }

    public int GridClientStop
    {
      get
      {
        CheckUpdateAxises();
        return _gridClientStop;
      }

      internal set
      {
        _gridClientStop = value;
      }
    }

    public int GridClientLen
    {
      get { return GetGridClientLen(); }
    }

    public int FixedBoundary
    {
      get
      {
        return _fixedBoundary;
      }

      internal set
      {
        _fixedBoundary = value;
      }
    }

    public int RollClientLen
    {
      get { return GetRollClientLen(); }
    }

    public int ContraStart
    {
      get
      {
        return _contraStart;
      }

      internal set
      {
        _contraStart = value;
      }
    }

    public int ContraLen
    {
      get
      {
        return _contraLen;
      }

      internal set
      {
        _contraLen = value;
      }
    }

    public int FrozenLen
    {
      get
      {
        return _frozenLen;
      }

      internal set
      {
        _frozenLen = value;
      }
    }

    public long RollStartVisPos
    {
      get { return _rollStartVisPos; }
      set { SetRollStartVisPos(value); }
    }

    public long RollStopVisPos
    {
      get { return GetRollStopVisPos(); }
    }

    public long RollLen
    {
      get { return GetRollLen(); }
    }

    public int RollInClientBoundary
    {
      get { return GetRollInClientBoundary(); }
    }

    public int RollStartVisCell
    {
      get { return _rollStartVisCell; }
    }

    public int RollStartVisCellOffset
    {
      get { return _rollStartVisCellOffset; }
    }

    public int RollLastVisCell
    {
      get
      {
        CheckUpdateRollCellPosArray();
        return _rolLastVisCel;
      }

      internal set
      {
        _rolLastVisCel = value;
      }

    }

    public int RollLastFullVisCell
    {
      get
      {
        CheckUpdateRollCellPosArray();
        return _rolLastFullVisCel;
      }
      internal set
      {
        _rolLastFullVisCel = value;
      }
    }

    public int StartVisCell
    {
      get { return GetStartVisCell(); }
    }

    public int RollVisCellCount
    {
      get { return RollLastVisCell - RollStartVisCell + 1; }
    }

    public AxisValuesArray FixedCellLens
    {
      get { return _fixedCellLens; }
    }

    public AxisValuesArray RollCellLens
    {
      get { return _rollCellLens; }
    }

    public AxisValuesArray ContraCellLens
    {
      get { return _contraCellLens; }
    }

    public CellLensAccessClass CellLens
    {
      get { return _cellLensAccess; }
    }

    public RollLocCellPosInternalArray RollLocCellPosArr
    {
      get { return _rollLocCellPosArr; }
    }

    public bool RollLocCellPosArrObsolete
    {
      get { return _rollLocCellPosArrObsolete; }
    }
    #endregion

    #region public methods
    public void InsertRollCells(int pos, int count)
    {
      ArrayInsertRange(ref _rollCellLens.Array, pos, count);
      FillArray(_rollCellLens.Array, pos, count, DefaultCellLen);

      Array.Resize(ref _rollLocCellPosArr.Array, _rollCellLens.Array.Length);

      _rolCellCount = _rollCellLens.Array.Length;
      _rollLocCellPosArrObsolete = true;
      Grid.CellCountChanged();
    }

    public void DeleteRollCells(int pos, int count)
    {
      ArrayDeleteRange(ref _rollCellLens.Array, pos, count);
      Array.Resize(ref _rollLocCellPosArr.Array, _rollCellLens.Array.Length);
      _rolCellCount = _rollCellLens.Array.Length;
      _rollLocCellPosArrObsolete = true;
      Grid.CellCountChanged();
    }

    public void RollCellAtPos(long pos, out int cell, out int cellOffset)
    {
      BinarySearch(_rollLocCellPosArr.Array, pos, out cell, out cellOffset);
    }

    internal void CheckUpdateRollCellPosArray()
    {
      if (_rollLocCellPosArrObsolete == true)
      {
        UpdateRollCellPosArray();
        Grid.RolSizeUpdated();
      }
    }

    public long SafeSetRollStartVisCell(int newStartCell, BaseGridScrollStepMode scrollStepType = BaseGridScrollStepMode.ByPixel)
    {
      if (newStartCell < 0) newStartCell = 0;
      if (newStartCell >= RollCellCount) newStartCell = RollCellCount - 1;

      long result = RollLocCellPosArr[newStartCell];
      if (result > RollLen - RollClientLen)
      {
        if (scrollStepType == BaseGridScrollStepMode.ByPixel)
        {
          result = RollLen - RollClientLen;
          RollStartVisPos = CheckRollStartVisPos(result);
        }
        else
        {
          newStartCell = CalcMaxStartCellFor(RollCellCount - 1);
          RollStartVisPos = RollLocCellPosArr[newStartCell];
        }
      }
      else
        RollStartVisPos = CheckRollStartVisPos(result);
      return result;
    }

    public long CheckRollStartVisPos(long rollStartVisPos)
    {
      long result = rollStartVisPos;
      if (result > RollLen - RollClientLen)
        result = RollLen - RollClientLen;
      if (result < 0)
        result = 0;
      return result;
    }

    public int CalcMaxStartCellFor(int rollFinishCell)
    {
      int result = rollFinishCell;

      for (int i = rollFinishCell; i >= 0; i--)
      {
        if (((RollLocCellPosArr[rollFinishCell] + RollCellLens[rollFinishCell]) - RollLocCellPosArr[i]) > RollClientLen)
          break;
        result = i;
      }

      return result;
    }
    #endregion

    #region internal methods
    private void SetFrozenCellCount(int value)
    {
      if (_frozenCellCount != value)
      {
        _frozenCellCount = value;
        Grid.UpdateBoundaries();
      }
    }

    private void SetRollCellCount(int value)
    {
      if (_rolCellCount == value) return;

      if (value < 1)
        throw (new InvalidOperationException("RollCellCount can''t be less then 1"));

      int delta = value - _rolCellCount;
      if (delta > 0)
        InsertRollCells(_rolCellCount, delta);
      else if (delta < 0)
        DeleteRollCells(_rolCellCount+delta, -delta);
    }

    private void SetContraCellCount(int value)
    {
      int delta;
      if (_contraCellCount != value)
      {
        if (value < 0)
          throw (new ArgumentOutOfRangeException("value", "ContraCellCount can''t be less then 0"));
        delta = value - _contraCellCount;
        Array.Resize(ref _contraCellLens.Array, value);
        if (delta > 0)
          FillArray(_contraCellLens.Array, _contraCellCount, delta, DefaultCellLen);
        _contraCellCount = value;
        Grid.CellCountChanged();
        Grid.UpdateBoundaries();
      }
    }

    private void FillArray(int[] array, int pos, int count, int value)
    {
      for (int i = pos; i < pos + count; i++)
      {
        array[i] = value;
      }
    }

    private void SetDefaultCellLen(int value)
    {
      _defaultCellLen = value;
    }

    private int GetCellCount()
    {
      return FixedCellCount + RollCellCount;
    }

    private int GetGridClientLen()
    {
      return GridClientStop - GridClientStart;
    }

    private int GetRollClientLen()
    {
      int result = ContraStart - FixedBoundary;
      if (result < 0) result = 0;
      return result;
    }

    private void SetRollStartVisPos(long value)
    {
      long oldRolStartVisPos;

      if (_rollStartVisPos != value)
      {
        if (value > RollLen)
          throw (new InvalidOperationException("RollStartVisPos can't be > RollLen"));
        oldRolStartVisPos = _rollStartVisPos;
        _rollStartVisPos = value;
        CheckUpdateRollCellPosArray();
        UpdateVisCells();
        Grid.RolPosAxisChanged(this, oldRolStartVisPos);
      }
    }

    private long GetRollStopVisPos()
    {
      return RollStartVisPos + RollLen;
    }

    private long GetRollLen()
    {
      return RollLocCellPosArr[_rollLocCellPosArr.Array.Length - 1] + RollCellLens[_rollCellLens.Array.Length - 1];
    }

    private int GetRollInClientBoundary()
    {
      int result = (int)(FixedBoundary - RollStartVisPos + RollLen);
      if (result > ContraStart)
        result = ContraStart;
      return result;
    }

    internal int GetCellLens(int index)
    {
      if (index < FixedCellCount)
        return FixedCellLens[index];
      else if (index < CellCount)
        return _rollCellLens[index - FixedCellCount];
      else
        return _contraCellLens[index - CellCount];
    }

    internal void SetCellLens(int index, int value)
    {
      if (index < FixedCellCount)
        FixedCellLens[index] = value;
      else if (index < CellCount)
        RollCellLens[index - FixedCellCount] = value;
      else
        ContraCellLens[index - CellCount] = value;
    }

    private void SetFixedColCount(int value)
    {
      int delta;
      if (_fixedCellCount != value)
      {
        if (value < 0)
          throw (new InvalidOperationException("RolColCount can''t be less then 0"));
        delta = value - _fixedCellCount;
        Array.Resize(ref _fixedCellLens.Array, value);
        if (delta > 0)
          FillArray(_fixedCellLens.Array, _fixedCellCount, delta, DefaultCellLen);
        _fixedCellCount = value;
        Grid.CellCountChanged();
        Grid.UpdateBoundaries();
      }
    }

    internal int GetScrollStep()
    {
      int result = (ContraStart - FixedBoundary) / 20;
      if (result == 0)
        result = 1;
      return result;
    }

    internal void MoveCell(int fromIndex, int toIndex)
    {
      if ((fromIndex <= FixedCellCount - 1) &&
          (toIndex <= FixedCellCount - 1))
      {
        ArrayMove(_fixedCellLens.Array, fromIndex, toIndex);
      }
      else if ((fromIndex >= FixedCellCount) &&
               (fromIndex <= CellCount - 1) &&
               (toIndex >= FixedCellCount) &&
               (toIndex <= CellCount - 1))
      {
        ArrayMove(_rollCellLens.Array, fromIndex - FixedCellCount, toIndex - FixedCellCount);
        _rollLocCellPosArrObsolete = true;
      }
      else if ((fromIndex >= CellCount) &&
               (fromIndex <= FullCellCount - 1) &&
               (toIndex >= CellCount) &&
               (toIndex <= FullCellCount - 1))
      {
        ArrayMove(_contraCellLens.Array, fromIndex - CellCount, toIndex - CellCount);
      }
      else
      {
        throw (new InvalidOperationException("MoveCell in different areas[FromIndex:%D, ToIndex:%D"));
      }

      Grid.AxisMoved(this, fromIndex, toIndex);
    }

    internal void ArrayMove(int[] extents, int fromIndex, int toIndex)
    {
      int extent;
      int i;

      if (extents.Length != 0)
      {
        extent = extents[fromIndex];
        if (fromIndex < toIndex)
          for (i = fromIndex + 1; i <= toIndex; i++)
            extents[i - 1] = extents[i];
        else if (fromIndex > toIndex)
          for (i = fromIndex - 1; i >= toIndex; i--)
            extents[i + 1] = extents[i];
        extents[toIndex] = extent;
      }
    }

    private void CheckUpdateAxises()
    {
      Grid.CheckUpdateAxises();
    }

    private void UpdateVisCells()
    {
      BinarySearch(_rollLocCellPosArr.Array, _rollStartVisPos, out _rollStartVisCell, out _rollStartVisCellOffset);
      GetLastVisibleCell(out _rolLastVisCel, out _rolLastFullVisCel);
    }

    internal void GetLastVisibleCell(out int lastVisCell, out int lastFullVisCell)
    {
      int i, pos;
      long targetPos;
      int targetCelOffset;

      lastVisCell = RollStartVisCell;
      lastFullVisCell = RollStartVisCell;
      if (FixedBoundary >= ContraStart)
      {
        pos = 0;
        for (i = 1; i < FixedCellCount; i++)
        {
          pos = pos + CellLens[i];
          if (pos >= ContraStart)
          {
            lastFullVisCell = i;
            if ((pos > ContraStart) && (i < FixedCellCount - 1))
              lastVisCell = i + 1;
            else
              lastVisCell = i;
            return;
          }
        }
      }
      else
      {
        targetPos = RollStartVisPos + RollClientLen - 1;
        BinarySearch(_rollLocCellPosArr.Array, targetPos, out lastVisCell, out targetCelOffset);
        if ((targetCelOffset < RollCellLens[lastVisCell] - 1) && (lastVisCell > RollStartVisCell))
          lastFullVisCell = lastVisCell - 1;
        else
          lastFullVisCell = lastVisCell;
        //    Inc(LastVisCell, FixedCellCount);
        //    Inc(LastFullVisCell, FixedCellCount);
      }
    }

    internal static void ArrayInsertRange(ref int[] extents, int startIndex, int amount)
    {
      if (amount < 0)
        throw (new InvalidOperationException("ArrayInsertRange: (Amount < 0)"));
      if (startIndex > extents.Length)
        throw (new InvalidOperationException("ArrayInsertRange: StartIndex > Length(Extents)"));

      if (extents.Length == startIndex)
        Array.Resize(ref extents, extents.Length + amount);
      else
      {
        Array.Resize(ref extents, extents.Length + amount);
        for (int i = extents.Length - amount - 1; i >= startIndex; i++)
          extents[i + amount] = extents[i];
      }
    }

    internal static void ArrayDeleteRange(ref int[] extents, int startIndex, int amount)
    {
      if (amount < 0)
        throw (new ArgumentOutOfRangeException("amount"));
      if (startIndex + amount > extents.Length)
        throw (new ArgumentOutOfRangeException("amount", "ArrayDeleteRange: StartIndex + Amount > Length(Extents)"));

      if (startIndex + amount < extents.Length)
        for (int i = startIndex; i <= extents.Length - amount - 1; i++)
          extents[i] = extents[i + amount];

      Array.Resize(ref extents, extents.Length - amount);
    }

    internal void BinarySearch(int[] poses, long targetPos, out int col, out int colOffset)
    {
      int arrSize = poses.Length;
      int min = 0;
      int max = arrSize - 1;
      if (poses[min] >= targetPos)
      {
        col = min;
        colOffset = (int)(targetPos - poses[min]);
        return;
      }
      else if (poses[max] <= targetPos)
      {
        col = max;
        colOffset = (int)(targetPos - poses[max]);
        return;
      }

      int Index = (max - min) / 2;
      int newIndex = Index;

      while (true)
      {
        if (poses[Index] > targetPos)
        {
          max = Index;
          Index = (max + min) / 2;
        }
        else if (poses[Index] < targetPos)
        {
          min = Index;
          Index = (max + min) / 2;
        }
        else
        {
          col = Index;
          colOffset = 0;
          return;
        }
        if (newIndex == Index)
        {
          col = Index;
          colOffset = (int)(targetPos - poses[Index]);
          return;
        }
        newIndex = Index;
      }
    }

    private void UpdateRollCellPosArray()
    {
      int i;

      _rollLocCellPosArr[0] = 0;
      for (i = 1; i < RollCellCount; i++)
        _rollLocCellPosArr.Array[i] = _rollLocCellPosArr.Array[i - 1] + _rollCellLens.Array[i - 1];
      UpdateVisCells();
      _rollLocCellPosArrObsolete = false;
    }

    private int GetStartVisCell()
    {
      return RollStartVisCell + FixedCellCount;
    }

    private void FixedCellLensChangedHandler(int index, int oldLen)
    {
      Grid.CellLenChanged(this, index, oldLen);
    }

    private void RolCellLensChangedHandler(int index, int oldLen)
    {
      _rollLocCellPosArrObsolete = true;
      Grid.CellLenChanged(this, index + FixedCellCount, oldLen + FixedCellCount);
      //Grid.CellCountChanged();
    }

    private void ContraCellLensChangedHandler(int index, int oldLen)
    {
      Grid.CellLenChanged(this, index + CellCount, oldLen + CellCount);
    }
    #endregion

  }
}
