using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Threading;

namespace WheelchairControlWM
{
    /// <summary>
    /// Main screen.
    /// </summary>
    public partial class FormMain : Form
    {
        #region Constants

        /// <summary>
        /// Slide length as a fraction of form width.
        /// </summary>
        private const Byte SLIDE_TO_FORM_WIDTH_RATIO = 4;

        /// <summary>
        /// Send error label blink timeout in ms.
        /// </summary>
        private const UInt16 SEND_ERROR_LABEL_BLINK_TIMEOUT_MS = 1000;

        /// <summary>
        /// Timeout for frame receiving.
        /// </summary>
        private const UInt16 FRAME_RECEIVE_TIMEOUT_MS = 2000;

        /// <summary>
        /// Color to paint pressed button background with.
        /// </summary>
        private static readonly Color PRESSED_BUTON_BACKGROUD_COLOR = Color.DarkBlue;

        /// <summary>
        /// Thickness of borders drawn around arrows [pixels].
        /// </summary>
        private const Byte ARROW_BORDER_THICKNESS = 5;

        /// <summary>
        /// Random (not well-known) UDP port.
        /// </summary>
        private const UInt16 UDP_PORT = 50005;

        /// <summary>
        /// Offset of the camera frame data in the packet.
        /// </summary>
        private const Byte CAMERA_FRAME_OFFSET = 1;

        /// <summary>
        /// Enum to determine UDP packet message header type.
        /// </summary>
        private enum PacketHeader : byte
        {
            /// <summary>
            /// Empty packet to reset watchdog.
            /// </summary>
            WATCHDOG_NOP = 0xC0,

            /// <summary>
            /// Artificial joystick data.
            /// </summary>
            JOYSTICK_COMMAND = 0xC1,

            /// <summary>
            /// Press remote stop button.
            /// </summary>
            STOP_BUTTON = 0xC2,

            /// <summary>
            /// Press remote start button.
            /// </summary>
            START_BUTTON = 0xC3,

            /// <summary>
            /// Press auto parking start button remotely.
            /// </summary>
            AUTO_PARKING_START_BUTTON = 0xC4,

            /// <summary>
            /// Request to send camera frame.
            /// </summary>
            CAMERA_FRAME_REQUEST = 0xC5,

            /// <summary>
            /// User's confirmation of mark detection correctness.
            /// </summary>
            USER_RESPONSE = 0xC6,

            /// <summary>
            /// Transmitt current auto parking phase.
            /// </summary>
            AUTOPARKING_PHASE = 0xC7,

            /// <summary>
            /// Camera frame data packet.
            /// </summary>
            CAMERA_FRAME_RESPONSE = 0xD0,

            /// <summary>
            /// Camera frame with detected marks to be confirmed by user.
            /// </summary>
            CAMERA_FRAME_TO_BE_CONFIRMED = 0xD1
        }

        /// <summary>
        /// Enum to determine user response (when mark confirmation via WiFi is demanded).
        /// </summary>
        public enum UserResponseCode : byte
        {
            /// <summary>
            /// User demanded script end.
            /// </summary>
            END = 0,

            /// <summary>
            /// Wrong detection, look further.
            /// </summary>
            LOOK_FURTHER,

            /// <summary>
            /// Marks detected, go on.
            /// </summary>
            MARKS_FOUND
        }

        /// <summary>
        /// Determines current phase of auto parking algorithm.
        /// </summary>
        public enum AutoParkingPhase : byte
        {
            /// <summary>
            /// Idle phase - waiting for start. 
            /// </summary>
            WAITING_FOR_START = 0,

            /// <summary>
            /// Wall-following phase.
            /// </summary>
            WALL_FOLLOWING,

            /// <summary>
            /// Backward drive.
            /// </summary>
            GOING_BACKWARDS,

            /// <summary>
            /// Trying to find marks.
            /// </summary>
            MARKS_TRACKING,

            /// <summary>
            /// Driving to next waypoint.
            /// </summary>
            PATH_FOLLOWING,

            /// <summary>
            /// Algorithm ended.
            /// </summary>
            END
        }

