using System;
using System.Collections.Generic;
using Passer.LinearAlgebra;

namespace Passer.RoboidControl {

    /// <summary>
    /// A thing is the primitive building block
    /// </summary>
    [Serializable]
    public class Thing {

        #region Types

        #endregion Types

        #region Properties

        public delegate void ChangeHandler();
        public delegate void SphericalHandler(Spherical v);
        public delegate void ThingHandler(Thing t);

        /// <summary>
        /// The participant to which this thing belongs
        /// </summary>
        public RemoteParticipant participant;

        /// <summary>
        /// The network ID of this thing.
        /// </summary>
        public byte networkId;
        /// <summary>
        /// The ID of this thing
        /// </summary>
        public byte id;

        /// <summary>
        /// Predefined thing types
        /// </summary>
        public enum Type {
            Undetermined,
            // Sensor
            Switch,
            DistanceSensor,
            DirectionalSensor,
            TemperatureSensor,
            // Motor
            ControlledMotor,
            UncontrolledMotor,
            Servo,
            // Other
            Roboid,
            Humanoid,
            ExternalSensor
        };
        /// <summary>
        /// The type of this thing. This can be either a Thing::Type (needs casting)
        /// or a byte value for custom types.
        /// </summary>
        public byte type;

        /// <summary>
        /// Event which is triggered when the parent changes
        /// </summary>
        public event ChangeHandler OnParentChanged = delegate { };
        private Thing _parent;
        /// <summary>
        /// The parent of this thing
        /// </summary>
        public Thing parent {
            get => _parent;
            set {
                if (_parent == value)
                    return;

                if (value == null) {
                    _parent?.RemoveChild(this);
                    _parent = null;
                }
                else {
                    value.AddChild(this);
                    OnParentChanged?.Invoke();
                }
            }
        }

        /// <summary>
        /// Attach a thing as a child of this thing
        /// </summary>
        /// <param name="child">The thing to attach as a child</param>
        public void AddChild(Thing child) {
            if (children.Find(thing => thing == child) != null)
                return;
            child._parent = this;
            children.Add(child);
        }
        /// <summary>
        /// Remove the given thing as a child of this thing
        /// </summary>
        /// <param name="child">The child to remove</param>
        public void RemoveChild(Thing child) {
            children.Remove(child);
        }

        /// <summary>
        /// The list of children of this thing
        /// </summary>
        [NonSerialized]
        public List<Thing> children = new List<Thing>();

        /// <summary>
        /// Event which is triggered when the name changes
        /// </summary>
        public event ChangeHandler OnNameChanged = delegate { };
        private string _name = "";
        /// <summary>
        /// The name of the thing
        /// </summary>
        public virtual string name {
            get => _name;
            set {
                if (_name != value) {
                    _name = value;
                    OnNameChanged?.Invoke();
                }
            }
        }

        /// <summary>
        /// An URL pointing to the location where a model of the thing can be found
        /// </summary>
        public string modelUrl = "";

        /// <summary>
        /// Event triggered when the position has changed
        /// </summary>
        public event ChangeHandler OnPositionChanged = delegate { };
        private Spherical _position = Spherical.zero;
        /// <summary>
        /// The position of the thing in local space, in meters.
        /// </summary>
        public Spherical position {
            get { return _position; }
            set {
                if (_position != value) {
                    _position = value;
                    OnPositionChanged?.Invoke();
                }
            }
        }

        /// <summary>
        /// Event triggered when the orientation has changed
        /// </summary>
        public event ChangeHandler OnOrientationChanged = delegate { };
        private SwingTwist _orientation = SwingTwist.zero;
        /// <summary>
        /// The orientation of the thing in local space
        /// </summary>
        public SwingTwist orientation {
            get { return _orientation; }
            set {
                if (_orientation != value) {
                    _orientation = value;
                    OnOrientationChanged?.Invoke();
                }
            }
        }

