using Godot;
using System.Collections.Generic;
using System.Text;

namespace Rokojori
{  
  public partial class BitView
  {
    byte[] _data = new byte[ 0 ];

    int _internalViewOffset = 0;
    public int bitOffset => _internalViewOffset;

    int _internalViewSize = 0;    
    public int numBits => _internalViewSize;

    public int numBytes => Mathf.CeilToInt( _internalViewSize / 8f );
    

    int _externalPointer = 0; 
    public int pointerPosition => _externalPointer;

    public void SetPointer( int bitPosition )
    {
      _externalPointer = bitPosition;
    }


    public byte[] GetBytes()
    {
      var numBytes = this.numBytes;
      var data = new byte[ numBytes ];
      System.Array.Fill( data, (byte)0 );

      for ( int i = 0; i < _internalViewSize; i++ )
      {
        var isSet = GetValueInternal( i + _internalViewOffset );

        if ( ! isSet )
        {
          continue;
        }

        var shift = i % 8;
        var index = i / 8;
        var value = 1 << shift;

        data[ index ] = (byte) ( data[ index ] | value );
      }


      return data;
    }

    public BitView Clone()
    {
      var clone = new BitView();
      clone._data = _data;
      clone._internalViewOffset = _internalViewOffset;
      clone._internalViewSize   = _internalViewSize;
      clone._externalPointer    = _externalPointer;

      return clone;
    }

    public string GetBitString()
    {
      var output = new StringBuilder();
      
      for ( int i = 0; i < _internalViewSize; i++ )
      {
        var mod = i % 8;

        if ( i != 0 && mod == 0 )
        {
          output.Append( " " );
        }

        var reverseIndex = 7 - mod * 2;

        var value = GetValueInternal( reverseIndex + i + _internalViewOffset ) ? "1" : "0";
        output.Append( value );
      }

      return output.ToString();
    }

    void SetValueInternal( int position, bool value )
    {
      var byteIndex  = position / 8;
      var bitOffset = position - byteIndex * 8;

      if ( value ) 
      {
        _data[ byteIndex ] = Bytes.SetBit( _data[ byteIndex ], bitOffset );  
      }
      else
      {
        _data[ byteIndex ] = Bytes.UnsetBit( _data[ byteIndex ], bitOffset );  
      }
    }

    bool GetValueInternal( int bitPosition )
    {
      var byteIndex = bitPosition / 8;
      var bitOffset = bitPosition - byteIndex * 8;

      return Bytes.IsBitSet( _data[ byteIndex ], bitOffset );
    }

    int GetShiftedValueInternal( int bitPosition, int shift )
    {
      if ( ! GetValueInternal( bitPosition ) )
      {
        return 0;
      }

      return 1 << shift;
    }

    void EnsureInternalSize( int bitPosition )
    {
      var byteIndex = bitPosition / 8;

      if ( byteIndex >= _data.Length )
      {
        var newData = new byte[ _data.Length + 1 ];
        System.Array.Fill( newData, (byte) 0 );
        System.Array.Copy( _data, 0, newData, 0, _data.Length );

        _data = newData;
      }
    }

    public void Clear()
    {
      _externalPointer = 0;

      for ( int i = 0; i < _internalViewSize; i++ )
      {
        SetValueInternal( i + _internalViewOffset, false );
      }      
    }

    public void ResetPointerAndSize()
    {
      _externalPointer = 0;
      _internalViewSize = 0;
    }

    public void WriteBit( bool value )
    {
      var position = _externalPointer + _internalViewOffset;
      EnsureInternalSize( position );

      SetValueInternal( position, value );
      _externalPointer ++;

      _internalViewSize = Mathf.Max( _externalPointer, _internalViewSize );
    }

    public bool GetBitAt( int bitPosition )
    {
      var internalBitPosition = bitPosition + _internalViewOffset;
      return GetValueInternal( bitPosition );
    }

    public bool ReadBit()
    {
      var bitPosition = _externalPointer + _internalViewOffset;

      if ( bitPosition < 0 || bitPosition >= numBits )
      {
        RJLog.Log( "Can't read: >> ", bitPosition, "in", numBits, "p:", _externalPointer, "o:", _internalViewOffset );
        return false;
      }
      var value = GetValueInternal( bitPosition );
      _externalPointer ++;

      return value;
    }

    public void SetPointerPosition( int position )
    {
      _externalPointer = Mathf.Clamp( position, 0, _internalViewSize );
    }

    public static BitView CreateEmpty()
    {
      var view = new BitView();
      view._data = new byte[]{};
      view._internalViewOffset = 0;
      view._internalViewSize = 0;
      view._externalPointer = 0;

      return view;      
    }
    
    public static BitView From( byte[] data)
    {
      var view = new BitView();

      view._data = data;
      view._internalViewOffset = 0;
      view._internalViewSize = data.Length * 8;
      view._externalPointer = 0;

      return view;      
    }   


    public static BitView From( byte[] data, int bitSize = -1 )
    {
      var view = new BitView();

      view._data = data;
      view._internalViewOffset = 0;
      view._internalViewSize = bitSize == -1 ? data.Length * 8 : bitSize;
      view._externalPointer = 0;

      return view;
    }

    public static BitView PrependBytes( byte[] before, BitView after )
    {
      var data = new byte[ before.Length + after.numBytes ];

      System.Array.Copy( before, 0, data, 0, before.Length );

      var view = BitView.From( data, before.Length * 8 + after.numBits );
      

      if ( after.bitOffset == 0 )
      {
        System.Array.Copy( after._data, 0, data, 0, after.numBytes );   
        view.SetPointer( after.pointerPosition + before.Length * 8 );

        return view;
      }
      
      view.SetPointer( before.Length * 8 );
      
      for ( int i = 0; i < after.numBits; i++ )
      {
        view.WriteBit( after.GetBitAt( i ) );
      }
      
      view.SetPointer( after.pointerPosition + before.Length * 8 );

      return view;
    }

    public static BitView PrependID( int id, BitView after )
    {
      var idView = BitView.CreateEmpty();
      idView.WriteUIntVL8( id );

      return BitView.PrependBytes( idView.GetBytes(), after );

    }

    


  }
}