using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;

using Bespoke.Common.Osc;
using System.Windows.Media.Media3D;

namespace Tuio
{
    /// <summary>
    /// Simple, still uncomplete implementation of a TUIO server in C#.
    /// 
    /// Current shortcomings:
    /// Object support missing.
    /// Does not implement frame times.
    /// Only supports external TUIO cursors.
    /// Allways commits all cursors.
    /// 
    /// (c) 2010 by Dominik Schmidt (schmidtd@comp.lancs.ac.uk)
    /// </summary>
    public class TuioServer
    {
        #region constants
        
        private const string _cursorAddressPattern = "/tuio/3Dcur";
        private const string _stringAddressPattern = "/tuio/_siP";

        #endregion

        #region fields

        private IPEndPoint _ipEndPoint;

        private Dictionary<int, TuioCursor> _cursors;
        private Dictionary<int, TuioString> _strings;

        private int _currentFrame;

        #endregion

        #region constructors

        /// <summary>
        /// Creates a new server with and endpoint at localhost, port 3333.
        /// </summary>
        public TuioServer() : this("127.0.0.1", 80) { }

        /// <summary>
        /// Creates a new server.
        /// </summary>
        /// <param name="host">Endpoint host</param>
        /// <param name="port">Endpoint port</param>
        public TuioServer(string host, int port)
        {
            _cursors = new Dictionary<int, TuioCursor>();
            _strings = new Dictionary<int, TuioString>();
            _ipEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
            _currentFrame = 0;
        }

        #endregion

        #region frame related methods

        /// <summary>
        /// Initialized a new frame and increases the frame counter.
        /// </summary>
        public void InitFrame()
        {
            _currentFrame++;
        }

        /// <summary>
        /// Commits the current frame.
        /// </summary>
        public void CommitFrame()
        {
            GetCursorFrameBundle().Send(_ipEndPoint);
            GetStringFrameBundle().Send(_ipEndPoint);
        }

        #endregion

        #region cursor related methods
        
        /// <summary>
        /// Adds a TUIO cursor. A new id, not used before, must be provided.
        /// </summary>
        /// <param name="id">New id</param>
        /// <param name="location">Location</param>
        public void AddTuioCursor(int id, Point3D location)
        {
            lock(_cursors)
                if(!_cursors.ContainsKey(id))
                    _cursors.Add(id, new TuioCursor(id, location));
        }

        /// <summary>
        /// Updates a TUIO cursor. An id of an existing cursor must be provided.
        /// </summary>
        /// <param name="id">Id</param>
        /// <param name="location">Location</param>
        public void UpdateTuioCursor(int id, Point3D location)
        {
            TuioCursor cursor;
            if(_cursors.TryGetValue(id, out cursor))
                cursor.Location = location;
        }

        /// <summary>
        /// Deletes a TUIO cursor. An id of an existing cursor must be provided.
        /// </summary>
        /// <param name="id">Id</param>
        public void DeleteTuioCursor(int id)
        {
            lock (_cursors)
                _cursors.Remove(id);
        }

        #endregion

        #region string related methods

        /// <summary>
        /// Adds a TUIO string. A new id, not used before, must be provided.
        /// </summary>
        /// <param name="id">New id</param>
        /// <param name="message">msg</param>
        public void AddTuioString(int id, String msg)
        {
            lock (_strings)
                if (!_strings.ContainsKey(id))
                    _strings.Add(id, new TuioString(id, msg));
        }

        /// <summary>
        /// Updates a TUIO string. An id of an existing string must be provided.
        /// </summary>
        /// <param name="id">Id</param>
        /// <param name="message">msg</param>
        public void UpdateTuioString(int id, String msg)
        {
            TuioString _string;
            if (_strings.TryGetValue(id, out _string))
                _string.message = msg;
        }

        /// <summary>
        /// Deletes a TUIO string. An id of an existing string must be provided.
        /// </summary>
        /// <param name="id">Id</param>
        public void DeleteTuioString(int id)
        {
            lock (_strings)
                _strings.Remove(id);
        }

        #endregion

        #region osc message assembly for cursors

        private OscBundle GetCursorFrameBundle()
        {
            OscBundle bundle = new OscBundle(_ipEndPoint);

            bundle.Append(GetCursorAliveMessage());
            foreach (OscMessage msg in GetCursorMessages())
                bundle.Append(msg);
            bundle.Append(GetCursorSequenceMessage());

            return bundle;
        }

        private OscMessage GetCursorAliveMessage()
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _cursorAddressPattern);

            msg.Append("alive");
            lock (_cursors)
                foreach (TuioCursor cursor in _cursors.Values)
                    msg.Append((Int32)cursor.Id);

            return msg;
        }

        private OscMessage GetCursorSequenceMessage()
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _cursorAddressPattern);

            msg.Append("fseq");
            msg.Append((Int32)_currentFrame);

            return msg;
        }

        private OscMessage GetCursorMessage(TuioCursor cursor)
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _cursorAddressPattern);

            msg.Append("set");
            msg.Append((Int32)cursor.Id);
            msg.Append((float)cursor.Location.X);
            msg.Append((float)cursor.Location.Y);
            msg.Append((float)cursor.Location.Z);
            msg.Append(cursor.Speed.X);
            msg.Append(cursor.Speed.Y);
            msg.Append(cursor.MotionAcceleration);

            return msg;
        }

        private IEnumerable<OscMessage> GetCursorMessages()
        {
            List<OscMessage> msgs = new List<OscMessage>();

            lock (_cursors)
                foreach (TuioCursor cursor in _cursors.Values)
                    msgs.Add(GetCursorMessage(cursor));

            return msgs.AsEnumerable();
        }

        #endregion

        #region osc message assembly for strings

        private OscBundle GetStringFrameBundle()
        {
            OscBundle bundle = new OscBundle(_ipEndPoint);

            bundle.Append(GetStringAliveMessage());
            foreach (OscMessage msg in GetStringMessages())
                bundle.Append(msg);
            bundle.Append(GetStringSequenceMessage());

            return bundle;
        }

        private OscMessage GetStringAliveMessage()
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _stringAddressPattern);

            msg.Append("alive");
            lock (_cursors)
                foreach (TuioString _string in _strings.Values)
                    msg.Append((Int32)_string.Id);

            return msg;
        }

        private OscMessage GetStringSequenceMessage()
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _stringAddressPattern);

            msg.Append("fseq");
            msg.Append((Int32)_currentFrame);

            return msg;
        }

        private OscMessage GetStringMessage(TuioString _string)
        {
            OscMessage msg = new OscMessage(_ipEndPoint, _stringAddressPattern);

            msg.Append("set");
            msg.Append((Int32)_string.Id);
            msg.Append((String)_string.message);

            return msg;
        }

        private IEnumerable<OscMessage> GetStringMessages()
        {
            List<OscMessage> msgs = new List<OscMessage>();

            lock (_strings)
                foreach (TuioString _string in _strings.Values)
                    msgs.Add(GetStringMessage(_string));

            return msgs.AsEnumerable();
        }

        #endregion
    }
}
