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

public class PhotonTransformViewPositionControl 
{
    PhotonTransformViewPositionModel m_Model;
    float m_CurrentSpeed;
    double m_LastSerializeTime;
    Vector3 m_SynchronizedSpeed = Vector3.zero;
    float m_SynchronizedTurnSpeed = 0;

    Vector3 m_NetworkPosition;
    Queue<Vector3> m_OldNetworkPositions = new Queue<Vector3>();

    bool m_UpdatedPositionAfterOnSerialize = true;

    public PhotonTransformViewPositionControl( PhotonTransformViewPositionModel model )
    {
        m_Model = model;
    }

    Vector3 GetOldestStoredNetworkPosition()
    {
        Vector3 oldPosition = m_NetworkPosition;

        if( m_OldNetworkPositions.Count > 0 )
        {
            oldPosition = m_OldNetworkPositions.Peek();
        }

        return oldPosition;
    }

    /// <summary>
    /// These values are synchronized to the remote objects if the interpolation mode
    /// or the extrapolation mode SynchronizeValues is used. Your movement script should pass on
    /// the current speed (in units/second) and turning speed (in angles/second) so the remote
    /// object can use them to predict the objects movement.
    /// </summary>
    /// <param name="speed">The current movement vector of the object in units/second.</param>
    /// <param name="turnSpeed">The current turn speed of the object in angles/second.</param>
    public void SetSynchronizedValues( Vector3 speed, float turnSpeed )
    {
        m_SynchronizedSpeed = speed;
        m_SynchronizedTurnSpeed = turnSpeed;
    }

    /// <summary>
    /// Calculates the new position based on the values setup in the inspector
    /// </summary>
    /// <param name="currentPosition">The current position.</param>
    /// <returns>The new position.</returns>
    public Vector3 UpdatePosition( Vector3 currentPosition )
    {
        Vector3 targetPosition = GetNetworkPosition() + GetExtrapolatedPositionOffset();

        switch( m_Model.InterpolateOption )
        {
        case PhotonTransformViewPositionModel.InterpolateOptions.Disabled:
            if( m_UpdatedPositionAfterOnSerialize == false )
            {
                currentPosition = targetPosition;
                m_UpdatedPositionAfterOnSerialize = true;
            }
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.FixedSpeed:
            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * m_Model.InterpolateMoveTowardsSpeed );
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.EstimatedSpeed:
            int positionsCount = Mathf.Min( 1, m_OldNetworkPositions.Count );
            float estimatedSpeed = Vector3.Distance( m_NetworkPosition, GetOldestStoredNetworkPosition() ) / positionsCount;
            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * estimatedSpeed );
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues:
            if( m_SynchronizedSpeed.magnitude == 0 )
            {
                currentPosition = targetPosition;
            }
            else
            {
                currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * m_SynchronizedSpeed.magnitude );
            }
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.Lerp:
            currentPosition = Vector3.Lerp( currentPosition, targetPosition, Time.deltaTime * m_Model.InterpolateLerpSpeed );
            break;
        /*case PhotonTransformViewPositionModel.InterpolateOptions.MoveTowardsComplex:
            float distanceToTarget = Vector3.Distance( currentPosition, targetPosition );
            float targetSpeed = m_Model.InterpolateSpeedCurve.Evaluate( distanceToTarget ) * m_Model.InterpolateMoveTowardsSpeed;

            if( targetSpeed > m_CurrentSpeed )
            {
                m_CurrentSpeed = Mathf.MoveTowards( m_CurrentSpeed, targetSpeed, Time.deltaTime * m_Model.InterpolateMoveTowardsAcceleration );
            }
            else
            {
                m_CurrentSpeed = Mathf.MoveTowards( m_CurrentSpeed, targetSpeed, Time.deltaTime * m_Model.InterpolateMoveTowardsDeceleration );
            }

            //Debug.Log( m_CurrentSpeed + " - " + targetSpeed + " - " + transform.localPosition + " - " + targetPosition );

            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * m_CurrentSpeed );
            break;*/
        }

        if( m_Model.TeleportEnabled == true )
        {
            if( Vector3.Distance( currentPosition, GetNetworkPosition() ) > m_Model.TeleportIfDistanceGreaterThan )
            {
                currentPosition = GetNetworkPosition();
            }
        }

        return currentPosition;
    }

    /// <summary>
    /// Gets the last position that was received through the network
    /// </summary>
    /// <returns></returns>
    public Vector3 GetNetworkPosition()
    {
        return m_NetworkPosition;
    }

    /// <summary>
    /// Calculates an estimated position based on the last synchronized position,
    /// the time when the last position was received and the movement speed of the object
    /// </summary>
    /// <returns>Estimated position of the remote object</returns>
    public Vector3 GetExtrapolatedPositionOffset()
    {
        float timePassed = (float)( PhotonNetwork.time - m_LastSerializeTime );

        if( m_Model.ExtrapolateIncludingRoundTripTime == true )
        {
            timePassed += (float)PhotonNetwork.GetPing() / 1000f;
        }

        Vector3 extrapolatePosition = Vector3.zero;

        switch( m_Model.ExtrapolateOption )
        {
        case PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues:
            Quaternion turnRotation = Quaternion.Euler( 0, m_SynchronizedTurnSpeed * timePassed, 0 );
            extrapolatePosition = turnRotation * ( m_SynchronizedSpeed * timePassed );
            break;
        case PhotonTransformViewPositionModel.ExtrapolateOptions.FixedSpeed:
            Vector3 moveDirection = ( m_NetworkPosition - GetOldestStoredNetworkPosition() ).normalized;

            extrapolatePosition = moveDirection * m_Model.ExtrapolateSpeed * timePassed;
            break;
        case PhotonTransformViewPositionModel.ExtrapolateOptions.EstimateSpeedAndTurn:
            Vector3 moveDelta = ( m_NetworkPosition - GetOldestStoredNetworkPosition() ) * PhotonNetwork.sendRateOnSerialize;
            extrapolatePosition = moveDelta * timePassed;
            break;
        }

        return extrapolatePosition;
    }

    public void OnPhotonSerializeView( Vector3 currentPosition, PhotonStream stream, PhotonMessageInfo info )
    {
        if( m_Model.SynchronizeEnabled == false )
        {
            return;
        }

        if( stream.isWriting == true )
        {
            SerializeData( currentPosition, stream, info );
        }
        else
        {
            DeserializeData( stream, info );
        }

        m_LastSerializeTime = PhotonNetwork.time;
        m_UpdatedPositionAfterOnSerialize = false;
    }

    void SerializeData( Vector3 currentPosition, PhotonStream stream, PhotonMessageInfo info )
    {
        stream.SendNext( currentPosition );

        if( m_Model.ExtrapolateOption == PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues ||
            m_Model.InterpolateOption == PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues )
        {
            stream.SendNext( m_SynchronizedSpeed );
            stream.SendNext( m_SynchronizedTurnSpeed );
        }
    }

    void DeserializeData( PhotonStream stream, PhotonMessageInfo info )
    {
        m_OldNetworkPositions.Enqueue( m_NetworkPosition );

        while( m_OldNetworkPositions.Count > m_Model.ExtrapolateNumberOfStoredPositions )
        {
            m_OldNetworkPositions.Dequeue();
        }

        m_NetworkPosition = (Vector3)stream.ReceiveNext();

        if( m_Model.ExtrapolateOption == PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues ||
            m_Model.InterpolateOption == PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues )
        {
            m_SynchronizedSpeed = (Vector3)stream.ReceiveNext();
            m_SynchronizedTurnSpeed = (float)stream.ReceiveNext();
        }
    }
}
