//#define RemoteAvatarBundles

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

namespace Passer.Humanoid {
    using Pawn;

    /// <summary>
    /// Interface for Humanoid Networking functions
    /// </summary>
    public interface IHumanoidNetworking {
        void Send(bool b);
        void Send(byte b);
        void Send(int x);
        void Send(float f);
        void Send(Vector3 v);
        void Send(Quaternion q);

        bool ReceiveBool();
        byte ReceiveByte();
        int ReceiveInt();
        float ReceiveFloat();
        Vector3 ReceiveVector3();
        Quaternion ReceiveQuaternion();

        float sendRate { get; }
        HumanoidNetworking.DebugLevel debug { get; }
        HumanoidNetworking.Smoothing smoothing { get; }
        bool createLocalRemotes { get; set; }


        bool isLocal { get; }

        ulong nwId { get; }

        bool syncFingerSwing { get; }

        bool syncTracking { get; set; }
        bool fuseTracking { get; }

        List<HumanoidControl> humanoids { get; }

        HumanoidNetworking.HumanoidPose lastHumanoidPose { get; set; }

        ulong GetObjectIdentity(GameObject obj);
        GameObject GetGameObject(ulong objIdentity);

        void InstantiateHumanoid(HumanoidControl humanoid);
        void DestroyHumanoid(HumanoidControl humanoid);

        void UpdateHumanoidPose(HumanoidControl humanoid);

        void Grab(HandTarget handTarget, GameObject obj, bool rangeCheck, HandTarget.GrabType grabType = HandTarget.GrabType.HandGrab);
        void LetGo(HandTarget handTarget);

        void ChangeAvatar(HumanoidControl humanoid, string remoteAvatarName, string possessionLocation = null);

        void SyncTrackingSpace(HumanoidControl humanoid);

        void DebugLog(string s);
        void DebugWarning(string s);
        void DebugError(string s);

        void ReenableNetworkSync(GameObject obj);
        void DisableNetworkSync(GameObject obj);

    }

    /// <summary>
    /// Humanoid Networking
    /// </summary>
    public static class HumanoidNetworking {
        //public enum Smoothing {
        //    None,
        //    Interpolation,
        //    Extrapolation
        //};

        public enum Smoothing {
            None,
            Interpolation,
            Extrapolation
        };

        public enum DebugLevel {
            Debug,
            Info,
            Warning,
            Error,
            None,
        }
        public static DebugLevel debug = DebugLevel.Error;

        public class IMessage
#if hNW_MIRROR
            : Mirror.NetworkMessage
#endif
        {
            public IMessage() { }
            public IMessage(byte[] data) {
                Deserialize(data);
            }

            public virtual byte[] Serialize() { return null; }
            public virtual void Deserialize(byte[] data) { }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    //writer.WriteBytesAndSize(data);
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    //byte[] data = reader.ReadBytesAndSize();
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif
        }

        public static void Connected(HumanoidControl humanoid) {
            OnConnectedToNetwork?.Invoke(humanoid);
        }

        #region Instantiate Humanoid

        public class InstantiateHumanoid : IMessage {
            public ulong nwId;
            public byte humanoidId;
            public string name;
            public string avatarPrefabName;
            public bool physics;
            public string possessionLocation = "";
#if RemoteAvatarBundles
            public int remoteAvatarBundleSize;
            public byte[] remoteAvatarBundle;
#endif

            public InstantiateHumanoid() { }
            public InstantiateHumanoid(HumanoidControl humanoid) {
                nwId = humanoid.nwId;
                humanoidId = (byte)humanoid.humanoidId;

                int nameLength = humanoid.gameObject.name.Length;
                if (nameLength > 9 && humanoid.gameObject.name.Substring(nameLength - 9, 9) == " (Remote)")
                    name = humanoid.gameObject.name.Substring(0, nameLength - 9);
                else
                    name = humanoid.gameObject.name;

                if (humanoid.remoteAvatar == null) {
                    Possessable avatarPossessable = humanoid.avatarRig.GetComponent<Possessable>();
                    if (avatarPossessable != null) {
                        possessionLocation = avatarPossessable.siteLocation;
                        avatarPrefabName = avatarPossessable.assetPath;
                    }
                    else
                        avatarPrefabName = humanoid.avatarRig.name; // .Substring(0, humanoid.avatarRig.name.Length - 7); 
                }
                else {
                    Possessable avatarPossessable = humanoid.remoteAvatar.GetComponent<Possessable>();
                    if (avatarPossessable != null) {
                        possessionLocation = avatarPossessable.siteLocation;
                        avatarPrefabName = avatarPossessable.assetPath;
                    }
                    else
                        avatarPrefabName = humanoid.remoteAvatar.name;
                }

#if RemoteAvatarBundles
                /* Get the remoteAvatarBundle from the streaming assets */
                remoteAvatarBundle = File.ReadAllBytes(Application.streamingAssetsPath + "/RemoteAvatarBundles/" + avatarPrefabName);
                remoteAvatarBundleSize = remoteAvatarBundle.Length;
#endif
                physics = humanoid.physics;
            }
            public InstantiateHumanoid(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);
                bw.Write(name);
                bw.Write(avatarPrefabName);
                bw.Write(physics);
                if (possessionLocation == null)
                    possessionLocation = "";
                bw.Write(possessionLocation);
#if RemoteAvatarBundles
                bw.Write(remoteAvatarBundleSize);
                bw.Write(remoteAvatarBundle);
#endif

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();
                name = br.ReadString();
                avatarPrefabName = br.ReadString();
                physics = br.ReadBoolean();
                possessionLocation = br.ReadString();
                if (possessionLocation == "")
                    possessionLocation = null;
#if RemoteAvatarBundles
                remoteAvatarBundleSize = br.ReadInt32();
                remoteAvatarBundle = br.ReadBytes(remoteAvatarBundleSize);
#endif
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static void ReceiveInstantiate(this IHumanoidNetworking networking, byte[] serializedData) {
            InstantiateHumanoid data = new InstantiateHumanoid(serializedData);

            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking receivingNetworking, InstantiateHumanoid msg) {

            GameObject networkingObj = receivingNetworking.GetGameObject(msg.nwId);
            if (networkingObj == null) {
                if (receivingNetworking.debug <= DebugLevel.Error)
                    receivingNetworking.DebugLog("Could not find Networking for Instantiate Humanoid " + msg.nwId + "/" + msg.humanoidId);
                return;
            }

            IHumanoidNetworking networking = networkingObj.GetComponent<IHumanoidNetworking>();

            if (networking.isLocal && !networking.createLocalRemotes) {
                if (networking.debug <= DebugLevel.Debug)
                    networking.DebugLog("Remote Humanoid " + msg.nwId + "/" + msg.humanoidId + " is local and local remotes are not created");
                return;
            }

            HumanoidControl remoteHumanoid = FindRemoteHumanoid(networking.humanoids, msg.humanoidId);
            if (remoteHumanoid != null) {
                if (networking.debug <= DebugLevel.Warning)
                    networking.DebugLog("Remote Humanoid " + msg.nwId + "/" + msg.humanoidId + " already exists");
                // This remote humanoid already exists
                return;
            }

            if (networking.debug <= DebugLevel.Info)
                networking.DebugLog("Receive Instantiate Humanoid " + msg.nwId + "/" + msg.humanoidId);

            remoteHumanoid = InstantiateRemoteHumanoid(msg.name, Vector3.zero, Quaternion.identity); //, position, rotation);
            remoteHumanoid.nwId = msg.nwId;
            remoteHumanoid.humanoidId = msg.humanoidId;

            if (networking.debug <= DebugLevel.Info)
                networking.DebugLog("Remote Humanoid " + remoteHumanoid.nwId + "/" + remoteHumanoid.humanoidId + " Added");

#if RemoteAvatarBundles
            AssetBundle assetBundle = AssetBundle.LoadFromMemory(data.remoteAvatarBundle);
            GameObject remoteAvatar = assetBundle.LoadAsset<GameObject>(data.avatarPrefabName);
            if (remoteAvatar == null) {
                if (networking.debug <= PawnNetworking.DebugLevel.Error)
                    networking.DebugError("Could not load remote avatar " + data.avatarPrefabName + ". Asset Bundle is not present");
                return;
            }
#else
            if (!string.IsNullOrEmpty(msg.possessionLocation)) {
                Debug.Log("Need to download instantiate avatar possession from " + msg.possessionLocation);
                remoteHumanoid.physics = msg.physics;

                HumanoidPlayer.instance.StartCoroutine(
                    VisitorPossessions.RetrievePossessableAsync(msg.possessionLocation, msg.avatarPrefabName,
                        retrievedAvatar => InstantiateRetrievedAvatar(networking, remoteHumanoid, retrievedAvatar))
                    );

                return;
            }

            GameObject remoteAvatar = (GameObject)Resources.Load(msg.avatarPrefabName);
            if (remoteAvatar == null) {
                if (networking.debug <= DebugLevel.Error)
                    networking.DebugError("Could not load remote avatar " + msg.avatarPrefabName + ". Is it located in a Resources folder?");
                return;
            }
#endif
            remoteHumanoid.physics = msg.physics;
            remoteHumanoid.LocalChangeAvatar(remoteAvatar);

            networking.humanoids.Add(remoteHumanoid);
            if (OnNewRemoteHumanoid != null)
                OnNewRemoteHumanoid(remoteHumanoid);
        }

