/*
* File name: MILApplication.cs
* Location: See Matrox Example Launcher in the MIL Control Center
* 
*/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows;
using Matrox.MatroxImagingLibrary;

//********************************************************************************
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved
//********************************************************************************

namespace MILApplicationLibrary
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// An object that encapsulates the functionality of a simple MIL application.
    /// </summary>
    /// <remarks>
    /// <list type="bullet">
    /// <item>This object implements the <see cref="INotifyPropertyChanged"/> interface to allow data binding.</item>
    /// </list>
    /// </remarks>
    public class MILApplication : INotifyPropertyChanged
    {
        #region Constants

        // Default image dimensions.
        private const int DEFAULT_IMAGE_SIZE_X = 640;
        private const int DEFAULT_IMAGE_SIZE_Y = 480;
        private const int DEFAULT_IMAGE_SIZE_BAND = 1;

        #endregion

        #region Constructor

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Creates a new unallocated MILApplication object.
        /// </summary>
        public MILApplication()
        {
            _appId = MIL.M_NULL;
            _sysId = MIL.M_NULL;
            _digId = MIL.M_NULL;
            _dispId = MIL.M_NULL;
            _bufId = MIL.M_NULL;
        }

        #endregion

        #region Public methods

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Allocates a MIL application, system, display, image buffer and digitizer (if available).
        /// </summary>
        public void Allocate()
        {
            // Allocate a MIL application.
            MIL.MappAlloc(MIL.M_NULL, MIL.M_DEFAULT, ref _appId);

            // Tell MIL to throw exceptions when an error occurs.
            // The type of Exception thrown by MIL functions is MILException
            MIL.MappControl(_appId, MIL.M_ERROR, MIL.M_THROW_EXCEPTION);

            try
            {
                // Allocate a MIL system.
                MIL.MsysAlloc(MIL.M_DEFAULT, "M_DEFAULT", MIL.M_DEFAULT, MIL.M_DEFAULT, ref _sysId);
            }
            catch (MILException exception)
            {
                Console.WriteLine("System Allocation Error : {0}", exception.Message);
                MessageBox.Show(exception.Message, "System Allocation Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            if (_sysId != MIL.M_NULL)
            {
                // Allocate a MIL display.
                MIL.MdispAlloc(_sysId, MIL.M_DEFAULT, "M_DEFAULT", MIL.M_WPF, ref _dispId);

                // Set default values for the image buffer in case no digitizer can be allocated.
                MIL_INT bufferSizeX = DEFAULT_IMAGE_SIZE_X;
                MIL_INT bufferSizeY = DEFAULT_IMAGE_SIZE_Y;
                MIL_INT bufferSizeBand = DEFAULT_IMAGE_SIZE_BAND;
                long imageAttributes = MIL.M_IMAGE | MIL.M_DISP | MIL.M_PROC;

                // Inquire the number of digitizers for the system.
                MIL_INT numberOfDigitizers = MIL.MsysInquire(_sysId, MIL.M_DIGITIZER_NUM, MIL.M_NULL);
                if (numberOfDigitizers > 0)
                    {
                    try
                        {
                        // Allocate a digitizer.
                        MIL.MdigAlloc(_sysId, MIL.M_DEFAULT, "M_DEFAULT", MIL.M_DEFAULT, ref _digId);
                        }
                    catch (MILException exception)
                        {
                        Console.WriteLine("Digitizer Allocation Error : {0}", exception.Message);
                        MessageBox.Show(exception.Message, "Digitizer Allocation Error", MessageBoxButton.OK, MessageBoxImage.Error);
                        }

                    if (_digId != MIL.M_NULL)
                        {
                        // Inquire the digitizer to determine the image buffer size.
                        bufferSizeBand = MIL.MdigInquire(_digId, MIL.M_SIZE_BAND, MIL.M_NULL);
                        bufferSizeX = MIL.MdigInquire(_digId, MIL.M_SIZE_X, MIL.M_NULL);
                        bufferSizeY = MIL.MdigInquire(_digId, MIL.M_SIZE_Y, MIL.M_NULL);

                        // Add the M_GRAB attibute to the image buffer.
                        imageAttributes |= MIL.M_GRAB;
                        }
                    }

                // Notify the UI that grabbing options have changed.
                RaisePropertyChangedEvent("CanStartGrab");
                RaisePropertyChangedEvent("CanStopGrab");

                // Allocate the image buffer.
                MIL.MbufAllocColor(_sysId, bufferSizeBand, bufferSizeX, bufferSizeY, 8 + MIL.M_UNSIGNED, imageAttributes, ref _bufId);

                // Notify the UI that the buffer size changed.
                RaisePropertyChangedEvent("BufferSizeX");
                RaisePropertyChangedEvent("BufferSizeY");

                // Fill the buffer with a default message.
                FillImageBuffer(bufferSizeX, bufferSizeY);

                // Select the image buffer to display.
                MIL.MdispSelect(_dispId, _bufId);

                // Initialize the overlay.  It won't be visible by default
                InitializeOverlay();
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Starts the grab on the digitizer.
        /// </summary>
        /// <remarks>
        /// This method is called from the StartGrab_Click method of the main window when the 
        /// user clicks the Stop Grab button in the UI.
        /// </remarks>
        public void StartGrab()
        {
            MIL.MdigGrabContinuous(_digId, _bufId);

            _isGrabbing = true;

            // Notify the UI that grabbing options have changed.
            RaisePropertyChangedEvent("CanStartGrab");
            RaisePropertyChangedEvent("CanStopGrab");
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Stops the grab on the digitizer.
        /// </summary>
        /// <remarks>
        /// This method is called from the StopGrab_Click method of the main window when the 
        /// user clicks the Stop Grab button in the UI.
        /// </remarks>
        public void StopGrab()
        {
            MIL.MdigHalt(_digId);

            _isGrabbing = false;

            // Notify the UI that grabbing options have changed.
            RaisePropertyChangedEvent("CanStartGrab");
            RaisePropertyChangedEvent("CanStopGrab");
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Frees the MIL Application and its associated resources.
        /// </summary>
        public void Free()
        {
            // Stop the grab if necessary.
            if (CanStopGrab)
            {
                StopGrab();
            }

            // Free the display.
            if (_dispId != MIL.M_NULL)
            {
                // If an image buffer is selected on the display, deselect it before freeing the display.
                MIL_ID selectedBufferId = (MIL_ID)MIL.MdispInquire(_dispId, MIL.M_SELECTED, MIL.M_NULL);
                if (selectedBufferId != MIL.M_NULL)
                {
                    MIL.MdispSelect(_dispId, MIL.M_NULL);
                }

                MIL.MdispFree(_dispId);
                _dispId = MIL.M_NULL;
            }

            // Free the image buffer.
            if (_bufId != MIL.M_NULL)
            {
                MIL.MbufFree(_bufId);
                _bufId = MIL.M_NULL;
            }

            // Free the digitizer.
            if (_digId != MIL.M_NULL)
            {
                MIL.MdigFree(_digId);
                _digId = MIL.M_NULL;
            }

            // Free the system.
            if (_sysId != MIL.M_NULL)
            {
                MIL.MsysFree(_sysId);
                _sysId = MIL.M_NULL;
            }

            // Free the application.
            if (_appId != MIL.M_NULL)
            {
                MIL.MappFree(_appId);
                _appId = MIL.M_NULL;
            }

            // The object has been cleaned up.
            // This call removes the object from the finalization queue and 
            // prevent finalization code object from executing a second time.
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Properties used in data bindings

        public MIL_ID MilDisplayId { get { return _dispId; } }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Returns whether or not the application can start grabbing live images.
        /// </summary>
        /// <remarks>This property is bound to the IsEnabled property of the Start Grab button.</remarks>
        public bool CanStartGrab
        {
            get { return ((_digId != MIL.M_NULL) && (!_isGrabbing)); }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Returns whether or not the application can stop grabbing live images.
        /// </summary>
        /// <remarks>This property is bound to the IsEnabled property of the Stop Grab button.</remarks>
        public bool CanStopGrab
        {
            get { return ((_digId != MIL.M_NULL) && (_isGrabbing)); }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Returns the width of the displayed image buffer.
        /// </summary>
        /// <remarks>This property is bound to the Width property of the WindowsFormsHost control.</remarks>
        public int BufferSizeX
        {
            get { return GetBufferSize(MIL.M_SIZE_X); }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Returns the height of the displayed image buffer.
        /// </summary>
        /// <remarks>This property is bound to the Height property of the WindowsFormsHost control.</remarks>
        public int BufferSizeY
        {
            get { return GetBufferSize(MIL.M_SIZE_Y); }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Gets or set whether or not the display's overlay is visible.
        /// </summary>
        /// <remarks>This property is bound to the IsChecked property of the Checkbox control.</remarks>
        public bool OverlayVisible
        {
            get
            {
                bool overlayVisible = false;

                if (_dispId != MIL.M_NULL)
                {
                    MIL_INT overlayShow = 0;
                    MIL.MdispInquire(_dispId, MIL.M_OVERLAY_SHOW, ref overlayShow);
                    if (overlayShow == MIL.M_ENABLE)
                    {
                        overlayVisible = true;
                    }
                }

                return overlayVisible;
            }

            set
            {
                bool overlayVisible = value;

                if (_dispId != MIL.M_NULL)
                {
                    MIL_INT controlValue = MIL.M_DISABLE;
                    if (overlayVisible)
                    {
                        controlValue = MIL.M_ENABLE;
                        if (!_overlayInitialized)
                        {
                            InitializeOverlay();
                        }
                    }

                    MIL.MdispControl(_dispId, MIL.M_OVERLAY_SHOW, controlValue);
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Helper functions

        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Returns the specified size of the displayed image buffer.
        /// </summary>
        /// <param name="inquireType">The size can be M_SIZE_X or M_SIZE_Y</param>
        /// <returns>Returns the inquired size, or 0 if the buffer is not allocated.</returns>
        private int GetBufferSize(long inquireType)
        {
            int bufferSize = 0;

            if (_bufId != MIL.M_NULL)
            {
                bufferSize = (int)MIL.MbufInquire(_bufId, inquireType, MIL.M_NULL);
            }

            return bufferSize;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Fills the image buffer using MIL drawing.
        /// </summary>
        /// <param name="bufferSizeX">The width of the image buffer.</param>
        /// <param name="bufferSizeY">The height of the image buffer.</param>
        private void FillImageBuffer(MIL_INT bufferSizeX, MIL_INT bufferSizeY)
        {
            // Start by clearing the buffer
            MIL.MbufClear(_bufId, MIL.M_RGB888(0, 0, 0));

            // Fill the buffer with default content.
            MIL_INT defaultGraFont = MIL.MgraInquire(MIL.M_DEFAULT, MIL.M_FONT, MIL.M_NULL);
            MIL.MgraFont(MIL.M_DEFAULT, MIL.M_FONT_DEFAULT_LARGE);
            MIL.MgraText(MIL.M_DEFAULT, _bufId, ((bufferSizeX / 8) * 2), bufferSizeY / 2, " Welcome to MIL !!! ");
            MIL.MgraRect(MIL.M_DEFAULT, _bufId, ((bufferSizeX / 8) * 2) - 60, (bufferSizeY / 2) - 80, ((bufferSizeX / 8) * 2) + 370, (bufferSizeY / 2) + 100);
            MIL.MgraRect(MIL.M_DEFAULT, _bufId, ((bufferSizeX / 8) * 2) - 40, (bufferSizeY / 2) - 60, ((bufferSizeX / 8) * 2) + 350, (bufferSizeY / 2) + 80);
            MIL.MgraRect(MIL.M_DEFAULT, _bufId, ((bufferSizeX / 8) * 2) - 20, (bufferSizeY / 2) - 40, ((bufferSizeX / 8) * 2) + 330, (bufferSizeY / 2) + 60);
            MIL.MgraFont(MIL.M_DEFAULT, defaultGraFont);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Prepares annotations to be shown in the display's overlay.
        /// </summary>
        private void InitializeOverlay()
        {
            MIL_ID defaultGraphicContext = MIL.M_DEFAULT;
            MIL_ID milOverlayImage = MIL.M_NULL;
            MIL_INT imageWidth, imageHeight;
            IntPtr hCustomDC = IntPtr.Zero;

            // Prepare overlay buffer.
            //***************************

            // Enable the display for overlay annotations.
            MIL.MdispControl(_dispId, MIL.M_OVERLAY, MIL.M_ENABLE);

            // Inquire the overlay buffer associated with the display.
            MIL.MdispInquire(_dispId, MIL.M_OVERLAY_ID, ref milOverlayImage);

            // Clear the overlay to transparent.
            MIL.MdispControl(_dispId, MIL.M_OVERLAY_CLEAR, MIL.M_DEFAULT);

            // Disable the overlay display update to accelerate annotations.
            MIL.MdispControl(_dispId, MIL.M_OVERLAY_SHOW, MIL.M_DISABLE);

            // Inquire overlay size.
            imageWidth = MIL.MbufInquire(milOverlayImage, MIL.M_SIZE_X, MIL.M_NULL);
            imageHeight = MIL.MbufInquire(milOverlayImage, MIL.M_SIZE_Y, MIL.M_NULL);

            // Draw MIL overlay annotations.
            //*********************************

            // Set the graphic text background to transparent.
            MIL.MgraControl(defaultGraphicContext, MIL.M_BACKGROUND_MODE, MIL.M_TRANSPARENT);

            // Print a white string in the overlay image buffer.
            MIL.MgraColor(defaultGraphicContext, MIL.M_COLOR_WHITE);
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth / 9, imageHeight / 5, " -------------------- ");
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth / 9, imageHeight / 5 + 25, " - MIL Overlay Text - ");
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth / 9, imageHeight / 5 + 50, " -------------------- ");

            // Print a green string in the overlay image buffer.
            MIL.MgraColor(defaultGraphicContext, MIL.M_COLOR_GREEN);
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth * 11 / 18, imageHeight / 5, " ---------------------");
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth * 11 / 18, imageHeight / 5 + 25, " - MIL Overlay Text - ");
            MIL.MgraText(defaultGraphicContext, milOverlayImage, imageWidth * 11 / 18, imageHeight / 5 + 50, " ---------------------");

            // Draw GDI color overlay annotation.
            //***********************************

            // The next control might not be supported
            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_DISABLE);

            // Create a device context to draw in the overlay buffer with GDI.
            MIL.MbufControl(milOverlayImage, MIL.M_DC_ALLOC, MIL.M_DEFAULT);

            // Inquire the device context.
            hCustomDC = (IntPtr)MIL.MbufInquire(milOverlayImage, MIL.M_DC_HANDLE, MIL.M_NULL);

            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_ENABLE);

            // Perform operation if GDI drawing is supported.
            if (!hCustomDC.Equals(IntPtr.Zero))
            {
                // NOTE : The using blocks will automatically call the Dispose method on the GDI objects.
                //        This ensures that resources are freed even if an exception occurs.

                // Create a System.Drawing.Graphics object from the Device context
                using (Graphics DrawingGraphics = Graphics.FromHdc(hCustomDC))
                {
                    using (Pen DrawingPen = new Pen(Color.Blue))
                    {
                        // Draw a blue cross in the overlay image
                        DrawingGraphics.DrawLine(DrawingPen, 0, (int)(imageHeight / 2), imageWidth, (int)(imageHeight / 2));
                        DrawingGraphics.DrawLine(DrawingPen, (int)(imageWidth / 2), 0, (int)(imageWidth / 2), imageHeight);

                        // Prepare transparent text annotations.
                        // Define the Brushes and fonts used to draw text
                        using (SolidBrush LeftBrush = new SolidBrush(Color.Red))
                        {
                            using (SolidBrush RightBrush = new SolidBrush(Color.Yellow))
                            {
                                using (Font OverlayFont = new Font(FontFamily.GenericSansSerif, 10, System.Drawing.FontStyle.Bold))
                                {
                                    // Write text in the overlay image
                                    SizeF GDITextSize = DrawingGraphics.MeasureString("GDI Overlay Text", OverlayFont);
                                    DrawingGraphics.DrawString("GDI Overlay Text", OverlayFont, LeftBrush, System.Convert.ToInt32(imageWidth / 4 - GDITextSize.Width / 2), System.Convert.ToInt32(imageHeight * 3 / 4 - GDITextSize.Height / 2));
                                    DrawingGraphics.DrawString("GDI Overlay Text", OverlayFont, RightBrush, System.Convert.ToInt32(imageWidth * 3 / 4 - GDITextSize.Width / 2), System.Convert.ToInt32(imageHeight * 3 / 4 - GDITextSize.Height / 2));
                                }
                            }
                        }
                    }
                }

                // Delete device context.
                MIL.MbufControl(milOverlayImage, MIL.M_DC_FREE, MIL.M_DEFAULT);

                // Signal MIL that the overlay buffer was modified.
                MIL.MbufControl(milOverlayImage, MIL.M_MODIFIED, MIL.M_DEFAULT);
            }

            _overlayInitialized = true;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event for the specified property.
        /// </summary>
        /// <param name="propertyName">A <see cref="string"/> object representing the name of the property that changed.</param>
        /// <remarks>Call this method to notify the UI that a property changed.</remarks>
        private void RaisePropertyChangedEvent(string propertyName)
        {
            // In debug builds, make sure the property exists in this object.
            VerifyPropertyName(propertyName);

            // Raise the PropertyChanged event.
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Warns the developer if this object does not have a public property with the specified name.
        /// </summary>
        /// <param name="propertyName">The name of the property to verify.</param>
        /// <remarks>This method does not exist in a Release build.</remarks>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real, 
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                Debug.Fail("Invalid property name: " + propertyName);
            }
        }

        #endregion

        #region Private members

        private MIL_ID _appId;
        private MIL_ID _sysId;
        private MIL_ID _digId;
        private MIL_ID _dispId;
        private MIL_ID _bufId;
        private bool _isGrabbing;
        private bool _overlayInitialized;

        #endregion
    }
}