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

namespace Rokojori
{
  public class Pose
	{
    bool _needsUpdate = true;
    Quaternion _rotation = Quaternion.Identity;
    public Quaternion rotation 
    {
      get => _rotation;
      set
      {
        _rotation = value;
        _needsUpdate = true;
      }
    }

    public Pose Clone()
    {
      var p  =new Pose();
      p._rotation = _rotation;
      p._position = _position;

      return p;
    }

    public void ApplyTwist( float twist )
    {
      rotation = rotation * Math3D.RotateZ( twist );
    }

    Vector3 _position = Vector3.Zero;
    public Vector3 position
    {
      get => _position;
      set
      {
        _position = value;
        _needsUpdate = true;
      }    
      
    }

    public float y 
    {
      get => position.Y;
      set { 
        var p = position;
        p.Y = value;

        position = p;
      } 
    }

    public float x 
    {
      get => position.X;
      set { 
        var p = position;
        p.X = value;

        position = p;
      } 
    }

    public float z 
    {
      get => position.Z;
      set { 
        var p = position;
        p.Z = value;

        position = p;
      } 
    }

    Basis _basis;

    void Update()
    {
      if ( ! _needsUpdate )
      {
        return;
      }
      
      _needsUpdate = false;

      _basis = new Basis( _rotation );
    }

    public Vector3 forward
    {
      get
      {
        Update();

        return -_basis.Z; 
      }
    }

    public Vector3 right
    {
      get
      {
        Update();

        return _basis.X; 
      }
    }


    Vector3? explicitUp = null;
    public Vector3 up
    {
      get
      {
        if ( explicitUp != null )
        {
          return (Vector3) explicitUp;
        }

        Update();

        return _basis.Y; 
      }

      set
      {
        explicitUp = value;
      }
    }

    public Pose()
    {

    }

    public static Pose Create( Vector3 position, Quaternion rotation )
    {
      var p = new Pose();
      p.position = position;
      p.rotation = rotation;
      return p;
    }

    public static Pose From( Pose a )
    {
       var p = new Pose();
      p.position = a.position;
      p.rotation = a.rotation;
      return p;

    }

    public static Pose Lerp( Pose a, Pose b, float weight = 0.5f )
    {
      var p = From( a );
      p.position = p.position.Lerp( b.position, weight );
      p.rotation = p.rotation.Slerp( b.rotation, weight );
      return p;

    }

    public void LookAt( Vector3 forward, Vector3 up )
    {
      rotation = Math3D.LookRotation( forward, up );
    }

    public static Pose From( Node3D node3D )
    {
      var p = new Pose();
      p.position = node3D.GlobalPosition;
      p.rotation = node3D.GlobalQuaternion();
      return p;
    }

    public static Pose InverseFrom( Node3D node3D )
    {
      var p = new Pose();
      p.position = - node3D.GlobalPosition;
      p.rotation = node3D.GlobalQuaternion().Inverse();
      return p;
    }

    
    public Pose ToGlobal( Node3D n )
    {
      var p = new Pose();
      p.rotation = rotation * n.GlobalQuaternion();
      p.position = n.ToGlobal( position );
      return p;
    }


    public Vector3 Apply( Vector3 p )
    {
      p = rotation * p;
      p += position;

      return p;
    }

    public void Set( Node3D node )
    {
      node.GlobalPosition = position;
      node.SetGlobalQuaternion( rotation );
    }

    public override string ToString()
    {
      return "Pose{" + RJLog.Stringify( position ) + RJLog.Stringify( rotation ) + "}";
    } 

    public Vector3 ApplyInverse( Vector3 p )
    {
      p -= position;
      p  = rotation.Inverse() * p;     

      return p;
    }

    public Vector3 OnRightUpCircle( float radians, float size = 1 )
    {
      var c = Mathf.Cos( radians ) * size;
      var s = Mathf.Sin( radians ) * size;

      return s * right + c * up + position;
    }

    public Vector3 ComputeCircleValues( float radians, Vector3 cos, Vector3 sin, float size )
    {
      var c = Mathf.Cos( radians ) * size;
      var s = Mathf.Sin( radians ) * size;

      var cValue = c * cos.X * right +
                   c * cos.Y * up +
                   c * cos.Z * forward;

      var sValue = s * sin.X * right +
                   s * sin.Y * up +
                   s * sin.Z * forward;

      return cValue + sValue + position;
    }

    public static void CopyTo( Node3D source, Node3D target )
    {
      target.GlobalPosition = source.GlobalPosition;
      target.SetGlobalQuaternion( source.GlobalQuaternion() );
    }

    public void Rotate( Quaternion rotation )
    {
      position = rotation * position;
      this.rotation *= rotation;
      this.rotation = this.rotation.Normalized();
    }

    public void RotateAround( Quaternion rotation, Vector3 pivot )
    {
      position -= pivot;
      Rotate( rotation );
      position += pivot;
    }


    public static Pose Merge( List<Pose> poses, List<float> weights = null )
    {
      var position  = Vector3.Zero;
      var up        = Vector3.Zero;
      var forward   = Vector3.Zero;

      if ( weights == null )
      {
        weights = new List<float>( poses.Count );

        for ( int i= 0; i < poses.Count; i++ )
        {
          weights[ i ] = 1f / poses.Count;
        }
      }

      for ( int i = 0; i < poses.Count; i++ )
      {
        position += poses[ i ].position * weights[ i ];
        up       += poses[ i ].up * weights[ i ];
        forward  += poses[ i ].forward * weights[ i ];   
      }

      return Create( position, Math3D.LookRotation( forward, up ) );
    }

  }
}