        private static void InstantiateRetrievedAvatar(IHumanoidNetworking networking, HumanoidControl remoteHumanoid, GameObject retrievedAvatar) {
            if (retrievedAvatar == null) {
                if (networking.debug <= DebugLevel.Error)
                    Debug.LogError("Could not retrieve  avatar.");
                //return;
            }
            else {
                if (networking.debug <= DebugLevel.Info)
                    networking.DebugLog("Receive Change Possessesable Avatar " + retrievedAvatar);

                remoteHumanoid.LocalChangeAvatar(retrievedAvatar);
            }
            networking.humanoids.Add(remoteHumanoid);
            if (OnNewRemoteHumanoid != null)
                OnNewRemoteHumanoid(remoteHumanoid);

        }


        public static HumanoidControl FindRemoteHumanoid(List<HumanoidControl> humanoids, ulong nwId, int humanoidId) {
            foreach (HumanoidControl humanoid in humanoids) {
                if (humanoid.isRemote && humanoid.nwId == nwId && humanoid.humanoidId == humanoidId)
                    return humanoid;
            }
            return null;
        }

        private static HumanoidControl InstantiateRemoteHumanoid(string name, Vector3 position, Quaternion rotation) {
            GameObject remoteHumanoidPrefab = (GameObject)Resources.Load("RemoteHumanoid");

            GameObject remoteHumanoidObj = UnityEngine.Object.Instantiate(remoteHumanoidPrefab, position, rotation);
            remoteHumanoidObj.name = name + " (Remote)";

            HumanoidControl remoteHumanoid = remoteHumanoidObj.GetComponent<HumanoidControl>();
            remoteHumanoid.isRemote = true;

            return remoteHumanoid;
        }

        public delegate void ConnectedToNetwork(HumanoidControl humanoid);
        public static event ConnectedToNetwork OnConnectedToNetwork;

        public delegate void NewRemoteHumanoidArgs(HumanoidControl humanoid);
        public static event NewRemoteHumanoidArgs OnNewRemoteHumanoid;

        #endregion

        #region Destroy Humanoid

        public class DestroyHumanoid : IMessage {
            public ulong nwId;
            public byte humanoidId;