        /// <summary>
        /// Determines whether auto parking phase is in error state.
        /// </summary>
        public enum PhaseError : byte
        {
            /// <summary>
            /// Error occured.
            /// </summary>
            ERROR = 0,

            /// <summary>
            /// No error occured.
            /// </summary>
            NO_ERROR
        }

        /// <summary>
        /// Artificial joystick axis value for arrow button press.
        /// </summary>
        private enum JoystickAxisValue : byte
        { 
            /// <summary>
            /// Maximal value.
            /// </summary>
            MAX = 255,

            /// <summary>
            /// Minimal value.
            /// </summary>
            MIN = 0, 

            /// <summary>
            /// Joystick zero state.
            /// </summary>
            ZERO = 127
        }

        /// <summary>
        /// Enum to determine whether detected marks should be transmitted with the frame.
        /// </summary>
        private enum DetectedMarksTransmit : byte
        { 
            /// <summary>
            /// Don't transmit marks flag.
            /// </summary>
            DONT_TRANSMIT = 0, 

            /// <summary>
            /// Transmit marks flag.
            /// </summary>
            TRANSMIT
        }

        /// <summary>
        /// Main tab control tabs' indexes.
        /// </summary>
        private enum MainTabIndexes : byte
        {
            /// <summary>
            /// Main tab index.
            /// </summary>
            MAIN,

            /// <summary>
            /// Service tab index.
            /// </summary>
            CAMERA,

            /// <summary>
            /// Auto parking phases tab index.
            /// </summary>
            PARKING,

            /// <summary>
            /// Settings tab index.
            /// </summary>
            SETTINGS,

            TABS_COUNT
        }

        #endregion

        # region Private variables

        /// <summary>
        /// Thread to be run separately and receive UDP communication asynchronously.
        /// </summary>
        private Thread UDPreceiveThread;

        /// <summary>
        /// IP Endpoint to receive data.
        /// </summary>
        private IPEndPoint ipEp = new IPEndPoint(IPAddress.Any, UDP_PORT);

        /// <summary>
        /// UDP client to receive data.
        /// </summary>
        private UdpClient incomingUDPClient;

        /// <summary>
        /// UDP client to send data.
        /// </summary>
        private UdpClient outgoingUDPClient;

        /// <summary>
        /// AutoResetEvent to synchronize frame receiving.
        /// </summary>
        private AutoResetEvent resetEventFrameSync;

        /// <summary>
        /// Delegate for invoking subroutines thread-safely.
        /// </summary>
        delegate void InvokeThreadSafeDelegate();

        /// <summary>
        /// Horizontal slide initial point.
        /// </summary>
        private Int32 slideDetectXcoordinate = -1;

        /// <summary>
        /// Length of slide to be detected.
        /// </summary>
        private Byte slideLength = 0;

        /// <summary>
        /// Class to load & save settings.
        /// </summary>
        private SettingsClass mySettings;

        /// <summary>
        /// Last received camera frame.
        /// </summary>
        private Bitmap cameraFrame;

        /// <summary>
        /// Timer to reset device's idle timer periodically.
        /// </summary>
        private System.Threading.Timer deviceIdleTimer;

        #endregion

        #region Miscelaneous methods