        /// <summary>
        /// Event triggered when the linear velocity has changed
        /// </summary>
        public event SphericalHandler OnLinearVelocityChanged = delegate { };
        private Spherical _linearVelocity = Spherical.zero;
        /// <summary>
        /// The linear velocity of the thing in local space in meters per second
        /// </summary>
        public Spherical linearVelocity {
            get => _linearVelocity;
            set {
                if (_linearVelocity != value) {
                    _linearVelocity = value;
                    OnLinearVelocityChanged?.Invoke(_linearVelocity);
                }
            }
        }
        /// <summary>
        /// The angular velocity of the thing in local space
        /// </summary>
        public Spherical angularVelocity = Spherical.zero;

#if UNITY_5_3_OR_NEWER
        /// <summary>
        /// A reference to the representation of the thing in Unity
        /// </summary>
        [NonSerialized]
        public Unity.Thing component = null;
#endif

        #endregion Properties

        #region Init

        /// <summary>
        /// Create a new thing for the given participant
        /// </summary>
        /// <param name="participant">The participant for which this thing is created</param>
        /// <param name="invokeEvent">True when a new thing event should be triggered</param>
        public Thing(RemoteParticipant participant, bool invokeEvent = false) {
            this.participant = participant;
            if (invokeEvent)
                InvokeNewThing(this);
        }
        /// <summary>
        /// Create a new thing for the given participant
        /// </summary>
        /// <param name="participant">The participant for which this thing is created</param>
        /// <param name="networkId">The network ID of the thing</param>
        /// <param name="thingId">The ID of the thing</param>
        /// <param name="thingType">The type of thing</param>
        public Thing(RemoteParticipant participant, byte networkId, byte thingId, byte thingType = 0) {
            this.participant = participant;
            this.id = thingId;
            this.type = thingType;
            this.networkId = networkId;
        }

        /// <summary>
        /// Function which can be used to create components in external engines.
        /// </summary>
        /// Currently this is used to create GameObjects in Unity
        public virtual void CreateComponent() {
#if UNITY_5_3_OR_NEWER            
            this.component = Unity.Thing.Create(this);
            this.component.core = this;
#endif            
        }

        #endregion Init

        #region Update

#if UNITY_5_3_OR_NEWER
        /// <summary>
        /// Convience function for use in Unity which removes the need for a currentTime argument
        /// </summary>
        public void Update() {
            Update((ulong)UnityEngine.Time.time * 1000);
        }
#endif
        /// <summary>
        /// Update this thing
        /// </summary>
        /// <param name="currentTime">The current time in milliseconds</param>
        public virtual void Update(ulong currentTime) {
            // should recurse over children...
        }

        /// <summary>
        /// Function used to generate binary data for this thing
        /// </summary>
        /// <returns>a byte array with the binary data</returns>
        /// @sa Passer::RoboidControl::BinaryMsg
        public virtual byte[] GenerateBinary() { return new byte[0]; }

        /// <summary>
        /// Function used to process binary data received for this thing
        /// </summary>
        /// <param name="bytes">The binary data</param>
        public virtual void ProcessBinary(byte[] bytes) {
        }

        #endregion Update

        /// <summary>
        /// Event triggered when a new thing has been created
        /// </summary>
        public static event ThingHandler OnNewThing = delegate { };
        /// <summary>
        /// Trigger the creation for the given thing
        /// </summary>
        /// <param name="thing">The created thing</param>
        public static void InvokeNewThing(Thing thing) {
            OnNewThing?.Invoke(thing);
        }

        /// <summary>
        /// Check if the thing has the given properaties
        /// </summary>
        /// <param name="thing">The thing to check</param>
        /// <param name="networkId">The network ID to compare to</param>
        /// <param name="thingId">The thing ID to compare to</param>
        /// <returns>True when the thing has the given properties</returns>
        public static bool IsThing(Thing thing, byte networkId, byte thingId) {
            if (thing == null)
                return false;
            return (thing.networkId == networkId) && (thing.id == thingId);
        }

    }
}