            public DestroyHumanoid() { }
            public DestroyHumanoid(HumanoidControl humanoid) {
                nwId = humanoid.nwId;
                humanoidId = (byte)humanoid.humanoidId;
            }
            public DestroyHumanoid(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static void ReceiveDestroy(this IHumanoidNetworking networking, byte[] serializedData) {
            DestroyHumanoid data = new DestroyHumanoid(serializedData);
            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking networking, DestroyHumanoid msg) {
            if (networking.isLocal && !networking.createLocalRemotes)
                return;

            HumanoidControl remoteHumanoid = FindRemoteHumanoid(networking.humanoids, msg.humanoidId);
            if (remoteHumanoid == null) {
                // Unknown remote humanoid
                return;
            }

            if (remoteHumanoid.gameObject != null)
                UnityEngine.Object.Destroy(remoteHumanoid.gameObject);
        }

        #endregion

        #region Pose

        [Serializable]
        public struct Vector3S {
            public float x;
            public float y;
            public float z;

            public Vector3S(Vector3 v) {
                x = v.x;
                y = v.y;
                z = v.z;
            }
            public Vector3S(Quaternion q) {
                Vector3 euler = q.eulerAngles;
                x = euler.x;
                y = euler.y;
                z = euler.z;
            }

            public Vector3 vector3 {
                get { return new Vector3(x, y, z); }
            }
            public Quaternion quaternion {
                get { return Quaternion.Euler(x, y, z); }
            }

            public void Write(BinaryWriter bw) {
                bw.Write(x);
                bw.Write(y);
                bw.Write(z);
            }

            public static Vector3S Read(BinaryReader br) {
                float x = br.ReadSingle();
                float y = br.ReadSingle();
                float z = br.ReadSingle();
                Vector3S v = new Vector3S() { x = x, y = y, z = z };
                return v;
            }
        }

        // Rotations can be sent much more efficient: 3 * 16bit (angles) or 4 * 16bit (quatornions)

        [Serializable]
        public class HumanoidAnimatorParameters {
            public class AnimatorParameter {
                public AnimatorControllerParameterType type;
                public bool boolValue;
                public float floatValue;
                public int intValue;
            }

            public AnimatorParameter[] parameters;
            public HumanoidAnimatorParameters() {
                parameters = new AnimatorParameter[0];
            }

            public HumanoidAnimatorParameters(HumanoidControl humanoid) {
                if (humanoid.targetsRig.runtimeAnimatorController == null) {
                    parameters = new AnimatorParameter[0];
                    return;
                }

                parameters = new AnimatorParameter[humanoid.targetsRig.parameterCount];

                for (int i = 0; i < humanoid.targetsRig.parameterCount; i++) {
                    AnimatorControllerParameter parameter = humanoid.targetsRig.parameters[i];
                    parameters[i] = new AnimatorParameter() {
                        type = parameter.type
                    };
                    switch (parameters[i].type) {
                        case AnimatorControllerParameterType.Bool:
                            parameters[i].boolValue = humanoid.targetsRig.GetBool(parameter.name);
                            break;
                        case AnimatorControllerParameterType.Float:
                            parameters[i].floatValue = humanoid.targetsRig.GetFloat(parameter.name);
                            break;
                        case AnimatorControllerParameterType.Int:
                            parameters[i].intValue = humanoid.targetsRig.GetInteger(parameter.name);
                            break;
                        case AnimatorControllerParameterType.Trigger:
                            parameters[i].boolValue = humanoid.targetsRig.GetBool(parameter.name);
                            break;
                    }
                }
            }

            public virtual void Write(BinaryWriter bw) {
                bw.Write(parameters.Length);
                for (int i = 0; i < parameters.Length; i++) {
                    bw.Write((byte)parameters[i].type);
                    switch (parameters[i].type) {
                        case AnimatorControllerParameterType.Bool:
                            bw.Write(parameters[i].boolValue);
                            break;
                        case AnimatorControllerParameterType.Float:
                            bw.Write(parameters[i].floatValue);
                            break;
                        case AnimatorControllerParameterType.Int:
                            bw.Write(parameters[i].intValue);
                            break;
                        case AnimatorControllerParameterType.Trigger:
                            bw.Write(parameters[i].boolValue);
                            break;
                    }
                }
            }
            public static HumanoidAnimatorParameters Read(BinaryReader br) {
                int parameterCount = br.ReadInt32();
                HumanoidAnimatorParameters animatorParameters = new HumanoidAnimatorParameters() {
                    parameters = new AnimatorParameter[parameterCount]
                };

                for (int i = 0; i < parameterCount; i++) {
                    AnimatorControllerParameterType parameterType = (AnimatorControllerParameterType)br.ReadByte();
                    animatorParameters.parameters[i] = new AnimatorParameter() {
                        type = parameterType
                    };
                    switch (parameterType) {
                        case AnimatorControllerParameterType.Bool:
                            animatorParameters.parameters[i].boolValue = br.ReadBoolean();
                            break;
                        case AnimatorControllerParameterType.Float:
                            animatorParameters.parameters[i].floatValue = br.ReadSingle();
                            break;
                        case AnimatorControllerParameterType.Int:
                            animatorParameters.parameters[i].intValue = br.ReadInt32();
                            break;
                        case AnimatorControllerParameterType.Trigger:
                            animatorParameters.parameters[i].boolValue = br.ReadBoolean();
                            break;
                    }
                }
                return animatorParameters;
            }
        }

        [Serializable]
        public class HumanoidHandPose : HumanoidTargetPose {
            public float thumbCurl;
            public float indexCurl;
            public float middleCurl;
            public float ringCurl;
            public float littleCurl;

            public bool syncSwing;

            public float thumbSwing;
            public float indexSwing;
            public float middleSwing;
            public float ringSwing;
            public float littleSwing;

            public HumanoidHandPose() : base(Tracking.Bone.None) { }

            public HumanoidHandPose(Tracking.Bone boneId) : base(boneId) { }

            public HumanoidHandPose(HandTarget handTarget, bool syncSwing) : base(handTarget) {
                this.syncSwing = syncSwing;

                FingersTarget.UpdateCurlValues(handTarget);
                //FingersTarget.UpdateCurlValues(handTarget);
                thumbCurl = handTarget.fingers.thumb.curl;
                indexCurl = handTarget.fingers.index.curl;
                middleCurl = handTarget.fingers.middle.curl;
                ringCurl = handTarget.fingers.ring.curl;
                littleCurl = handTarget.fingers.little.curl;

                thumbSwing = handTarget.fingers.thumb.swing;
                indexSwing = handTarget.fingers.index.swing;
                middleSwing = handTarget.fingers.middle.swing;
                ringSwing = handTarget.fingers.ring.swing;
                littleSwing = handTarget.fingers.little.swing;
            }

            public override void Write(BinaryWriter bw) {
                base.Write(bw);

                if (boneId != Tracking.Bone.None) {
                    bw.Write(thumbCurl);
                    bw.Write(indexCurl);
                    bw.Write(middleCurl);
                    bw.Write(ringCurl);
                    bw.Write(littleCurl);
                }
            }
            public static new HumanoidHandPose Read(BinaryReader br) {
                Tracking.Bone boneId = (Tracking.Bone)br.ReadSByte();
                if (boneId == Tracking.Bone.None)
                    return new HumanoidHandPose();
                else
                    return new HumanoidHandPose(boneId) {
                        localPosition = Vector3S.Read(br),
                        positionConfidenceByte = br.ReadByte(),
                        rotation = Vector3S.Read(br),
                        rotationConfidenceByte = br.ReadByte(),

                        thumbCurl = br.ReadSingle(),
                        indexCurl = br.ReadSingle(),
                        middleCurl = br.ReadSingle(),
                        ringCurl = br.ReadSingle(),
                        littleCurl = br.ReadSingle(),
                    };

            }
        }

        [Serializable]
        public class HumanoidTargetPose {
            public Tracking.Bone boneId;

            public Vector3S localPosition;
            protected byte positionConfidenceByte;
            public float positionConfidence {
                get { return ToFloat(positionConfidenceByte); }
            }
            public Vector3S rotation;
            protected byte rotationConfidenceByte;
            public float rotationConfidence {
                get { return ToFloat(rotationConfidenceByte); }
            }

            public float ToFloat(byte confidenceByte) {
                float value = Convert.ToSingle(confidenceByte) / 255;
                return value;
            }

            public HumanoidTargetPose() {
                this.boneId = Tracking.Bone.None;

                localPosition = new Vector3S();
                rotation = new Vector3S();
            }

            public HumanoidTargetPose(Tracking.Bone boneId) {
                this.boneId = boneId;

                localPosition = new Vector3S();
                rotation = new Vector3S();
            }
            public HumanoidTargetPose(HumanoidTarget target) {
                boneId = GetBoneId(target);
                // Head bone is always synced
                if (boneId != Tracking.Bone.Head && !HumanoidPose.TargetActive(target))
                    boneId = Tracking.Bone.None;

                Vector3 localPosition = target.humanoid.transform.InverseTransformPoint(target.main.target.transform.position);
                this.localPosition = new Vector3S(localPosition);
                positionConfidenceByte = Convert.ToByte(target.main.target.confidence.position * 255);

                rotation = new Vector3S(target.main.target.transform.rotation);
                rotationConfidenceByte = Convert.ToByte(target.main.target.confidence.rotation * 255);
            }

            private Tracking.Bone GetBoneId(HumanoidTarget target) {
                if (target == target.humanoid.hipsTarget)
                    return Tracking.Bone.Hips;
                else if (target == target.humanoid.headTarget)
                    return Tracking.Bone.Head;
                else if (target == target.humanoid.leftHandTarget)
                    return Tracking.Bone.LeftHand;
                else if (target == target.humanoid.rightHandTarget)
                    return Tracking.Bone.RightHand;
                else if (target == target.humanoid.leftFootTarget)
                    return Tracking.Bone.LeftFoot;
                else if (target == target.humanoid.rightFootTarget)
                    return Tracking.Bone.RightFoot;
                else
                    return Tracking.Bone.None;
            }

            public virtual void Write(BinaryWriter bw) {
                bw.Write((sbyte)boneId);
                if (boneId != Tracking.Bone.None) {
                    localPosition.Write(bw);
                    bw.Write(positionConfidenceByte);
                    rotation.Write(bw);
                    bw.Write(rotationConfidenceByte);
                }
            }

            public static HumanoidTargetPose Read(BinaryReader br) {
                Tracking.Bone boneId = (Tracking.Bone)br.ReadSByte();
                if (boneId == Tracking.Bone.None)
                    return new HumanoidTargetPose();
                else {
                    return new HumanoidTargetPose(boneId) {
                        localPosition = Vector3S.Read(br),
                        positionConfidenceByte = br.ReadByte(),
                        rotation = Vector3S.Read(br),
                        rotationConfidenceByte = br.ReadByte()
                    };
                }
            }
        }

        [Serializable]
        public class HumanoidPose : IMessage {
            public ulong nwId;
            public byte humanoidId;

            public float poseTime;
            public float receiveTime;

            public Vector3S position;
            public Vector3S rotation;

            public HumanoidAnimatorParameters animatorParameters;

            //public byte targetMask;
            public HumanoidTargetPose hips;
            public HumanoidTargetPose head;
            public HumanoidHandPose leftHand;
            public HumanoidHandPose rightHand;
            public HumanoidTargetPose leftFoot;
            public HumanoidTargetPose rightFoot;

            public bool syncFace;
#if hFACE
            public HumanoidFacePose faceTarget;
#endif

            public HumanoidPose() {
                animatorParameters = new HumanoidAnimatorParameters();

                hips = new HumanoidTargetPose(Tracking.Bone.None);
                head = new HumanoidTargetPose(Tracking.Bone.None);
                leftHand = new HumanoidHandPose(Tracking.Bone.None);
                rightHand = new HumanoidHandPose(Tracking.Bone.None);
                leftFoot = new HumanoidTargetPose(Tracking.Bone.None);
                rightFoot = new HumanoidTargetPose(Tracking.Bone.None);
#if hFACE
                faceTarget = new HumanoidFacePose();
#endif
            }
            public HumanoidPose(HumanoidControl humanoid, float poseTime,
                bool syncFingerSwing = false, bool syncFace = false) {

                this.syncFace = syncFace;

                nwId = humanoid.nwId;
                humanoidId = (byte)humanoid.humanoidId;

                this.poseTime = poseTime;

                position = new Vector3S(humanoid.transform.position);
                rotation = new Vector3S(humanoid.transform.rotation);

                animatorParameters = new HumanoidAnimatorParameters(humanoid);

                hips = new HumanoidTargetPose(humanoid.hipsTarget);
                head = new HumanoidTargetPose(humanoid.headTarget);
                leftHand = new HumanoidHandPose(humanoid.leftHandTarget, syncFingerSwing);
                rightHand = new HumanoidHandPose(humanoid.rightHandTarget, syncFingerSwing);
                leftFoot = new HumanoidTargetPose(humanoid.leftFootTarget);
                rightFoot = new HumanoidTargetPose(humanoid.rightFootTarget);
#if hFACE
                faceTarget = new HumanoidFacePose(humanoid.headTarget.face);
#endif
            }
            public HumanoidPose(byte[] data) : base(data) { }


            public static bool TargetActive(HumanoidTarget target) {
                //bool active = target.main != null &&
                //    (target.main.target.confidence.position > 0.2F || target.main.target.confidence.rotation > 0.2F);

                //                if (!target.animator.enabled)
                return true;

                //                return active;
            }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);

                bw.Write(poseTime);

                position.Write(bw);
                rotation.Write(bw);

                animatorParameters.Write(bw);

                hips.Write(bw);
                head.Write(bw);
                leftHand.Write(bw);
                rightHand.Write(bw);
                leftFoot.Write(bw);
                rightFoot.Write(bw);
#if hFACE
                if (syncFace)
                    faceTarget.Write(bw);
#endif

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();

                poseTime = br.ReadSingle();

                position = Vector3S.Read(br);
                rotation = Vector3S.Read(br);

                animatorParameters = HumanoidAnimatorParameters.Read(br);

                hips = HumanoidTargetPose.Read(br);
                head = HumanoidTargetPose.Read(br);
                leftHand = HumanoidHandPose.Read(br);
                rightHand = HumanoidHandPose.Read(br);
                leftFoot = HumanoidTargetPose.Read(br);
                rightFoot = HumanoidTargetPose.Read(br);
#if hFACE
                if (syncFace)
                    faceTarget = HumanoidFacePose.Read(br);
#endif
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif

            public HumanoidTargetPose GetTargetPose(Tracking.Bone boneId) {
                switch (boneId) {
                    case Tracking.Bone.Hips:
                        return hips;
                    case Tracking.Bone.Head:
                        return head;
                    case Tracking.Bone.LeftHand:
                        return leftHand;
                    case Tracking.Bone.RightHand:
                        return rightHand;
                    case Tracking.Bone.LeftFoot:
                        return leftFoot;
                    case Tracking.Bone.RightFoot:
                        return rightFoot;
                    default:
                        return null;
                }
            }
        }
#if hFACE
        [Serializable]
        public class HumanoidFacePose {
            public short targetMask;

