// ALL RIGHTS RESERVED:
// ====================
// The contents of this file, and associated files in this directory, are 
// Copyright (C) SPT , all rights reserved, 2009.
// All software Source Code, Images, Database-Design and code, Graphics Design 
// and source files, and related content (collectively referred to as SOURCE) are 
// Copyright (c) SPT, 2009, All Rights Reserved.
// http://SerialPortTool.com

// PTZ Controller is a Registered Trademark of SerialPortTool.com.
using System;
using System.IO;
using System.Text;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using SPT.PTZ.Core;
using System.Diagnostics;

namespace PTZController.Controls
{
    public class FreeMoveBox : PictureBox
    {
        public FreeMoveBox()
        {
            this.MouseDown += new MouseEventHandler(FreeMoveBox_MouseDown);
            this.MouseMove += new MouseEventHandler(FreeMoveBox_MouseMove);
            this.MouseUp += new MouseEventHandler(FreeMoveBox_MouseUp);

            int UNIT = Properties.Resources.East.Width;

            _cursors[0] = PTZCursor.CreateCursor(Properties.Resources.East, UNIT - 1, UNIT / 2);
            _cursors[1] = PTZCursor.CreateCursor(Properties.Resources.South, UNIT / 2, UNIT - 1);
            _cursors[2] = PTZCursor.CreateCursor(Properties.Resources.West, 0, UNIT / 2);
            _cursors[3] = PTZCursor.CreateCursor(Properties.Resources.North, UNIT / 2, 0);
            _cursors[4] = PTZCursor.CreateCursor(Properties.Resources.SE, UNIT - 1, UNIT - 1);
            _cursors[5] = PTZCursor.CreateCursor(Properties.Resources.SW, 0, UNIT - 1);
            _cursors[6] = PTZCursor.CreateCursor(Properties.Resources.NE, UNIT - 1, 0);
            _cursors[7] = PTZCursor.CreateCursor(Properties.Resources.NW, 0, 0);
        }

        private Cursor LoadCursor(byte[] buffer)
        {
            MemoryStream stream = new MemoryStream(buffer);

            return new Cursor(stream);
        }

        void FreeMoveBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _mouseDownFlag = true;
                
                SendMoveCommand(e);

