/*
 *  Copyright (C) 2008-2013 Team XBMC
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace XBMC
{

    public enum IconType
    {
        ICON_NONE = 0x00,
        ICON_JPEG = 0x01,
        ICON_PNG = 0x02,
        ICON_GIF = 0x03
    }

    public enum ButtonFlagsType
    {
        BTN_USE_NAME = 0x01,
        BTN_DOWN = 0x02,
        BTN_UP = 0x04,
        BTN_USE_AMOUNT = 0x08,
        BTN_QUEUE = 0x10,
        BTN_NO_REPEAT = 0x20,
        BTN_VKEY = 0x40,
        BTN_AXIS = 0x80
    }

    public enum MouseFlagsType
    {
        MS_ABSOLUTE = 0x01
    }

    public enum LogTypeEnum
    {
        LOGDEBUG = 0,
        LOGINFO = 1,
        LOGNOTICE = 2,
        LOGWARNING = 3,
        LOGERROR = 4,
        LOGSEVERE = 5,
        LOGFATAL = 6,
        LOGNONE = 7
    }

    public enum ActionType
    {
        ACTION_EXECBUILTIN = 0x01,
        ACTION_BUTTON = 0x02
    }

    public class EventClient
    {

        /************************************************************************/
        /* Written by Peter Tribe aka EqUiNox (TeamBlackbolt)                   */
        /* Based upon XBMC's xbmcclient.cpp class                               */
        /************************************************************************/

        private enum PacketType
        {
            PT_HELO = 0x01,
            PT_BYE = 0x02,
            PT_BUTTON = 0x03,
            PT_MOUSE = 0x04,
            PT_PING = 0x05,
            PT_BROADCAST = 0x06,  //Currently not implemented
            PT_NOTIFICATION = 0x07,
            PT_BLOB = 0x08,
            PT_LOG = 0x09,
            PT_ACTION = 0x0A,
            PT_DEBUG = 0xFF //Currently not implemented
        }

        private const int STD_PORT = 9777;
        private const int MAX_PACKET_SIZE = 1024;
        private const int HEADER_SIZE = 32;
        private const int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
        private const byte MAJOR_VERSION = 2;
        private const byte MINOR_VERSION = 0;

        private uint uniqueToken;
        private Socket socket;

        public bool Connect(string Address)
        {
            return Connect(Address, STD_PORT, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
        }

        public bool Connect(string Address, int Port)
        {
            return Connect(Address, Port, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
        }


        public bool Connect(string Address, int Port, uint UID)
        {
            try
            {
                if (socket != null) Disconnect();
                uniqueToken = UID;
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

// For compilation with .net framework 1.0 or 1.1
// define the FRAMEWORK_1_x conditional compilation constant in the project
// or add /define:FRAMEWORK_1_x to the csc.exe command line
#if FRAMEWORK_1_x
                socket.Connect(Dns.GetHostByName(Address).AddressList[0].ToString(), Port);
#else
                socket.Connect(Address, Port);
#endif
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool Connected
        {
            get
            {
                if (socket == null) return false;
                return socket.Connected;
            }
        }

        public void Disconnect()
        {
            try
            {
                if (socket != null)
                {
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                    socket = null;
                }
            }
            catch
            {
            }
        }

        private byte[] Header(PacketType PacketType, int NumberOfPackets, int CurrentPacket, int PayloadSize)
        {

            byte[] header = new byte[HEADER_SIZE];

            header[0] = (byte)'X';
            header[1] = (byte)'B';
            header[2] = (byte)'M';
            header[3] = (byte)'C';

            header[4] = MAJOR_VERSION;
            header[5] = MINOR_VERSION;

            if (CurrentPacket == 1)
            {
                header[6] = (byte)(((ushort)PacketType & 0xff00) >> 8);
                header[7] = (byte)((ushort)PacketType & 0x00ff);
            }
            else
            {
                header[6] = (byte)(((ushort)PacketType.PT_BLOB & 0xff00) >> 8);
                header[7] = (byte)((ushort)PacketType.PT_BLOB & 0x00ff);
            }

            header[8] = (byte)((CurrentPacket & 0xff000000) >> 24);
            header[9] = (byte)((CurrentPacket & 0x00ff0000) >> 16);
            header[10] = (byte)((CurrentPacket & 0x0000ff00) >> 8);
            header[11] = (byte)(CurrentPacket & 0x000000ff);

            header[12] = (byte)((NumberOfPackets & 0xff000000) >> 24);
            header[13] = (byte)((NumberOfPackets & 0x00ff0000) >> 16);
            header[14] = (byte)((NumberOfPackets & 0x0000ff00) >> 8);
            header[15] = (byte)(NumberOfPackets & 0x000000ff);

            header[16] = (byte)((PayloadSize & 0xff00) >> 8);
            header[17] = (byte)(PayloadSize & 0x00ff);

            header[18] = (byte)((uniqueToken & 0xff000000) >> 24);
            header[19] = (byte)((uniqueToken & 0x00ff0000) >> 16);
            header[20] = (byte)((uniqueToken & 0x0000ff00) >> 8);
            header[21] = (byte)(uniqueToken & 0x000000ff);

            return header;

        }

        private bool Send(PacketType PacketType, byte[] Payload)
        {
            try
            {

                bool successful = true;
                int packetCount = (Payload.Length / MAX_PAYLOAD_SIZE) + 1;
                int bytesToSend = 0;
                int bytesSent = 0;
                int bytesLeft = Payload.Length;

                for (int Package = 1; Package <= packetCount; Package++)
                {

                    if (bytesLeft > MAX_PAYLOAD_SIZE)
                    {
                        bytesToSend = MAX_PAYLOAD_SIZE;
                        bytesLeft -= bytesToSend;
                    }
                    else
                    {
                        bytesToSend = bytesLeft;
                        bytesLeft = 0;
                    }

                    byte[] header = Header(PacketType, packetCount, Package, bytesToSend);
                    byte[] packet = new byte[MAX_PACKET_SIZE];

                    Array.Copy(header, 0, packet, 0, header.Length);
                    Array.Copy(Payload, bytesSent, packet, header.Length, bytesToSend);

                    int sendSize = socket.Send(packet, header.Length + bytesToSend, SocketFlags.None);

                    if (sendSize != (header.Length + bytesToSend))
                    {
                        successful = false;
                        break;
                    }

                    bytesSent += bytesToSend;

                }

                return successful;

            }
            catch
            {

                return false;

            }

        }

        /************************************************************************/
        /* SendHelo - Payload format                                            */
        /* %s -  device name (max 128 chars)                                    */
        /* %c -  icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF )              */
        /* %s -  my port ( 0=>not listening )                                   */
        /* %d -  reserved1 ( 0 )                                                */
        /* %d -  reserved2 ( 0 )                                                */
        /* XX -  imagedata ( can span multiple packets )                        */
        /************************************************************************/
        public bool SendHelo(string DevName, IconType IconType, string IconFile)
        {

            byte[] icon = new byte[0];
            if (IconType != IconType.ICON_NONE)
                icon = File.ReadAllBytes(IconFile);

            byte[] payload = new byte[DevName.Length + 12 + icon.Length];

            int offset = 0;

            for (int i = 0; i < DevName.Length; i++)
                payload[offset++] = (byte)DevName[i];
            payload[offset++] = (byte)'\0';

            payload[offset++] = (byte)IconType;

            payload[offset++] = (byte)0;
            payload[offset++] = (byte)'\0';

            for (int i = 0; i < 8; i++)
                payload[offset++] = (byte)0;

            Array.Copy(icon, 0, payload, DevName.Length + 12, icon.Length);

            return Send(PacketType.PT_HELO, payload);

        }

        public bool SendHelo(string DevName)
        {
            return SendHelo(DevName, IconType.ICON_NONE, "");
        }

        /************************************************************************/
        /* SendNotification - Payload format                                    */
        /* %s - caption                                                         */
        /* %s - message                                                         */
        /* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF )               */
        /* %d - reserved ( 0 )                                                  */
        /* XX - imagedata ( can span multiple packets )                         */
        /************************************************************************/
        public bool SendNotification(string Caption, string Message, IconType IconType, string IconFile)
        {

            byte[] icon = new byte[0];
            if (IconType != IconType.ICON_NONE)
                icon = File.ReadAllBytes(IconFile);

            byte[] payload = new byte[Caption.Length + Message.Length + 7 + icon.Length];

            int offset = 0;

            for (int i = 0; i < Caption.Length; i++)
                payload[offset++] = (byte)Caption[i];
            payload[offset++] = (byte)'\0';

            for (int i = 0; i < Message.Length; i++)
                payload[offset++] = (byte)Message[i];
            payload[offset++] = (byte)'\0';

            payload[offset++] = (byte)IconType;

            for (int i = 0; i < 4; i++)
                payload[offset++] = (byte)0;

            Array.Copy(icon, 0, payload, Caption.Length + Message.Length + 7, icon.Length);

            return Send(PacketType.PT_NOTIFICATION, payload);

        }

        public bool SendNotification(string Caption, string Message)
        {
            return SendNotification(Caption, Message, IconType.ICON_NONE, "");
        }

        /************************************************************************/
        /* SendButton - Payload format                                          */
        /* %i - button code                                                     */
        /* %i - flags 0x01 => use button map/name instead of code               */
        /*            0x02 => btn down                                          */
        /*            0x04 => btn up                                            */
        /*            0x08 => use amount                                        */
        /*            0x10 => queue event                                       */
        /*            0x20 => do not repeat                                     */
        /*            0x40 => virtual key                                       */
        /*            0x80 => axis key                                          */
        /* %i - amount ( 0 => 65k maps to -1 => 1 )                             */
        /* %s - device map (case sensitive and required if flags & 0x01)        */
        /*      "KB" - Standard keyboard map                                    */
        /*      "XG" - Xbox Gamepad                                             */
        /*      "R1" - Xbox Remote                                              */
        /*      "R2" - Xbox Universal Remote                                    */
        /*      "LI:devicename" -  valid LIRC device map where 'devicename'     */
        /*                         is the actual name of the LIRC device        */
        /*      "JS<num>:joyname" -  valid Joystick device map where            */
        /*                           'joyname'  is the name specified in        */
        /*                           the keymap. JS only supports button code   */
        /*                           and not button name currently (!0x01).     */
        /* %s - button name (required if flags & 0x01)                          */
        /************************************************************************/
        private bool SendButton(string Button, ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
        {

            if (Button.Length != 0)
            {
                if ((Flags & ButtonFlagsType.BTN_USE_NAME) == 0)
                    Flags |= ButtonFlagsType.BTN_USE_NAME;
                ButtonCode = 0;
            }
            else
                Button = "";

            if (Amount > 0)
            {
                if ((Flags & ButtonFlagsType.BTN_USE_AMOUNT) == 0)
                    Flags |= ButtonFlagsType.BTN_USE_AMOUNT;
            }

            if ((Flags & ButtonFlagsType.BTN_DOWN) == 0 && (Flags & ButtonFlagsType.BTN_UP) == 0)
                Flags |= ButtonFlagsType.BTN_DOWN;

            byte[] payload = new byte[Button.Length + DeviceMap.Length + 8];

            int offset = 0;

            payload[offset++] = (byte)((ButtonCode & 0xff00) >> 8);
            payload[offset++] = (byte)(ButtonCode & 0x00ff);

            payload[offset++] = (byte)(((ushort)Flags & 0xff00) >> 8);
            payload[offset++] = (byte)((ushort)Flags & 0x00ff);

            payload[offset++] = (byte)((Amount & 0xff00) >> 8);
            payload[offset++] = (byte)(Amount & 0x00ff);

            for (int i = 0; i < DeviceMap.Length; i++)
                payload[offset++] = (byte)DeviceMap[i];
            payload[offset++] = (byte)'\0';

            for (int i = 0; i < Button.Length; i++)
                payload[offset++] = (byte)Button[i];
            payload[offset++] = (byte)'\0';

            return Send(PacketType.PT_BUTTON, payload);

        }

        public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags, short Amount)
        {
            return SendButton(Button, 0, DeviceMap, Flags, Amount);
        }

        public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags)
        {
            return SendButton(Button, 0, DeviceMap, Flags, 0);
        }

        public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
        {
            return SendButton("", ButtonCode, DeviceMap, Flags, Amount);
        }

        public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags)
        {
            return SendButton("", ButtonCode, DeviceMap, Flags, 0);
        }

        public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags, short Amount)
        {
            return SendButton("", ButtonCode, "", Flags, Amount);
        }

        public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags)
        {
            return SendButton("", ButtonCode, "", Flags, 0);
        }

        public bool SendButton()
        {
            return SendButton("", 0, "", ButtonFlagsType.BTN_UP, 0);
        }

        /************************************************************************/
        /* SendPing - No payload                                                */
        /************************************************************************/
        public bool SendPing()
        {
            byte[] payload = new byte[0];
            return Send(PacketType.PT_PING, payload);
        }

        /************************************************************************/
        /* SendBye - No payload                                                 */
        /************************************************************************/
        public bool SendBye()
        {
            byte[] payload = new byte[0];
            return Send(PacketType.PT_BYE, payload);
        }

        /************************************************************************/
        /* SendMouse - Payload format                                           */
        /* %c - flags                                                           */
        /*    - 0x01 absolute position                                          */
        /* %i - mousex (0-65535 => maps to screen width)                        */
        /* %i - mousey (0-65535 => maps to screen height)                       */
        /************************************************************************/
        public bool SendMouse(ushort X, ushort Y, MouseFlagsType Flags)
        {

            byte[] payload = new byte[9];

            int offset = 0;

            payload[offset++] = (byte)Flags;

            payload[offset++] = (byte)((X & 0xff00) >> 8);
            payload[offset++] = (byte)(X & 0x00ff);

            payload[offset++] = (byte)((Y & 0xff00) >> 8);
            payload[offset++] = (byte)(Y & 0x00ff);

            return Send(PacketType.PT_MOUSE, payload);

        }

        public bool SendMouse(ushort X, ushort Y)
        {
            return SendMouse(X, Y, MouseFlagsType.MS_ABSOLUTE);
        }

        /************************************************************************/
        /* SendLog - Payload format                                             */
        /* %c - log type                                                        */
        /* %s - message                                                         */
        /************************************************************************/
        public bool SendLog(LogTypeEnum LogLevel, string Message)
        {

            byte[] payload = new byte[Message.Length + 2];

            int offset = 0;

            payload[offset++] = (byte)LogLevel;

            for (int i = 0; i < Message.Length; i++)
                payload[offset++] = (byte)Message[i];
            payload[offset++] = (byte)'\0';

            return Send(PacketType.PT_LOG, payload);

        }

        /************************************************************************/
        /* SendAction - Payload format                                          */
        /* %c - action type                                                     */
        /* %s - action message                                                  */
        /************************************************************************/
        public bool SendAction(ActionType Action, string Message)
        {

            byte[] payload = new byte[Message.Length + 2];

            int offset = 0;

            payload[offset++] = (byte)Action;

            for (int i = 0; i < Message.Length; i++)
                payload[offset++] = (byte)Message[i];
            payload[offset++] = (byte)'\0';

            return Send(PacketType.PT_ACTION, payload);

        }

        public bool SendAction(string Message)
        {
            return SendAction(ActionType.ACTION_EXECBUILTIN, Message);
        }

    }
}