            public sbyte leftBrowOuterRaise;
            public sbyte leftBrowInnerRaise;

            public sbyte rightBrowOuterRaise;
            public sbyte rightBrowInnerRaise;

            public sbyte leftEyeClosed;
            public sbyte rightEyeClosed;

            public sbyte mouthLeftRaise;
            public sbyte mouthRightRaise;
            public sbyte mouthLeftStretch;
            public sbyte mouthRightStretch;
            public sbyte mouthShiftRight;

            public sbyte jawOpen;
            public sbyte jawShiftRight;

            public HumanoidFacePose() { }
            public HumanoidFacePose(FaceTarget face) {
                leftBrowOuterRaise = (sbyte)(face.leftBrow.outerRaise * 127);
                leftBrowInnerRaise = (sbyte)(face.leftBrow.innerRaise * 127);
                rightBrowOuterRaise = (sbyte)(face.rightBrow.outerRaise * 127);
                rightBrowInnerRaise = (sbyte)(face.rightBrow.innerRaise * 127);
                leftEyeClosed = (sbyte)(face.leftEye.closed * 127);
                rightEyeClosed = (sbyte)(face.rightEye.closed * 127);
                mouthLeftRaise = (sbyte)(face.mouth.leftRaise * 127);
                mouthRightRaise = (sbyte)(face.mouth.rightRaise * 127);
                mouthLeftStretch = (sbyte)(face.mouth.leftStretch * 127);
                mouthRightStretch = (sbyte)(face.mouth.rightStretch * 127);
                mouthShiftRight = (sbyte)(face.mouth.shiftRight * 127);
                jawOpen = (sbyte)(face.jaw.open * 127);
                jawShiftRight = (sbyte)(face.jaw.shiftRight * 127);
            }

            public void Write(BinaryWriter bw) {
                bw.Write(leftBrowOuterRaise);
                bw.Write(leftBrowInnerRaise);
                bw.Write(rightBrowOuterRaise);
                bw.Write(rightBrowInnerRaise);
                bw.Write(leftEyeClosed);
                bw.Write(rightEyeClosed);
                bw.Write(mouthLeftRaise);
                bw.Write(mouthRightRaise);
                bw.Write(mouthLeftStretch);
                bw.Write(mouthRightStretch);
                bw.Write(mouthShiftRight);
                bw.Write(jawOpen);
                bw.Write(jawShiftRight);
            }

            public static HumanoidFacePose Read(BinaryReader br) {
                HumanoidFacePose pose = new HumanoidFacePose();
                pose.leftBrowOuterRaise = br.ReadSByte();
                pose.leftBrowInnerRaise = br.ReadSByte();
                pose.rightBrowOuterRaise = br.ReadSByte();
                pose.rightBrowInnerRaise = br.ReadSByte();
                pose.leftEyeClosed = br.ReadSByte();
                pose.rightEyeClosed = br.ReadSByte();
                pose.mouthLeftRaise = br.ReadSByte();
                pose.mouthRightRaise = br.ReadSByte();
                pose.mouthLeftStretch = br.ReadSByte();
                pose.mouthRightStretch = br.ReadSByte();
                pose.mouthShiftRight = br.ReadSByte();
                pose.jawOpen = br.ReadSByte();
                pose.jawShiftRight = br.ReadSByte();
                return pose;
            }
        }
#endif
        public static void ReceiveHumanoidPose(this IHumanoidNetworking networking, byte[] data) {
            if (data == null)
                return;


            HumanoidPose humanoidPose = new HumanoidPose(data);
            Receive(networking, humanoidPose);
        }