                _currentPt.X = e.X;
                _currentPt.Y = e.Y;
            }
            else
            {
                this.Cursor = Cursors.Default;
            }
        }

        void FreeMoveBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (_mouseDownFlag)
            {
                _currentPt.X = e.X;
                _currentPt.Y = e.Y;
                SendMoveCommand(e);
            }
            else
            {
                Cursor = Cursors.Default;
            }
        }

        void FreeMoveBox_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _mouseDownFlag = false;
                MoveCommand(null, Properties.Resources.TextReady);
            }
        }

        private void SendMoveCommand(MouseEventArgs e)
        {
            // Show the cursor in the defined area and show the speed in the status bar
            byte[] extra = new byte[3];

            JudgeInWhichPart(e);
            CalOrdinate(e);
            PackCoordinates(ref extra);
            MoveCommand(extra, DirectionText);
            Refresh();
            RestrictMouse();
        }

        public string DirectionText
        {
            get
            {
                string directionH = null;
                string directionV = null;

                switch (_direction)
                {
                    case Direction.NorthWest:
                        directionV = Properties.Resources.TextUp;
                        directionH = Properties.Resources.TextLeft;
                        break;
                    case Direction.NorthEast:
                        directionV = Properties.Resources.TextUp;
                        directionH = Properties.Resources.TextRight;
                        break;
                    case Direction.SouthEast:
                        directionV = Properties.Resources.TextDown;
                        directionH = Properties.Resources.TextRight;
                        break;
                    case Direction.SouthWest:
                        directionV = Properties.Resources.TextDown;
                        directionH = Properties.Resources.TextLeft;
                        break;
                    case Direction.North:
                        directionV = Properties.Resources.TextUp;
                        break;
                    case Direction.South:
                        directionV = Properties.Resources.TextDown;
                        break;
                    case Direction.West:
                        directionH = Properties.Resources.TextLeft;
                        break;
                    case Direction.East:
                        directionH = Properties.Resources.TextRight;
                        break;
                }

                string text = null;

                if (directionH != null)
                {
                    text += string.Format("{0}: {1}%",
                        directionH, DisplayFactor * _coordinateX / (MaxSpeed - 1));
                }

                if (directionV != null)
                {
                    text += string.Format(" {0}: {1}%",
                        directionV, DisplayFactor * _coordinateY / (MaxSpeed - 1));
                }

                Debug.Print(text);

                return text;
            }
        }

        private void PackCoordinates(ref byte[] data)
        {
            data[0] = (byte)_direction;
            data[1] = (byte)_coordinateX;
            data[2] = (byte)_coordinateY;
        }

        //Judge the mouse is in which part of the mouse area
        //Direction:  left 04H  right 02H  up 08H  down 10H
        private void JudgeInWhichPart(MouseEventArgs e)
        {
            FindDirection(new Point(e.X, e.Y));
        }

        //Judge the mouse is in which part of the mouse area
        //Direction:  left 04H  right 02H  up 08H  down 10H
        private void FindDirection(Point e)
        {
            Rectangle rc = this.ClientRectangle;
            int halfWidth = rc.Width / 2;
            int halfHeight = rc.Height / 2;

            if (e.X < halfWidth - MarginPixel) // west part
            {
                if (e.Y < halfHeight - MarginPixel)
                {
                    _direction = Direction.NorthWest;
                }
                else if (e.Y > halfHeight + MarginPixel)
                {
                    _direction = Direction.SouthWest;
                }
                else
                {
                    _direction = Direction.West;
                }
            }
            else if (e.X > halfWidth + MarginPixel) // east part
            {
                if (e.Y < halfHeight - MarginPixel)
                {
                    _direction = Direction.NorthEast;
                }
                else if (e.Y > halfHeight + MarginPixel)
                {
                    _direction = Direction.SouthEast;
                }
                else
                {
                    _direction = Direction.East;
                }
            }
            else // north and south part
            {
                if (e.Y < halfHeight - MarginPixel)
                {
                    _direction = Direction.North;
                }
                else if (e.Y > halfHeight + MarginPixel)
                {
                    _direction = Direction.South;
                }
                else
                {
                    // empty region
                }
            }

            SetCursorIcon(_cursors[(int)_direction]);
        }

        //Get the picture of cursor
        private void SetCursorIcon(Cursor cursorIco)
        {
            try
            {
                this.Cursor = cursorIco;
            }
            catch (System.IO.DirectoryNotFoundException)
            {
                MessageBox.Show(
                    "Please check the cursor folder in the exeFile folder",
                    Text,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Exclamation);
            }
        }

        //Calculate the X-ordinate and Y-ordinate
        private void CalOrdinate(Point e)
        {
            Rectangle rc = this.ClientRectangle;
            int validWidth = rc.Width / 2;
            int validHeight = rc.Height / 2;

            _coordinateX = 0;
            _coordinateY = 0;

            int border = (this.Width - ClientRectangle.Width) / 2;

            _centerArea.X = border + ClientRectangle.Width / 2; ;
            _centerArea.Y = border + ClientRectangle.Height / 2;

            // _centerArea.Inflate(MarginPixel, MarginPixel);

            _coordinateX = Math.Min(Math.Abs(e.X - _centerArea.Right),
                Math.Abs(e.X - _centerArea.Left)) * MaxSpeed / validWidth;

            if (_coordinateX >= MaxSpeed)
            {
                _coordinateX = MaxSpeed - 1;
            }

            _coordinateY = Math.Min(Math.Abs(e.Y - _centerArea.Bottom),
                Math.Abs(e.Y - _centerArea.Top)) * MaxSpeed / validHeight;

            if (_coordinateY >= MaxSpeed)
            {
                _coordinateY = MaxSpeed - 1;
            }
        }

        //Calculate the X-ordinate and Y-ordinate
        private void CalOrdinate(MouseEventArgs e)
        {
            CalOrdinate(new Point(e.X, e.Y));
        }

        //When left button is down and pressed all the time 
        //Restricted the cursor in the mouse area
        private void RestrictMouse()
        {
            if (!_mouseDownFlag)
            {
                return;
            }

            Rectangle boxScreen = this.RectangleToScreen(ClientRectangle);

            Point cursorPos = Cursor.Position;
            int leftLimit, topLimit, rightLimit, bottomLimit;

            //Define the limit position 
            leftLimit = boxScreen.Left; // +picClient.Left;
            rightLimit = leftLimit + boxScreen.Width;
            topLimit = boxScreen.Top; // +picClient.Top;
            bottomLimit = topLimit + boxScreen.Height;

            //Restrict the cursor in the limit position
            if (cursorPos.X < leftLimit)
            {
                Cursor.Position = new Point(leftLimit, cursorPos.Y);
            }

            if (cursorPos.X >= rightLimit)
            {
                Cursor.Position = new Point(rightLimit - 1, cursorPos.Y);
            }

            if (cursorPos.Y < topLimit)
            {
                Cursor.Position = new Point(cursorPos.X, topLimit);
            }

            if (cursorPos.Y >= bottomLimit)
            {
                Cursor.Position = new Point(cursorPos.X, bottomLimit - 1);
            }
        }

        /// <summary>
        /// Sets free move data
        /// </summary>
        /// <param name="param">3 bytes, 1 for direction, 2 for coordinates</param>
        public void SetMoveData(byte[] param)
        {
            if (param != null)
            {
                _mouseDownFlag = true;
                _direction = (Direction)param[0];
                _coordinateX = param[1];
                _coordinateY = param[2];

                Point pt = GetMousePosition(_direction, _coordinateX, _coordinateY);

                Debug.Print("X = {0}, Y = {1}. ", pt.X, pt.Y);

                Rectangle rc = this.RectangleToScreen(this.ClientRectangle);

                // this will cause mouse move event
                Cursor.Position = new Point(rc.Left + pt.X, rc.Top + pt.Y);
            }
            else
            {
                _mouseDownFlag = false;
                SetCursorIcon(Cursors.Default);
            }
        }

        /// <summary>
        /// Gets mouse position based on direction and speed x and y
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private Point GetMousePosition(Direction direct, int x, int y)
        {
            Point center = new Point(
                (ClientRectangle.Left + ClientRectangle.Right) / 2,
                (ClientRectangle.Top + ClientRectangle.Bottom) / 2);
            Size size = new Size(ClientRectangle.Width / 2, ClientRectangle.Height / 2);
            int dx = x * size.Width / (MaxSpeed - 1);
            int dy = y * size.Height / (MaxSpeed - 1);

            switch (direct)
            {
                case Direction.East:
                    center.X += dx;
                    break;
                case Direction.South:
                    center.Y += dy;
                    break;
                case Direction.West:
                    center.X -= dx;
                    break;
                case Direction.North:
                    center.Y -= dy;
                    break;
                case Direction.SouthEast:
                    center.X += dx;
                    center.Y += dy;
                    break;
                case Direction.SouthWest:
                    center.X -= dx;
                    center.Y += dy;
                    break;
                case Direction.NorthEast:
                    center.X += dx;
                    center.Y -= dy;
                    break;
                case Direction.NorthWest:
                    center.X -= dx;
                    center.Y -= dy;
                    break;
                default:
                    break;
            }

            if (center.X >= ClientRectangle.Width)
            {
                center.X = ClientRectangle.Width - 1;
            }

            if (center.Y >= ClientRectangle.Height)
            {
                center.Y = ClientRectangle.Height - 1;
            }

            return center;
        }

        public bool MouseDownFlag
        {
            get { return _mouseDownFlag; }
            set { _mouseDownFlag = value; }
        }

        /// <summary>
        /// Delegate for notifying main window
        /// </summary>
        /// <param name="param"></param>
        /// <param name="text"></param>
        public delegate void MoveCommandHandler(byte[] param, string text); 

        private MoveCommandHandler _moveCommand;

	    public MoveCommandHandler MoveCommand
	    {
		    get { return _moveCommand; }
		    set { _moveCommand = value; }
	    }

        private bool _mouseDownFlag = false;
        private Point _currentPt = new Point();

        private Direction _direction;

        private int _coordinateX;
        private int _coordinateY;

        private Rectangle _centerArea;

        /// <summary>
        /// Cursors for directions.
        /// </summary>
        private Cursor[] _cursors = new Cursor[DirectCount];

        public const int MaxSpeed = 64;

        private const int DirectCount = 8;
        private const int MarginPixel = 4;
        private const int DisplayFactor = 100;
    }

    public struct IconInfo
    {
        public bool fIcon;
        public int xHotspot;
        public int yHotspot;
        public IntPtr hbmMask;
        public IntPtr hbmColor;
    }


    class PTZCursor
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetIconInfo(IntPtr hIcon,
            ref IconInfo pIconInfo);

        [DllImport("user32.dll")]
        public static extern IntPtr CreateIconIndirect(
            ref IconInfo icon);

        public static Cursor CreateCursor(Bitmap bmp,
            int xHotSpot, int yHotSpot)
        {
            IntPtr ptr = bmp.GetHicon();
            IconInfo tmp = new IconInfo();
            GetIconInfo(ptr, ref tmp);
            tmp.xHotspot = xHotSpot;
            tmp.yHotspot = yHotSpot;
            tmp.fIcon = false;
            ptr = CreateIconIndirect(ref tmp);
            return new Cursor(ptr);
        }
    }

}