          /// <summary>
        /// Visualize current phase.
        /// </summary>
        /// <param name="phase">Current phase.</param>
        /// <param name="isError">True if error occured.</param>
        private void VisualizePhase(AutoParkingPhase phase, Boolean isError)
        {
            //Invoke this function in controls' owner thread
            //Method called from UDPReceiverThread - invoke always
            this.Invoke((InvokeThreadSafeDelegate)delegate()
            {
                Color lblColor = isError ? Color.Red : Color.Lime;

                //Erase all labels
                foreach (Control ctrl in tabPageAutoParking.Controls)
                    if (ctrl is Label)
                        ((Label)ctrl).BackColor = tabPageAutoParking.BackColor;

                //Light up correct label
                switch (phase)
                {
                    default:
                    case AutoParkingPhase.WAITING_FOR_START:

                        labelPhaseWaitToStart.BackColor = lblColor;

                        break;

                    case AutoParkingPhase.WALL_FOLLOWING:

                        labelPhaseWallFollowing.BackColor = lblColor;

                        break;

                    case AutoParkingPhase.GOING_BACKWARDS:

                        labelPhaseBackwardDrive.BackColor = lblColor;

                        break;

                    case AutoParkingPhase.MARKS_TRACKING:

                        labelPhaseDetectingMarks.BackColor = lblColor;

                        break;

                    case AutoParkingPhase.PATH_FOLLOWING:

                        labelPhaseNextWaypoint.BackColor = lblColor;

                        break;

                    case AutoParkingPhase.END:

                        labelPhaseEnd.BackColor = lblColor;

                        break;
                }
            });
        }

        /// <summary>
        /// Receives camera frame via UDP.
        /// </summary>
        /// <param name="includeDetectedMarks">If set to true, detected marks will also be transmitted.</param>
        private void GetCameraFrame(Boolean includeDetectedMarks)
        {
            //Reset ARevent first
            resetEventFrameSync.Reset();

            //Send frame request
            SendUDPPacket(new Byte[] { (Byte)PacketHeader.CAMERA_FRAME_REQUEST, includeDetectedMarks ? (Byte)DetectedMarksTransmit.TRANSMIT : (Byte)DetectedMarksTransmit.DONT_TRANSMIT });

            //Wait for synchronization (ARevent is set within ReceiveCameraFrame() method)
            if (resetEventFrameSync.WaitOne(FRAME_RECEIVE_TIMEOUT_MS, false))
            {
                //Success, draw frame
                Graphics pbGraph = pictureBoxCameraFrame.CreateGraphics();

                pbGraph.DrawImage(cameraFrame,
                    new Rectangle(0, 0, pictureBoxCameraFrame.Width, pictureBoxCameraFrame.Height),
                    new Rectangle(0, 0, cameraFrame.Width, cameraFrame.Height),
                    GraphicsUnit.Pixel);
            }
            else
            {
                MessageBox.Show(FRAME_RECEIVE_TIMEOUT_MS.ToString() + " ms timeout has expired!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
            }
        }

        /// <summary>
        /// Function to be run in separate thread and receive data via UDP.
        /// </summary>
        private void UDPDataReceiver()
        {
            Byte[] inBuffer;

            while (true)
            {
                try
                {
                    try
                    {
                        //Receive
                        inBuffer = incomingUDPClient.Receive(ref ipEp);
                    }
                    catch (SocketException)
                    {
                        //Exit the loop on SocketException
                        break;
                    }

                    //Parse header
                    switch (inBuffer[0])
                    {
                        case (Byte)PacketHeader.WATCHDOG_NOP:
                            //No action
                            break;

                        case (Byte)PacketHeader.CAMERA_FRAME_RESPONSE:
                            //Camera frame received
                            using (MemoryStream inStream = new MemoryStream(inBuffer, CAMERA_FRAME_OFFSET /*Omit packet header*/, inBuffer.Length - CAMERA_FRAME_OFFSET - 1))
                            {
                                cameraFrame = new Bitmap(inStream);

                                //Operation complete signal
                                resetEventFrameSync.Set();
                            }

                            break;

                        case (Byte)PacketHeader.CAMERA_FRAME_TO_BE_CONFIRMED:

                            //Camera frame received, switch to camera tab and display confirmation panel
                            using (MemoryStream inStream = new MemoryStream(inBuffer, CAMERA_FRAME_OFFSET /*Omit packet header*/, inBuffer.Length - CAMERA_FRAME_OFFSET - 1))
                            {
                                //Invoke always
                                this.Invoke((InvokeThreadSafeDelegate)delegate()
                                {
                                    //Switch to camera tab
                                    tabControlMain.SelectedIndex = (Int32)MainTabIndexes.CAMERA;

                                    cameraFrame = new Bitmap(inStream);

                                    panelUserConfirmation.Visible = true;

                                    tabPageCamera.Refresh();

                                    //Draw frame
                                    using(Graphics pbGraph = pictureBoxCameraFrame.CreateGraphics())
                                        pbGraph.DrawImage(cameraFrame,
                                            new Rectangle(0, 0, pictureBoxCameraFrame.Width, pictureBoxCameraFrame.Height),
                                            new Rectangle(0, 0, cameraFrame.Width, cameraFrame.Height),
                                            GraphicsUnit.Pixel);
                                });
                            }

                            break;

                        case (Byte)PacketHeader.AUTOPARKING_PHASE:
                            //Auto parking phase received
                            VisualizePhase((AutoParkingPhase)Enum.Parse(typeof(AutoParkingPhase),inBuffer[1].ToString(),true), 
                                (inBuffer[2] > 0) ? true: false);

                            break;

                        default:
                            //Next loop
                            continue;
                    }
                }
                catch (Exception)
                {
                    //Log error and blink indicator
                }
            }
        }

        /// <summary>
        /// Enables / disables arrow buttons.
        /// </summary>
        /// <param name="enableState"></param>
        private void EnableArrows(Boolean enableState)
        {
            //Go through all controls and enable/disable them
            foreach (Control controlItem in panelArrowContainer.Controls)
                if (controlItem is PictureBox)
                    controlItem.Enabled = enableState;
        }

        /// <summary>
        /// Handler to be called when user performs right slide.
        /// </summary>
        private void RightSlideHandler()
        {
            //Switch to previous tab page
            if (tabControlMain.SelectedIndex > 0)
                tabControlMain.SelectedIndex--;
        }

        /// <summary>
        /// Send packet via UDP, handle errors here.
        /// </summary>
        /// <param name="data">Data array.</param>
        private void SendUDPPacket(Byte[] data)
        {
            try 
            {
                outgoingUDPClient.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(textBoxTargetIP.Text), UDP_PORT));
            }
            catch (Exception)
            {
                //Visualize error
                new Thread(new ThreadStart(delegate() {

                    BlinkSendErrorLabel();

                    Thread.Sleep(SEND_ERROR_LABEL_BLINK_TIMEOUT_MS);

                    BlinkSendErrorLabel();

                })).Start();
            }
        }