        public static void Receive(this IHumanoidNetworking receivingNetworking, HumanoidPose humanoidPose) {
            //DebugHumanoidPose(humanoidPose);

            humanoidPose.receiveTime = Time.time;

            IHumanoidNetworking networking = GetHumanoidNetworking(receivingNetworking, humanoidPose.nwId);
            if (networking == null) {
                if (receivingNetworking.debug <= DebugLevel.Error)
                    receivingNetworking.DebugLog("Could not find Networking for Humanoid Pose " + humanoidPose.nwId + "/" + humanoidPose.humanoidId);
                return;
            }

            if (networking.isLocal && !networking.createLocalRemotes)
                return;

            if (networking.humanoids == null) {
                networking.DebugLog("No humanoids found");
                return;
            }

            HumanoidControl remoteHumanoid = FindRemoteHumanoid(networking.humanoids, humanoidPose.humanoidId);
            if (remoteHumanoid == null) {
                if (networking.debug <= DebugLevel.Warning)
                    networking.DebugWarning("Could not find humanoid: " + humanoidPose.nwId + "/" + humanoidPose.humanoidId);
                return;
            }

            if (networking.debug <= DebugLevel.Debug)
                networking.DebugLog("Receive HumanoidPose " + humanoidPose.nwId + "/" + humanoidPose.humanoidId);

            ReceiveHumanoidPose(remoteHumanoid, humanoidPose, networking.lastHumanoidPose, networking.smoothing);

            if (networking.createLocalRemotes)
                remoteHumanoid.transform.Translate(0, 0, 1, Space.Self);

            networking.lastHumanoidPose = humanoidPose;
        }

        private static void DebugHumanoidPose(HumanoidPose pose) {
            string s = "";
            if (pose.hips.boneId == Tracking.Bone.Hips)
                s += " HI_" + pose.hips.positionConfidence + "/" + pose.hips.rotationConfidence;
            if (pose.head.boneId == Tracking.Bone.Head)
                s += " HE_" + pose.head.positionConfidence + "/" + pose.head.rotationConfidence;
            if (pose.leftHand.boneId == Tracking.Bone.LeftHand)
                s += " LH_" + pose.leftHand.positionConfidence + "/" + pose.leftHand.rotationConfidence;
            if (pose.rightHand.boneId == Tracking.Bone.RightHand)
                s += " RH_" + pose.rightHand.positionConfidence + "/" + pose.rightHand.rotationConfidence;
            if (pose.leftFoot.boneId == Tracking.Bone.LeftFoot)
                s += " LH_" + pose.leftFoot.positionConfidence + "/" + pose.leftFoot.rotationConfidence;
            if (pose.rightFoot.boneId == Tracking.Bone.RightFoot)
                s += " RH_" + pose.rightFoot.positionConfidence + "/" + pose.rightFoot.rotationConfidence;
            Debug.Log(s);
        }

