using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;

namespace MoveTest
{
    public partial class Form1 : Form
    {
        FileStream fS;          //Stream for correct file access
        StreamReader sR;        //Stream for nice reading
        Int64 seekPosition;     //Store current read position, probably not needed?

        double xPosNum = 0; double yPosNum = 0;     //Creates and instantiates
        double zPosNum = 0; double xVelNum = 0;     //variables to store axis
        double yVelNum = 0; double zVelNum = 0;     //position and velocity

        int currentRow = 0;         //Current row in image to print
        int currentColumn = 0;      //Current column in image to print

        bool active = false;    //Used to determine if communications to controller open
        bool runPrint = false;  //Controls termination of print and motion loops

        Bitmap pngFile;         //Stores the png file in memory

        SerialCommunications serialCom;                 //For sending/receiving serial data
        delegate void SetTextCallback(string text);     //For ensuring form controls are manipulated on the correct thread

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>Is called when the button labelled Initialise is called. Initialises dll/controller, sets up streams to read in log information.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void initialise_Click(object sender, EventArgs e)
        {
            uint initResult = MoveClass.initialise();   //Call the dll function initialise and store the result

            if (initResult == 0)                        //No error
            {
                fS = new FileStream("log.txt", 
                    FileMode.Open, FileAccess.Read, 
                    FileShare.ReadWrite);               //Open filestream with correct access priorities
                sR = new StreamReader(fS);              //Open stream to read data

                updateFromLog.Enabled = true;           //Start timer to read from log file
                active = true;                          //It's running now
            }
            else                                        //Error!
            {
               MessageBox.Show("Failed", 
                   Convert.ToString(initResult));       //Display error
            }
        
        }

        /// <summary>Is called when button labelled Disable drives is clicked. Disables the drives and changes status label.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void disable_Click(object sender, EventArgs e)
        {
            uint disableResult = MoveClass.disableAxes();   //Call dll function to disable the axes and store result

            if (disableResult == 0)                         //No error
            {
                driveStatus.Text = "Drives disabled";       //Display this in text box
            }
            else                                            //Error
            {
                MessageBox.Show("Disable failed", 
                    Convert.ToString(disableResult));       //Display message and error code
            }
        }

        /// <summary>Is called when button labelled Move axis is clicked. Moves an axis...</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void moveAxis_Click(object sender, EventArgs e)
        {
            uint moveResult;            //Initialise variable to store result of move operation

            int distance = Convert.ToInt32(distanceBox.Text);   //Convert text from boxes to
            uint speed = Convert.ToUInt32(feedrateBox.Text);    //numbers and store in variables

            if (xButton.Checked) moveResult = MoveClass.incX(distance, speed);  //Check which axis button is selected
            if (yButton.Checked) moveResult = MoveClass.incY(distance, speed);  //and call appropriate dll function
            if (zButton.Checked) moveResult = MoveClass.incZ(distance, speed);  //with the above variabales as parameters

        }

        //Sort this tat out
        private void moveAbsoluteButton_Click(object sender, EventArgs e)
        {
            int[] distanceArray = new int[] { Convert.ToInt32(absX.Text), 
                Convert.ToInt32(absY.Text), Convert.ToInt32(absZ.Text) };

            uint feedrate = Convert.ToUInt32(feedrateBox.Text);
            uint[] speedArray = new uint[] { feedrate, feedrate, feedrate };

            uint errReturn = MoveClass.moveAbsolute(ref distanceArray, ref speedArray);
        }

        /// <summary>Called when button labelled Enable Drives is clicked. Enables the drives and changes status label.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void enableDrives_Click(object sender, EventArgs e)
        {
            uint initResult = MoveClass.enableAxes();   //Calls dll function to enable the drives and stores returned error result

            if (initResult == 0)                        //No error
            { 
                driveStatus.Text = "Drives enabled";    //Change status label
            }
            else                                        //Error
            { MessageBox.Show("Drive enable Failed", 
                Convert.ToString(initResult)); }        //Display it
        }

        /// <summary>Called when form closes, used to clean up file and controller resources</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (active)                     //When controller has not been explicitly shutdown
            {
                MoveClass.disableAxes();    //Disable and release axes, might be done by shutdown?
                MoveClass.shutdown();       //Shuts down communications
            }

            if (fS != null)                 //Only when initialise has succeeded and log file opened
            {
                fS.Close();                 //Close both streams
                sR.Close();
            }
      