        /// <summary>
        /// Blink send error label to indicate transfer error.
        /// </summary>
        private void BlinkSendErrorLabel()
        {
            if (this.InvokeRequired)
            {
                //Invoke this function in controls' owner thread
                this.Invoke(new InvokeThreadSafeDelegate(BlinkSendErrorLabel));
            }
            else
            {
                lock(labelSendError)
                {
                    if (labelSendError.Text == "Send error")
                    {
                        //Erase label
                        labelSendError.Text = "";
                        labelSendError.BackColor = panelArrowContainer.BackColor;
                    }
                    else
                    {
                        //Blink label
                        labelSendError.Text = "Send error";
                        labelSendError.BackColor = Color.Red;
                    }
                }
            }
        }

        /// <summary>
        /// Device idle reset timer callback function.
        /// </summary>
        /// <param name="param">Parameters.</param>
        private void DeviceIdleResetTimerCallback(Object param)
        {
            //Invoke
            try
            {
                this.Invoke(new InvokeThreadSafeDelegate(delegate()
                {
                    if (checkBoxResetIdleTimer.Checked)
                        DevicePowerManagement.ResetIdleTimer();
                }));
            }
            catch (Exception)
            {/*Nothing to be done*/}
        }

        /// <summary>
        /// Handler to be called when user performs left slide.
        /// </summary>
        private void LeftSlideHandler()
        {
            //Switch to next tab page
            if (tabControlMain.SelectedIndex < (tabControlMain.TabPages.Count - 1))
                tabControlMain.SelectedIndex++;
        }

        #endregion