        public static void ReceiveHumanoidPose(
            HumanoidControl remoteHumanoid,
            HumanoidPose humanoidPose, HumanoidPose lastHumanoidPose,
            HumanoidNetworking.Smoothing smoothing) {

            remoteHumanoid.transform.position = humanoidPose.position.vector3;
            remoteHumanoid.transform.rotation = humanoidPose.rotation.quaternion;

            if (lastHumanoidPose != null)
                CalculateVelocity(remoteHumanoid, humanoidPose.position.vector3, lastHumanoidPose.position.vector3,
                    humanoidPose.poseTime, humanoidPose.receiveTime, lastHumanoidPose.poseTime);

            ReceiveAnimationParameters(remoteHumanoid, humanoidPose);

            remoteHumanoid.headTarget.animator.enabled = true;
            remoteHumanoid.leftHandTarget.animator.enabled = true;
            remoteHumanoid.rightHandTarget.animator.enabled = true;
            remoteHumanoid.hipsTarget.animator.enabled = true;
            remoteHumanoid.leftFootTarget.animator.enabled = true;
            remoteHumanoid.rightFootTarget.animator.enabled = true;

            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.hips, lastHumanoidPose, smoothing);
            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.head, lastHumanoidPose, smoothing);
            HeadAnimator.UpdateNeckTargetFromHead(remoteHumanoid.headTarget);
            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.leftHand, lastHumanoidPose, smoothing);
            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.rightHand, lastHumanoidPose, smoothing);
            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.leftFoot, lastHumanoidPose, smoothing);
            ReceiveTargetPose(remoteHumanoid, humanoidPose, humanoidPose.rightFoot, lastHumanoidPose, smoothing);

            remoteHumanoid.CopyRigToTargets();
        }

        private static bool ReceiveAnimationParameters(HumanoidControl humanoid, HumanoidPose pose) {
            if (humanoid.targetsRig.runtimeAnimatorController == null) {
                if (pose.animatorParameters.parameters.Length > 0)
                    Debug.LogWarning("Animation Controller is missing on remote Humanoid.\nAnitaion will not be synchronized");
                return false;
            }

            for (int i = 0; i < humanoid.targetsRig.parameterCount; i++) {
                AnimatorControllerParameter parameter = humanoid.targetsRig.parameters[i];
                switch (parameter.type) {
                    case AnimatorControllerParameterType.Bool:
                        humanoid.targetsRig.SetBool(parameter.name, pose.animatorParameters.parameters[i].boolValue);
                        break;
                    case AnimatorControllerParameterType.Float:
                        humanoid.targetsRig.SetFloat(parameter.name, pose.animatorParameters.parameters[i].floatValue);
                        break;
                    case AnimatorControllerParameterType.Int:
                        humanoid.targetsRig.SetInteger(parameter.name, pose.animatorParameters.parameters[i].intValue);
                        break;
                    case AnimatorControllerParameterType.Trigger:
                        humanoid.targetsRig.SetBool(parameter.name, pose.animatorParameters.parameters[i].boolValue);
                        break;
                }
            }
            return true;
        }

        private static void ReceiveTargetPose(
            HumanoidControl humanoid,
            HumanoidPose humanoidPose,
            HumanoidTargetPose targetPose,
            HumanoidPose lastPose,
            Smoothing smoothing) {

            if (targetPose == null) {
                return;
            }

            HumanoidTarget target = GetTarget(humanoid, targetPose.boneId);
            if (target != null) {
                if (targetPose.boneId == Tracking.Bone.Head) {
                    HeadAnimator headAnimator = target.animator as HeadAnimator;
                    if (headAnimator != null)
                        headAnimator.headAnimation = false;
                }
                else {
                    target.animator.enabled = false;
                    //target.EnableAnimator(false);
                }
            }

            if (targetPose.boneId == Tracking.Bone.LeftHand || targetPose.boneId == Tracking.Bone.RightHand)
                ReceiveHand((HandTarget)target, (HumanoidHandPose)targetPose);
            if (lastPose != null) {
                HumanoidTargetPose lastTargetPose = lastPose.GetTargetPose(targetPose.boneId);
                if (lastTargetPose != null) {
                    ReceiveTarget(target, targetPose, lastTargetPose, humanoidPose.poseTime, humanoidPose.receiveTime, lastPose.poseTime, lastPose.receiveTime, smoothing);
                    return;
                }
            }
            ReceiveTarget(target, targetPose);
        }

        private static HumanoidTarget GetTarget(HumanoidControl humanoid, Tracking.Bone boneId) {
            switch (boneId) {
                case Tracking.Bone.Hips:
                    return humanoid.hipsTarget;
                case Tracking.Bone.Head:
                    return humanoid.headTarget;
                case Tracking.Bone.LeftHand:
                    return humanoid.leftHandTarget;
                case Tracking.Bone.RightHand:
                    return humanoid.rightHandTarget;
                case Tracking.Bone.LeftFoot:
                    return humanoid.leftFootTarget;
                case Tracking.Bone.RightFoot:
                    return humanoid.rightFootTarget;
                default:
                    return null;
            }
        }


        #region Receive Target

        private static void ReceiveTarget(HumanoidTarget target, HumanoidTargetPose targetPose) {
            if (target == null || targetPose == null)
                return;

            HumanoidTarget.TargetTransform targetTransform = target.main.target;
            if (targetPose.positionConfidence >= targetTransform.confidence.position) {
                targetTransform.transform.position = target.humanoid.transform.TransformPoint(targetPose.localPosition.vector3);
                targetTransform.confidence.position = targetPose.positionConfidence;
            }
            if (targetPose.rotationConfidence >= targetTransform.confidence.rotation) {
                targetTransform.transform.rotation = targetPose.rotation.quaternion;
                targetTransform.confidence.rotation = targetPose.rotationConfidence;
            }
        }

        private static void ReceiveTarget(
            HumanoidTarget target,
            HumanoidTargetPose targetPose, HumanoidTargetPose lastTargetPose,
            float poseTime, float receiveTime, float lastPoseTime, float lastReceiveTime,
            Smoothing smoothing) {

            if (target == null || targetPose == null)
                return;

            HumanoidTarget.TargetTransform targetTransform = target.main.target;
            if (targetPose.positionConfidence >= targetTransform.confidence.position) {
                // This code results in wrong hand positions:
                //Vector3 oldLocalPosition = target.humanoid.transform.InverseTransformPoint(target.transform.position);
                //targetTransform.transform.position =
                //    CorrectedLocalPosition(oldLocalPosition, target.humanoid.transform, targetPose.localPosition.vector3, lastTargetPose.localPosition.vector3, poseTime, receiveTime, lastPoseTime, lastReceiveTime, smoothing);
                targetTransform.transform.position = target.humanoid.transform.TransformPoint(targetPose.localPosition.vector3);
                targetTransform.confidence.position = targetPose.positionConfidence;
            }
            if (targetPose.rotationConfidence >= targetTransform.confidence.rotation) {
                targetTransform.transform.rotation = targetPose.rotation.quaternion;
                targetTransform.confidence.rotation = targetPose.rotationConfidence;
            }
        }

        ///<summary>Update the transform position with correction for transport jitter</summary>
        private static Vector3 CorrectedPosition(
            Transform targetTransform,
            Vector3 receivedPosition, Vector3 lastReceivedPosition,
            float poseTime, float receiveTime,
            float lastPoseTime, float lastReceiveTime,
            Smoothing smoothing) {

            Vector3 newTargetPosition = receivedPosition;

            float deltaPoseTime = poseTime - lastPoseTime;
            float deltaReceiveTime = receiveTime - lastReceiveTime;

            if (deltaPoseTime > 0 && deltaReceiveTime > 0 && lastReceivedPosition != Vector3.zero) {
                Vector3 receivedTranslation = receivedPosition - lastReceivedPosition;
                Vector3 translation = receivedTranslation * (deltaReceiveTime / deltaPoseTime);

                if (smoothing == Smoothing.None)
                    newTargetPosition = targetTransform.position + translation;
                else if (smoothing == Smoothing.Extrapolation)
                    newTargetPosition = receivedPosition;
                else if (smoothing == Smoothing.Interpolation)
                    newTargetPosition = lastReceivedPosition;
            }

            lastReceiveTime = receiveTime;
            lastPoseTime = poseTime;

            return newTargetPosition;
        }

        ///<summary>Update the transform position with correction for transport jitter</summary>
        private static Vector3 CorrectedLocalPosition(
            Vector3 oldLocalPosition, Transform parentTransform,
            Vector3 receivedPosition, Vector3 lastReceivedPosition,
            float poseTime, float receiveTime,
            float lastPoseTime, float lastReceiveTime,
            Smoothing smoothing) {

            Vector3 newLocalPosition = receivedPosition;

            float deltaPoseTime = poseTime - lastPoseTime;
            float deltaReceiveTime = receiveTime - lastReceiveTime;

            if (deltaPoseTime > 0 && deltaReceiveTime > 0 && lastReceivedPosition != Vector3.zero) {
                Vector3 receivedTranslation = receivedPosition - lastReceivedPosition;
                Vector3 translation = receivedTranslation * (deltaReceiveTime / deltaPoseTime);
                if (smoothing != Smoothing.Interpolation)
                    newLocalPosition = oldLocalPosition + translation;
            }

            lastReceiveTime = receiveTime;
            lastPoseTime = poseTime;

            return parentTransform.TransformPoint(newLocalPosition);
        }

        private static void CalculateVelocity(
            HumanoidControl humanoid,
            Vector3 receivedPosition, Vector3 lastReceivedPosition,
            float poseTime, float receiveTime, float lastPoseTime) {

            float deltaTime = poseTime - lastPoseTime;
            if (deltaTime > 0) {
                Vector3 translation = receivedPosition - lastReceivedPosition;
                humanoid.velocity = translation / deltaTime;
            }
        }

        #endregion

        private static void ReceiveHand(HandTarget handTarget, HumanoidHandPose handPose) {
            if (handPose == null)
                return;

            FingersTarget fingersTarget = handTarget.fingers;

            fingersTarget.thumb.curl = handPose.thumbCurl;
            fingersTarget.index.curl = handPose.indexCurl;
            fingersTarget.middle.curl = handPose.middleCurl;
            fingersTarget.ring.curl = handPose.ringCurl;
            fingersTarget.little.curl = handPose.littleCurl;

            if (handPose.syncSwing) {
                fingersTarget.thumb.swing = handPose.thumbSwing;
                fingersTarget.index.swing = handPose.indexSwing;
                fingersTarget.middle.swing = handPose.middleSwing;
                fingersTarget.ring.swing = handPose.ringSwing;
                fingersTarget.little.swing = handPose.littleSwing;
            }
        }

#if hFACE
        private static void ReceiveFace(FaceTarget faceTarget, HumanoidFacePose facePose) {
            if (facePose == null)
                return;

            faceTarget.leftBrow.outerRaise = ((float)facePose.leftBrowOuterRaise) / 127;
            faceTarget.leftBrow.innerRaise = ((float)facePose.leftBrowInnerRaise) / 127;

            faceTarget.rightBrow.outerRaise = ((float)facePose.rightBrowOuterRaise) / 127;
            faceTarget.rightBrow.innerRaise = ((float)facePose.rightBrowInnerRaise) / 127;

            faceTarget.leftEye.closed = ((float)facePose.leftEyeClosed) / 127;
            faceTarget.rightEye.closed = ((float)facePose.rightEyeClosed) / 127;

            faceTarget.mouth.leftRaise = ((float)facePose.mouthLeftRaise) / 127;
            faceTarget.mouth.rightRaise = ((float)facePose.mouthRightRaise) / 127;
            faceTarget.mouth.leftStretch = ((float)facePose.mouthLeftStretch) / 127;
            faceTarget.mouth.rightStretch = ((float)facePose.mouthRightStretch) / 127;
            faceTarget.mouth.shiftRight = ((float)facePose.mouthShiftRight) / 127;

            faceTarget.jaw.open = ((float)facePose.jawOpen) / 127;
            faceTarget.jaw.shiftRight = ((float)facePose.jawShiftRight) / 127;

            faceTarget.UpdateMorphTargets();
        }