            if (serialCom != null)          //When serial port communications have been used
            {
                serialCom.Dispose();        //Clean up serial port
            }
        }

        /// <summary>Called 10 times per second to update text box with log from dll</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updateFromLog_Tick(object sender, EventArgs e)
        {
            sR.BaseStream.Seek(seekPosition, SeekOrigin.Begin); //Sets cursor position to last read from point, unnecessary?
            logBox.Text += sR.ReadToEnd();                      //Reads all text from current point in log and appends to text box
            seekPosition = sR.BaseStream.Position;              //Sets variable to current file position, unnecessary?

            logBox.Select(logBox.Text.Length, 0);       //Positions text box cursor at the end
            logBox.ScrollToCaret();                     //and scrolls to it
        }

        /// <summary>Called when button labelled SHUTDOWN is pressed. Shuts down</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void stopButton_Click(object sender, EventArgs e)
        {
            updatePosVelTimer.Enabled = false;  //Stop updating position and velocity information
            MoveClass.shutdown();               //Calls dll function to close communication with the controller
            active = false;                     //Stopped running
        }

        /// <summary>Updates text boxes with stage's current position and velocity</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updatePosVelTimer_Tick(object sender, EventArgs e)
        {
            xPosNum = MoveClass.getPosition(0);             //Get the position
            yPosNum = MoveClass.getPosition(1);             //of each of the
            zPosNum = MoveClass.getPosition(2);             //axes

            xVelNum = MoveClass.getVelocity(0);             //Get the velocity
            yVelNum = MoveClass.getVelocity(1);             //of each of the
            zVelNum = MoveClass.getVelocity(2);             //axes

            xPos.Text = "x: " + Convert.ToString(xPosNum);  //Place numbers into
            yPos.Text = "y: " + Convert.ToString(yPosNum);  //appropriate text boxes
            zPos.Text = "z: " + Convert.ToString(zPosNum);
            xVel.Text = "x: " + Convert.ToString(xVelNum);
            yVel.Text = "y: " + Convert.ToString(yVelNum);
            zVel.Text = "z: " + Convert.ToString(zVelNum);
        }

        /// <summary>Called when button labelled Update/Stop is pressed. Enables/Disables timer to update position and velocity information</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updatePosVelButton_Click(object sender, EventArgs e)
        {
            if (updatePosVelTimer.Enabled)              //When timer is running
            {
                updatePosVelTimer.Enabled = false;      //Turn it off
                updatePosVelButton.Text = "Update";     //And reset button label
            }
            else                                        //When timer is not running
            {
                updatePosVelTimer.Enabled = true;       //Turn it on
                updatePosVelButton.Text = "Stop";       //And set label to Stop
            }
        }