        /// <summary>
        /// Defualt constructor.
        /// </summary>
        public FormMain()
        {
            InitializeComponent();

            //Initialize UDP clients
            outgoingUDPClient = new UdpClient();
            incomingUDPClient = new UdpClient(ipEp);

            //Init resetevent
            resetEventFrameSync = new AutoResetEvent(false);

            //Run UDP receiver thread
            UDPreceiveThread = new Thread(new ThreadStart(UDPDataReceiver));
            UDPreceiveThread.IsBackground = true;
            UDPreceiveThread.Priority = ThreadPriority.Normal;
            UDPreceiveThread.Start();

            //Init device idle reset timer
            deviceIdleTimer = new System.Threading.Timer(new TimerCallback(DeviceIdleResetTimerCallback), null, 0 /*Start immediately*/, 5000 /*Invoke callback every 5s*/);
        }

        #region Auto generated controls events handlers

        private void buttonExit_Click(object sender, EventArgs e)
        {
            //Try to save settings
            try
            {
                mySettings.Save();
            }
            catch (Exception)
            {
                //Inform user
                MessageBox.Show("Failed to save settings!", "Warning!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation,MessageBoxDefaultButton.Button1);
            }
            finally
            {
                //Exit with native exit code
                Application.Exit();
            }
        }

        private void FormMain_MouseDown(object sender, MouseEventArgs e)
        {
            //Store initial point
            if (e.X >= 0 && e.X < this.Width)
                slideDetectXcoordinate = e.X;
            else
                slideDetectXcoordinate = -1;    //Error, store fake point
        }

        private void FormMain_MouseUp(object sender, MouseEventArgs e)
        {
            //Try to detect slide
            if (e.X >= 0 && e.X < this.Width)
            {
                if (Math.Abs(e.X - slideDetectXcoordinate) >= slideLength)
                {
                    if (e.X > slideDetectXcoordinate)
                    {
                        //Slide right
                        RightSlideHandler();
                    }
                    else
                    { 
                        //Slide left
                        LeftSlideHandler();
                    }
                }
            }
        }

        private void FormMain_Load(object sender, EventArgs e)
        {
            #region Read settings

            //New settings class instance -> load from default file location.
            mySettings = new SettingsClass();

            //Load IP address
            textBoxTargetIP.Text = mySettings.TargetIP.ToString();

            //Load gain
            trackBarGain.Value = mySettings.Gain;

            //Load checkboxes' checked states
            checkBoxEnableRemoteStart.Checked = mySettings.RemoteStartEnabled;
            checkBoxResetIdleTimer.Checked = mySettings.ResetIdleTimerEnabled;

            #endregion

            #region Initialize variables

            try
            { 
                slideLength = (Byte)(this.Width / SLIDE_TO_FORM_WIDTH_RATIO);
            }
            catch (ArgumentOutOfRangeException)
            { 
                slideLength = 0;
            }

            #endregion

            //Try to get local IP
            try
            {
                IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());

                foreach (IPAddress ip in host.AddressList)
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                        labelIP.Text = ip.ToString();
            }
            catch (Exception)
            {
                labelIP.Text = "Failed to get local IP.";
            }

            //Erase status labels
            labelWatchdogIndicator.Text = "";
            labelSendError.Text = "";

            //Disable arrows
            EnableArrows(false);
        }