#endif

        public static bool IsTargetActive(byte targetMask, HumanoidControl.TargetId targetIndex) {
            int bitset = targetMask & (byte)(1 << ((int)targetIndex + 1));
            return (bitset != 0);
        }

        #endregion

        #region Grab

        public class Grab : IMessage {
            public ulong nwId;
            public byte humanoidId;
            public bool isLeft;
            public ulong nwId_grabbedObject;
            public bool rangeCheck;
            public HandTarget.GrabType grabType;

            public Grab() { }
            public Grab(HandTarget handTarget, ulong nwId_grabbedObject, bool rangeCheck, HandTarget.GrabType grabType) {
                nwId = handTarget.humanoid.nwId;
                humanoidId = (byte)handTarget.humanoid.humanoidId;
                isLeft = handTarget.isLeft;
                this.nwId_grabbedObject = nwId_grabbedObject;
                this.rangeCheck = rangeCheck;
                this.grabType = grabType;
            }
            public Grab(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);
                bw.Write(isLeft);
                bw.Write(nwId_grabbedObject);
                bw.Write(rangeCheck);
                bw.Write((int)grabType);

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();
                isLeft = br.ReadBoolean();
                nwId_grabbedObject = br.ReadUInt64();
                rangeCheck = br.ReadBoolean();
                grabType = (HandTarget.GrabType)br.ReadInt32();
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static void ReceiveGrab(this IHumanoidNetworking networking, byte[] serializedData) {
            Grab data = new Grab(serializedData);
            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking networking, Grab msg) {
            GameObject obj = networking.GetGameObject(msg.nwId_grabbedObject);

            if (networking.debug <= DebugLevel.Info)
                networking.DebugLog("GrabEvent " + obj);

            HumanoidControl humanoid = HumanoidNetworking.FindRemoteHumanoid(networking.humanoids, msg.humanoidId);
            if (humanoid == null) {
                if (networking.debug <= DebugLevel.Warning)
                    networking.DebugWarning("Could not find humanoid: " + msg.humanoidId);
                return;
            }

            HandTarget handTarget = msg.isLeft ? humanoid.leftHandTarget : humanoid.rightHandTarget;
            handTarget.Grab(obj, msg.rangeCheck);
        }

        #endregion

        #region LetGo

        public class LetGo : IMessage {
            public ulong nwId;
            public byte humanoidId;
            public bool isLeft;

            public LetGo() { }
            public LetGo(HandTarget handTarget) {
                nwId = handTarget.humanoid.nwId;
                humanoidId = (byte)handTarget.humanoid.humanoidId;
                isLeft = handTarget.isLeft;
            }
            public LetGo(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);
                bw.Write(isLeft);

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();
                isLeft = br.ReadBoolean();
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static void ReceiveLetGo(this IHumanoidNetworking networking, byte[] serializedData) {
            LetGo data = new LetGo(serializedData);

            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking networking, LetGo msg) {
            HumanoidControl humanoid = FindRemoteHumanoid(networking.humanoids, msg.humanoidId);
            if (humanoid == null) {
                if (networking.debug <= DebugLevel.Warning)
                    Debug.LogWarning("Could not find humanoid: " + msg.humanoidId);
                return;
            }

            HandTarget handTarget = msg.isLeft ? humanoid.leftHandTarget : humanoid.rightHandTarget;
            handTarget.LetGo();
        }

        #endregion

        #region Change Avatar

        public class ChangeAvatar : IMessage {
            public ulong nwId;
            public byte humanoidId;
            public string avatarPrefabName;
            public string possessionLocation = "";

            public ChangeAvatar() { }
            public ChangeAvatar(HumanoidControl humanoid, string avatarPrefabName, string possessionLocation) {
                nwId = humanoid.nwId;
                humanoidId = (byte)humanoid.humanoidId;
                this.avatarPrefabName = avatarPrefabName;
                this.possessionLocation = possessionLocation;
            }
            public ChangeAvatar(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);
                bw.Write(avatarPrefabName);
                if (possessionLocation == null)
                    possessionLocation = "";
                bw.Write(possessionLocation);

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();
                avatarPrefabName = br.ReadString();
                possessionLocation = br.ReadString();
                if (possessionLocation == "")
                    possessionLocation = null;
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static void ReceiveChangeAvatar(this IHumanoidNetworking networking, byte[] serializedData) {
            ChangeAvatar data = new ChangeAvatar(serializedData);

            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking receivingNetworking, ChangeAvatar msg) {
            IHumanoidNetworking networking = GetHumanoidNetworking(receivingNetworking, msg.nwId);
            if (networking == null) {
                if (receivingNetworking.debug <= DebugLevel.Error)
                    receivingNetworking.DebugLog("Could not find Networking for Humanoid Pose " + msg.nwId + "/" + msg.humanoidId);
                return;
            }

            if (networking.isLocal && !networking.createLocalRemotes) {
                networking.DebugLog("Don't change the local avatar");
                return;
            }

            HumanoidControl remoteHumanoid = FindRemoteHumanoid(networking.humanoids, msg.humanoidId);
            if (remoteHumanoid == null) {
                if (networking.debug <= DebugLevel.Warning)
                    Debug.LogWarning("Could not find humanoid: " + msg.humanoidId);
                return;
            }

            if (msg.possessionLocation != null) {
                Debug.Log("Need to download avatar possession from " + msg.possessionLocation);
                HumanoidPlayer.instance.StartCoroutine(
                    VisitorPossessions.RetrievePossessableAsync(msg.possessionLocation, msg.avatarPrefabName,
                        retrievedAvatar => ProcessRetrievedAvatar(networking, remoteHumanoid, retrievedAvatar))
                    );
                return;
            }

            GameObject remoteAvatar = (GameObject)Resources.Load(msg.avatarPrefabName);
            if (remoteAvatar == null) {
                if (networking.debug <= DebugLevel.Error)
                    Debug.LogError("Could not load remote avatar " + msg.avatarPrefabName + ". Is it located in a Resources folder?");
                return;
            }

            if (networking.debug <= DebugLevel.Info)
                networking.DebugLog("Receive Change Avatar " + msg.nwId + "/" + msg.humanoidId + " " + msg.avatarPrefabName);

            remoteHumanoid.LocalChangeAvatar(remoteAvatar);
        }

        private static void ProcessRetrievedAvatar(IHumanoidNetworking networking, HumanoidControl remoteHumanoid, GameObject retrievedAvatar) {
            if (retrievedAvatar == null) {
                if (networking.debug <= DebugLevel.Error)
                    Debug.LogError("Could not retrieve  avatar.");
                return;
            }

            if (networking.debug <= DebugLevel.Info)
                networking.DebugLog("Receive Change Possessesable Avatar " + retrievedAvatar);

            remoteHumanoid.LocalChangeAvatar(retrievedAvatar);
        }

        #endregion

        #region Sync Tracking Space

        public class SyncTrackingSpace : IMessage {
            public ulong nwId;
            public byte humanoidId;
            public Vector3S position;
            public Vector3S rotation;

            public SyncTrackingSpace() { }
            public SyncTrackingSpace(HumanoidControl humanoid, Vector3 position, Quaternion rotation) {
                nwId = humanoid.nwId;
                humanoidId = (byte)humanoid.humanoidId;

                this.position = new Vector3S(position);
                this.rotation = new Vector3S(rotation);
            }
            public SyncTrackingSpace(byte[] data) : base(data) { }

            public override byte[] Serialize() {
                MemoryStream ms = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(ms);

                bw.Write(nwId);
                bw.Write(humanoidId);

                position.Write(bw);
                rotation.Write(bw);

                byte[] data = ms.ToArray();
                return data;
            }

            public override void Deserialize(byte[] data) {
                MemoryStream ms = new MemoryStream(data);
                BinaryReader br = new BinaryReader(ms);

                nwId = br.ReadUInt64();
                humanoidId = br.ReadByte();

                position = Vector3S.Read(br);
                rotation = Vector3S.Read(br);
            }

            // This is the same code as in the base class IMessage
            // It is needed because the base class implementation only
            // results in compiler errors.
#if hNW_MIRROR
            //public override void Serialize(Mirror.NetworkWriter writer) {
            //    byte[] data = Serialize();
            //    Mirror.NetworkWriterExtensions.WriteBytesAndSize(writer, data);
            //}

            //public override void Deserialize(Mirror.NetworkReader reader) {
            //    byte[] data = Mirror.NetworkReaderExtensions.ReadBytesAndSize(reader);
            //    Deserialize(data);
            //}
#endif        
        }

        public static Transform GetTrackingTransform(HumanoidControl humanoid) {
#if hANTILATENCY
            if (humanoid.antilatency != null)
                return humanoid.antilatency.trackerTransform;
#endif
            //#if hOPENVR && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX)
            //            if (humanoid.openVR != null)
            //                return humanoid.openVR.GetTrackingTransform();
            //#endif
            return null;
        }

        public static void ReceiveSyncTrackingSpace(this IHumanoidNetworking networking, byte[] serializedData) {
            SyncTrackingSpace data = new SyncTrackingSpace(serializedData);

            Receive(networking, data);
        }

        public static void Receive(this IHumanoidNetworking networking, SyncTrackingSpace msg) {
            foreach (HumanoidControl humanoid in HumanoidControl.allHumanoids) {
                if (humanoid.isRemote || humanoid.nwId == msg.nwId)
                    continue;

                // The lowest (= earliest) nwId is the boss
                if (msg.nwId > humanoid.nwId)
                    return;

                Debug.Log("receive tracking sync");

                //#if hOPENVR && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX)
                //                // The lowest (= earliest) nwId is the boss
                //                // NOT ATM FOR TESTING
                //                //if (msg.nwId > humanoid.nwId)
                //                if (humanoid.openVR != null) {
                //                    humanoid.openVR.SyncTracking(msg.position.vector3, msg.rotation.quaternion);
                //                }
                //#endif
#if hANTILATENCY
                //if (msg.nwId > humanoid.nwId) {
                if (humanoid.antilatency != null) {
                    humanoid.antilatency.SyncTracking(msg.position.vector3, msg.rotation.quaternion);
                }
                //}
#endif
            }
        }

        #endregion

        public static IHumanoidNetworking GetHumanoidNetworking(IHumanoidNetworking networking, ulong nwId) {
            if (networking.nwId == nwId)
                return networking;

            GameObject networkingObj = networking.GetGameObject(nwId);
            if (networkingObj == null)
                return null;

            return networkingObj.GetComponent<IHumanoidNetworking>();
        }

        public static List<HumanoidControl> FindLocalHumanoids() {
            List<HumanoidControl> humanoidList = new List<HumanoidControl>();
            HumanoidControl[] foundHumanoids = UnityEngine.Object.FindObjectsOfType<HumanoidControl>();
            for (int i = 0; i < foundHumanoids.Length; i++) {
                if (!foundHumanoids[i].isRemote) {
                    humanoidList.Add(foundHumanoids[i]);
                }
            }
            return humanoidList;
        }

        public static IHumanoidNetworking GetLocalHumanoidNetworking() {
            HumanoidPlayer[] humanoidNetworkings = UnityEngine.Object.FindObjectsOfType<HumanoidPlayer>();
            foreach (IHumanoidNetworking humanoidNetworking in humanoidNetworkings) {
                if (humanoidNetworking.isLocal)
                    return humanoidNetworking;
            }

            return null;
        }

        public static void DisableNetworkSync(GameObject obj) {
#if hNW_UNET || hNW_PHOTON || hNW_BOLT || hNW_MIRROR
            HumanoidPlayer.DisableNetworkSync(obj);
#endif
        }

        public static void ReenableNetworkSync(GameObject obj) {
#if hNW_UNET || hNW_PHOTON || hNW_BOLT || hNW_MIRROR
            HumanoidPlayer.ReenableNetworkSync(obj);
#endif
        }

        public static void TakeOwnership(GameObject obj) {
#if hNW_PHOTON
            HumanoidPlayer.TakeOwnership(obj);
#endif
        }

        public static HumanoidControl FindHumanoid(List<HumanoidControl> humanoids, int humanoidId) {
            foreach (HumanoidControl humanoid in humanoids) {
                if (humanoid == null)
                    continue;
                if (humanoid.humanoidId == humanoidId)
                    return humanoid;
            }
            return null;
        }

        public static HumanoidControl FindLocalHumanoid(List<HumanoidControl> humanoids, int humanoidId) {
            foreach (HumanoidControl humanoid in humanoids) {
                if (humanoid == null)
                    continue;
                if (!humanoid.isRemote && humanoid.humanoidId == humanoidId)
                    return humanoid;
            }
            return null;
        }

        public static HumanoidControl FindRemoteHumanoid(List<HumanoidControl> humanoids, int humanoidId) {
            foreach (HumanoidControl humanoid in humanoids) {
                if (humanoid == null)
                    continue;
                if (humanoid.isRemote && humanoid.humanoidId == humanoidId)
                    return humanoid;
            }
            return null;
        }
        #region Start

        //public static void Start(PawnNetworking.DebugLevel debug, bool syncFingerSwing) {
        //    HumanoidNetworking.debug = debug;
        //    HumanoidNetworking.syncFingerSwing = syncFingerSwing;
        //}

        #endregion

        #region Start Humanoid
        //public static HumanoidControl StartHumanoid(
        //    ulong nwId,
        //    int humanoidId,
        //    string name,
        //    string avatarPrefabName,
        //    Vector3 position, Quaternion rotation,
        //    bool physics) {

        //    if (debug <= PawnNetworking.DebugLevel.Info)
        //        UnityEngine.Debug.Log(nwId + ": Receive StartHumanoid " + humanoidId);

        //    HumanoidControl remoteHumanoid = InstantiateRemoteHumanoid(remoteHumanoidPrefab, name, position, rotation);
        //    remoteHumanoid.nwId = nwId;
        //    remoteHumanoid.humanoidId = humanoidId;

        //    if (debug <= PawnNetworking.DebugLevel.Info)
        //        UnityEngine.Debug.Log(remoteHumanoid.nwId + ": Remote Humanoid " + remoteHumanoid.humanoidId + " Added");

        //    GameObject remoteAvatar = (GameObject)Resources.Load(avatarPrefabName);
        //    if (remoteAvatar == null) {
        //        if (debug <= PawnNetworking.DebugLevel.Error)
        //            UnityEngine.Debug.LogError("Could not load remote avatar " + avatarPrefabName + ". Is it located in a Resources folder?");
        //        return remoteHumanoid;
        //    }
        //    remoteHumanoid.physics = physics;
        //    remoteHumanoid.LocalChangeAvatar(remoteAvatar);

        //    return remoteHumanoid;
        //}




        #endregion

        #region Pose

        #region Smoothing

        public static void SmoothUpdate(List<HumanoidControl> humanoids) {
            foreach (HumanoidControl humanoid in humanoids) {
                if (humanoid.isRemote)
                    SmoothUpdate(humanoid);
            }
        }

        public static void SmoothUpdate(HumanoidControl humanoid) {
            //Debug.Log("smoothing");
            humanoid.transform.position += humanoid.velocity * Time.deltaTime;
        }

        #endregion

        #endregion
    }
}