        /// <summary>Is called when the form first loads</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            foreach (string s in System.IO.Ports.SerialPort.GetPortNames()) //Step through each available com port
                comPortsBox.Items.Add(s);                                   //Add it to the combo box
        }

        /// <summary>Is called when the button labelled open is pressed. Displays a file open dialog and sets up drawing objects</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openImageButton_Click(object sender, EventArgs e)
        {
            DialogResult dialogResult = openFileDialog1.ShowDialog();   //Open the file dialog and store the result

            if (dialogResult == DialogResult.OK)                        //User clicked ok in dialog
            {
                firstImageBox.Load(openFileDialog1.FileName);           //Load selected image into first picture box
                pngFile = (Bitmap)firstImageBox.Image;                  //Create a bitmap in memory from this picture
            }
        }

        private void runPretendCycleButton_Click(object sender, EventArgs e)
        {         
            int width = (int)pngFile.Width * Convert.ToInt32(pixelSizeBox.Text);
            int height = (int)pngFile.Height * Convert.ToInt32(pixelSizeBox.Text);
            int pixelSize = int.Parse(pixelSizeBox.Text);
            int tolerance = int.Parse(toleranceBox.Text);

            double initialX = MoveClass.getPosition(0);
            double initialY = MoveClass.getPosition(1);

            Graphics drawResults = resultImageBox.CreateGraphics();
            
            Bitmap blackDot = new Bitmap(1, 1);
            Bitmap whiteDot = new Bitmap(1, 1);

            blackDot.SetPixel(0, 0, Color.Black);
            whiteDot.SetPixel(0, 0, Color.White);

            runPrint = true;

            currentColumn = 0;

            double[,] positionArray = new double[pngFile.Width, 2];


            for (int i = 0; i < pngFile.Width; ++i)
            {
                positionArray[i, 0] = initialX + (i * pixelSize) - tolerance;
                positionArray[i, 1] = initialX + (i * pixelSize) + tolerance;
            }

            drawResults.Clear(Color.Salmon);

            MoveClass.incX(-(int.Parse(overrunBox.Text)), 5000000);

            Thread motionThread = new Thread(new ThreadStart(MotionLoop));
            motionThread.Start();

            while (runPrint)
            {
                xPosNum = MoveClass.getPosition(0);             //Get the position

                if ((xPosNum > positionArray[currentColumn, 0]) & (xPosNum < positionArray[currentColumn, 1]))
                {
                    panel1.BackColor = pngFile.GetPixel(currentColumn, currentRow);

                    printStatusBox.Text = Convert.ToString(currentColumn) + "," + Convert.ToString(currentRow) +
                       ": " + pngFile.GetPixel(currentColumn, currentRow).ToString() + "\n\r";

                    if (pngFile.GetPixel(currentColumn, currentRow) == Color.FromArgb(255, 0, 0, 0))
                        drawResults.DrawImageUnscaled(blackDot, currentColumn, currentRow);
                    else
                        drawResults.DrawImageUnscaled(whiteDot, currentColumn, currentRow);

                    if ((currentRow & 1) == 0)
                    {
                        if (currentColumn < (pngFile.Width - 1))
                            ++currentColumn; 
                        else
                            currentColumn = pngFile.Width - 1;
                    }
                    else
                    {
                        if (currentColumn > 0)
                            --currentColumn;
                        else
                            currentColumn = 0;
                    }
                }

                Application.DoEvents();
            }

            drawResults.Dispose();


        }

        /// <summary>Is called when the button labelled Open is clicked, sets up com port and communications to the microcontroller</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openSerialPortButton_Click(object sender, EventArgs e)
        {
            if (serialCom != null)                          //Check if instance already exists
                serialCom.Dispose();                        //If so, cleanup
            
            serialCom = new SerialCommunications(comPortsBox.Text, int.Parse(baudRateComboBox.Text));   //Create new instance of class with given com port and baud rate

            byte[] nullByte = new byte[1] { 0x00 };         //Create a null byte to send to initialise communications

            while (true)                                    //Loop indefinitely until microcontroller responds
            {
                serialCom.Send(nullByte);                   //Sends the null byte

                Application.DoEvents();                     //Wait a short time so as not to spam com port or lock program execution

                int result = 0;                                                 //Initialise variable
                try { result = serialCom.serialPort.ReadByte(); }               //Try to read a byte from the receive buffer
                catch (TimeoutException exception) { exception.ToString(); }    //Only so that timeout on reading a byte doesn't crash the program
                
                if (result == 0x01)                 //If the byte's 00000001
                {
                    byte[] fudgeByte = new byte[1] { 0x0D };         //fudge character
                    serialCom.Send(fudgeByte);                        //Sends the null byte
                    break;                          //break out of the loop
                }
                this.openSerialPortButton.Enabled = false;  //this button needs to be disabled because if it clicked again it WILL hang the computer!
            }

            serialCom.serialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(serialPort_DataReceived);

            serialPort_DataReceived(sender, null);
        }

        /// <summary>Is called by data received event of the serial port</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            
            //MessageBox.Show(serialCom.serialPort.ReadExisting());
            string input = serialCom.serialPort.ReadExisting();                         //Read text in buffer
            

            if (this.receiveSerialDataBox.InvokeRequired)                               //Checks if main thread is required to alter received text box
            {
                SetTextCallback d = new SetTextCallback(setText);                       //Sets up the delegate
                this.Invoke(d, new object[] { input });                                 //and calls it
            }
            else                                                                        //Current thread is acceptable
            {
                receiveSerialDataBox.Text += input;// +"\n";                              //Add text and new line to receive box
                receiveSerialDataBox.Select(receiveSerialDataBox.Text.Length, 0);       //Set cursor position
                receiveSerialDataBox.ScrollToCaret();                                   //and scroll to bottom of text
            }
        }

        /// <summary>Is called by a Marshall invoke delegate to add text to receive box on main thread</summary>
        /// <param name="text">Text string to add</param>
        void setText(string text)
        {
            receiveSerialDataBox.Text += text;// +"\n";                           //Add text and new line to receive box
            receiveSerialDataBox.Select(receiveSerialDataBox.Text.Length, 0);   //Set cursor position
            receiveSerialDataBox.ScrollToCaret();                               //and scroll to bottom of text
        }

        /// <summary>Is called when the button labelled Send is clicked</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void sendSerialDataButton_Click(object sender, EventArgs e)
        {
            if (serialCom != null)                                                      //True if serial port exists
            {
                //serialCom.Send(sendDataToSerialBox.Text);                               //Send text
                byte[] buffer = ASCIIEncoding.UTF8.GetBytes(sendDataToSerialBox.Text);
                Array.Resize<byte>(ref buffer, buffer.Length + 1);
                buffer[buffer.GetUpperBound(0)] = 13;           //decimal 13 = 0x0D
                //buffer[buffer.GetUpperBound(0) - 1] = 13;
                
                serialCom.Send(buffer);
                if (sendDataToSerialBox.Text == "reset") this.openSerialPortButton.Enabled = true;  //this enables the open port button because the PIC has been reset comms need to be re-initialised
                
                sentSerialDataBox.Text += /*"\n" +*/ sendDataToSerialBox.Text + "\n";     //Add text to sent message log box
                sendDataToSerialBox.Clear();                                            //Clear input box

                sentSerialDataBox.Select(sentSerialDataBox.Text.Length, 0);             //Set cursor position
                sentSerialDataBox.ScrollToCaret();                                      //and scroll to bottom of text
            }
        }

        /// <summary>Sends text to serial port when enter is pressed</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void sendDataToSerialBox_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter & serialCom != null)            //True when enter is pressed in focus of textbox and serial port has been created
            {
                e.Handled = true;

                //chris' bodge
                byte[] buffer = ASCIIEncoding.UTF8.GetBytes(sendDataToSerialBox.Text);
                Array.Resize<byte>(ref buffer, buffer.Length -1);
                
                serialCom.Send(buffer);
                if (sendDataToSerialBox.Text == "reset") this.openSerialPortButton.Enabled = true;//this enables the open port button because the PIC has been reset comms need to be re-initialised

                sentSerialDataBox.Text += /*"\n" +*/ sendDataToSerialBox.Text;          //Add text to sent message log box
                sendDataToSerialBox.Clear();                                            //Clear input box

                sentSerialDataBox.Select(sentSerialDataBox.Text.Length, 0);             //Set cursor position
                sentSerialDataBox.ScrollToCaret();                                      //and scroll to bottom of text
                //end of chris' bodge             

                //sendSerialDataButton_Click(sender, new EventArgs());    //Call function for send button click
                
            }
        }

        /// <summary>Changes the interval between position/velocity timer ticks</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updateTimerIntervalScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            updatePosVelTimer.Interval = updateTimerIntervalScrollBar.Value;
        }

        /// <summary>Is called when the button labelled cancel is clicked, stops processing print instructions and stops motion at the end of the current cycle</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cancelPrintButton_Click(object sender, EventArgs e)
        {
            runPrint = false;
        }

        /// <summary>The function run in a separate thread to allow the motion to be carried out synchronously without blocking GUI and printing execution</summary>
        private void MotionLoop()
        {
            int width = (int)pngFile.Width * Convert.ToInt32(pixelSizeBox.Text);    //Sets width of x motion from image width and pixel width, in machine steps
            uint feedrate = uint.Parse(printRunFeedrateBox.Text);                   //Sets feedrate from text box, in machine steps per second
            int overrun = int.Parse(overrunBox.Text);                               //Sets overrun for x motion from box, in machine steps

            for (int j = 0; j < pngFile.Height; ++j)                                //Loop through each row
            {
                if (runPrint == false)                                              //Set to false if cancel is pressed
                    break;                                                          //Exits the loop

                MoveClass.incY(Convert.ToInt32(pixelSizeBox.Text), feedrate);       //Increments y axis by one row synchronously
                currentRow = j;                                                     //Sets current row for printing to current motion step

                Application.DoEvents();                                             //Wait a short time between motions

                MoveClass.incX((width + 2 * overrun) * (int)Math.Pow(-1, j), feedrate); //Increments x axis across the columns synchronously

                Application.DoEvents();                                             //Wait a short time

            }

            runPrint = false;                                                       //Exits the printing instructions loop
        }
        
    }
}