        private void buttonRefreshIP_Click(object sender, EventArgs e)
        {
            //Try to get local IP
            try
            {
                IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());

                foreach (IPAddress ip in host.AddressList)
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                        labelIP.Text = ip.ToString();
            }
            catch (Exception)
            {
                labelIP.Text = "Failed to get local IP.";
            }
        }

        private void pbButtonStart_Click(object sender, EventArgs e)
        {
            //Toggle components' visibility
            pbButtonStart.Visible = false;
            menuItemStart.Enabled = false;
            pbButtonStop.Visible = true;
            menuItemStop.Enabled = true;

            //Enable watchdog
            timerWifiWatchdog.Enabled = true;

            //Enable buttons
            EnableArrows(true);

            //Enable AutoParking start button
            pbButtonStartII.Enabled = true;

            //optionally click PC app's START button remotely
            SendUDPPacket(new Byte[] { (Byte)PacketHeader.START_BUTTON });
        }

        private void pbButtonStop_Click(object sender, EventArgs e)
        {
            //Toggle components' visibility
            pbButtonStop.Visible = false;
            menuItemStop.Enabled = false;
            pbButtonStart.Visible = true;
            menuItemStart.Enabled = true;

            //Disable watchdog
            timerWifiWatchdog.Enabled = false;

            //Disable AutoParking start button
            pbButtonStartII.Enabled = false;

            //Disable buttons
            EnableArrows(false);

            //Erase label
            if (labelWatchdogIndicator.Text == "WIFI COP")
            {
                labelWatchdogIndicator.BackColor = panelArrowContainer.BackColor;
                labelWatchdogIndicator.Text = "";
            }

            //click PC app's STOP button remotely
            SendUDPPacket(new Byte[] { (Byte)PacketHeader.STOP_BUTTON });
        }

        private void pbIcon_MouseDown(object sender, MouseEventArgs e)
        {
            //Show menu
            contextMenuMain.Show(panelTopStrip, new Point(0, 0));
        }

        private void menuItemViewMain_Click(object sender, EventArgs e)
        {
            //Switch tab
            tabControlMain.SelectedIndex = (Int32)MainTabIndexes.MAIN;
        }

        private void ViewSettings_Click(object sender, EventArgs e)
        {
            //Switch tab
            tabControlMain.SelectedIndex = (Int32)MainTabIndexes.SETTINGS;
        }

        private void menuItemViewCamera_Click(object sender, EventArgs e)
        {
            //Switch tab
            tabControlMain.SelectedIndex = (Int32)MainTabIndexes.CAMERA;
        }

        private void tabControlMain_SelectedIndexChanged(object sender, EventArgs e)
        {
            //First enable/disable related menu items
            menuItemManualControl.Enabled = false;
            menuItemViewMain.Enabled = true;
            menuItemViewCamera.Enabled = true;
            menuItemViewParking.Enabled = true;
            menuItemViewSettings.Enabled = true;

            //Switch action according to selected index
            switch (tabControlMain.SelectedIndex)
            {
                default:
                case (Int32)MainTabIndexes.MAIN:

                    menuItemManualControl.Enabled = true;
                    menuItemViewMain.Enabled = false;

                    break;

                case (Int32)MainTabIndexes.CAMERA:

                    menuItemViewCamera.Enabled = false;

                    break;

                case (Int32)MainTabIndexes.PARKING:

                    menuItemViewParking.Enabled = false;

                    break;

                case (Int32)MainTabIndexes.SETTINGS:

                    menuItemViewSettings.Enabled = false;

                    break;
            }
        }

        private void pbButtonStop_MouseDown(object sender, MouseEventArgs e)
        {
            pbButtonStop.BackColor = PRESSED_BUTON_BACKGROUD_COLOR;
        }

        private void pbButtonStop_MouseUp(object sender, MouseEventArgs e)
        {
            pbButtonStop.BackColor = tabPageMain.BackColor;
        }

        private void pbButtonStart_MouseDown(object sender, MouseEventArgs e)
        {
            pbButtonStart.BackColor = PRESSED_BUTON_BACKGROUD_COLOR;
        }

        private void pbButtonStart_MouseUp(object sender, MouseEventArgs e)
        {
            pbButtonStart.BackColor = tabPageMain.BackColor;
        }

        private void pbUL_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbUL.Left - ARROW_BORDER_THICKNESS, pbUL.Top - ARROW_BORDER_THICKNESS, pbUL.Width + 2 * ARROW_BORDER_THICKNESS, pbUL.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MIN, (Byte)JoystickAxisValue.MAX, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbUL_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbU_MouseDown(object sender, MouseEventArgs e)
        {
            //Visualize
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbU.Left - ARROW_BORDER_THICKNESS, pbU.Top - ARROW_BORDER_THICKNESS, pbU.Width + 2 * ARROW_BORDER_THICKNESS, pbU.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND,(Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.MAX, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbU_MouseUp(object sender, MouseEventArgs e)
        {
            //Visualize
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbUR_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbUR.Left - ARROW_BORDER_THICKNESS, pbUR.Top - ARROW_BORDER_THICKNESS, pbUR.Width + 2 * ARROW_BORDER_THICKNESS, pbUR.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MAX, (Byte)JoystickAxisValue.MAX, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbUR_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbL_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbL.Left - ARROW_BORDER_THICKNESS, pbL.Top - ARROW_BORDER_THICKNESS, pbL.Width + 2 * ARROW_BORDER_THICKNESS, pbL.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MIN, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbL_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbStop_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbStop.Left - ARROW_BORDER_THICKNESS, pbStop.Top - ARROW_BORDER_THICKNESS, pbStop.Width + 2 * ARROW_BORDER_THICKNESS, pbStop.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.STOP_BUTTON };
            SendUDPPacket(packetOut);
        }

        private void pbStop_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();
        }

        private void pbR_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbR.Left - ARROW_BORDER_THICKNESS, pbR.Top - ARROW_BORDER_THICKNESS, pbR.Width + 2 * ARROW_BORDER_THICKNESS, pbR.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MAX, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbR_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbDL_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbDL.Left - ARROW_BORDER_THICKNESS, pbDL.Top - ARROW_BORDER_THICKNESS, pbDL.Width + 2 * ARROW_BORDER_THICKNESS, pbDL.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MAX, (Byte)JoystickAxisValue.MIN, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbDL_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbD_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbD.Left - ARROW_BORDER_THICKNESS, pbD.Top - ARROW_BORDER_THICKNESS, pbD.Width + 2 * ARROW_BORDER_THICKNESS, pbD.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.MIN, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbD_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbDR_MouseDown(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                new Rectangle(pbDR.Left - ARROW_BORDER_THICKNESS, pbDR.Top - ARROW_BORDER_THICKNESS, pbDR.Width + 2 * ARROW_BORDER_THICKNESS, pbDR.Height + 2 * ARROW_BORDER_THICKNESS));

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.MIN, (Byte)JoystickAxisValue.MIN, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void pbDR_MouseUp(object sender, MouseEventArgs e)
        {
            Graphics gr = panelArrowContainer.CreateGraphics();
            gr.Clear(panelArrowContainer.BackColor);

            gr.Dispose();

            //Create and send UDP packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.JOYSTICK_COMMAND, (Byte)JoystickAxisValue.ZERO, (Byte)JoystickAxisValue.ZERO, (Byte)trackBarGain.Value };
            SendUDPPacket(packetOut);
        }

        private void buttonSaveSettings_Click(object sender, EventArgs e)
        {
            //Save IP using SettingsClass
            try
            {
                //set values
                mySettings.TargetIP = IPAddress.Parse(textBoxTargetIP.Text);
                mySettings.ResetIdleTimerEnabled = checkBoxResetIdleTimer.Checked;
                mySettings.RemoteStartEnabled = checkBoxEnableRemoteStart.Checked;

                //Save settings
                mySettings.Save();
            }
            catch (Exception)
            {
                MessageBox.Show("Failed to save settings!", "Warning!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
            }
        }

        private void textBoxTargetIP_GotFocus(object sender, EventArgs e)
        {
            //Show keyboard
            inputPanelKeyboard.Enabled = true;
        }

        private void textBoxTargetIP_LostFocus(object sender, EventArgs e)
        {
            //Hide keyboard
            inputPanelKeyboard.Enabled = false;
        }

        private void trackBarGain_ValueChanged(object sender, EventArgs e)
        {
            //Refresh label
            labelGain.Text = trackBarGain.Value.ToString();

            //Save to settings
            mySettings.Gain = (Byte)trackBarGain.Value;
        }

        private void FormMain_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //Save settings
            try
            {
                mySettings.Save();
            }
            catch (Exception)
            { /*Nothing to be done*/ }
        }

        private void timerWifiWatchdog_Tick(object sender, EventArgs e)
        {
            //Create and send UDP watchdog packet
            Byte[] packetOut = new Byte[] { (Byte)PacketHeader.WATCHDOG_NOP };
            SendUDPPacket(packetOut);

            //Blink label
            if (labelWatchdogIndicator.Text == "WIFI COP")
            {
                labelWatchdogIndicator.BackColor = panelArrowContainer.BackColor;
                labelWatchdogIndicator.Text = "";
            }
            else
            {
                labelWatchdogIndicator.BackColor = Color.LimeGreen;
                labelWatchdogIndicator.Text = "WIFI COP";
            }
        }

        private void buttonReceiveFrame_Click(object sender, EventArgs e)
        {
            GetCameraFrame(false);
        }

        private void buttonReceiveFrameMarks_Click(object sender, EventArgs e)
        {
            GetCameraFrame(true);
        }

        private void menuItemCameraFrameWithoutMarks_Click(object sender, EventArgs e)
        {
            //Switch tabs and get frame
            tabControlMain.SelectedIndex = (Byte)MainTabIndexes.CAMERA;

            //Force tabcontrol to redraw first
            tabControlMain.Refresh();

            GetCameraFrame(false);
        }

        private void menuItemCameraFrameWithMarks_Click(object sender, EventArgs e)
        {
            //Switch tabs and get frame
            tabControlMain.SelectedIndex = (Byte)MainTabIndexes.CAMERA;

            //Force tabcontrol to redraw first
            tabControlMain.Refresh();

            GetCameraFrame(true);
        }

        private void checkBoxEnableRemoteStart_CheckStateChanged(object sender, EventArgs e)
        {
            //Save setting
            mySettings.RemoteStartEnabled = checkBoxEnableRemoteStart.Checked;
        }

        private void checkBoxResetIdleTimer_CheckStateChanged(object sender, EventArgs e)
        {
            //Save setting
            mySettings.ResetIdleTimerEnabled = checkBoxResetIdleTimer.Checked;
        }

        private void menuItemViewParking_Click(object sender, EventArgs e)
        {
            //Switch tab
            tabControlMain.SelectedIndex = (Byte)MainTabIndexes.PARKING;
        }

        private void pictureBoxStartII_MouseDown(object sender, MouseEventArgs e)
        {
            //Visualize
            using (Graphics gr = tabPageAutoParking.CreateGraphics())
                gr.FillRectangle(new SolidBrush(PRESSED_BUTON_BACKGROUD_COLOR),
                    new Rectangle(pbButtonStartII.Left - ARROW_BORDER_THICKNESS, pbButtonStartII.Top - ARROW_BORDER_THICKNESS, pbButtonStartII.Width + 2 * ARROW_BORDER_THICKNESS, pbButtonStartII.Height + 2 * ARROW_BORDER_THICKNESS));
        }

        private void pictureBoxStartII_MouseUp(object sender, MouseEventArgs e)
        {
            //Visualize
            using (Graphics gr = tabPageAutoParking.CreateGraphics())
                gr.Clear(tabPageAutoParking.BackColor);

            //Send start command
            SendUDPPacket(new Byte[] {(Byte)PacketHeader.AUTO_PARKING_START_BUTTON });
        }

        private void buttonStopDetection_Click(object sender, EventArgs e)
        {
            //Send confirmation and disable the panel
            SendUDPPacket(new Byte[] {(Byte)PacketHeader.USER_RESPONSE, (Byte)UserResponseCode.END });

            panelUserConfirmation.Visible = false;
        }

        private void buttonRedetect_Click(object sender, EventArgs e)
        {
            //Send confirmation and disable the panel
            SendUDPPacket(new Byte[] { (Byte)PacketHeader.USER_RESPONSE, (Byte)UserResponseCode.LOOK_FURTHER });

            panelUserConfirmation.Visible = false;
        }

        private void buttonMarksDetectedOK_Click(object sender, EventArgs e)
        {
            //Send confirmation and disable the panel
            SendUDPPacket(new Byte[] { (Byte)PacketHeader.USER_RESPONSE, (Byte)UserResponseCode.MARKS_FOUND});

            panelUserConfirmation.Visible = false;
        }

        #endregion
    }
}