//*******************************************************************************
//
// File name: MThread.cs
// Location: See Matrox Example Launcher in the MIL Control Center
// 
//
// Synopsis:  This program shows how to use threads in a MIL application and
//            synchronize them with event. It creates 4 processing threads that 
//            are used to work in 4 different regions of a display buffer.
//
//     Thread usage:
//      - The main thread starts a processing thread in each of the 4 different
//        quarters of a display buffer. The main thread then waits for a key to
//        be pressed to stop them.
//      - The top-left and bottom-left threads work in a loop, as follows: the
//        top-left thread adds a constant to its buffer, then sends an event to
//        the bottom-left thread. The bottom-left thread waits for the event
//        from the top-left thread, rotates the top-left buffer image, then sends an
//        event to the top-left thread to start a new loop.
//      - The top-right and bottom-right threads work the same way as the
//        top-left and bottom-left threads, except that the bottom-right thread
//        performs an edge detection operation, rather than a rotation.
//
//      Note : - Under MIL-Lite, the threads will do graphic annotations instead.
//             - Comment out the MIL.MdispSelect() if you wish to avoid benchmarking 
//               the display update overhead on CPU usage and processing rate.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved
//*******************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

using Matrox.MatroxImagingLibrary;

namespace MThread
{
    class Program
    {
        // Local constants.
        private const string IMAGE_FILE = MIL.M_IMAGE_PATH + "Bird.mim";
        private const int IMAGE_WIDTH = 256;
        private const int IMAGE_HEIGHT = 240;
        private const int STRING_LENGTH_MAX = 40;
        private const int STRING_POS_X = 10;
        private const int STRING_POS_Y = 220;
        private const int DRAW_RADIUS_NUMBER = 5;
        private const int DRAW_RADIUS_STEP = 10;
        private const int DRAW_CENTER_POSX = 196;
        private const int DRAW_CENTER_POSY = 180;

        // Thread parameters object.
        public class THREAD_PARAM
        {
            public MIL_ID Id;
            public MIL_ID System;
            public MIL_ID OrgImage;
            public MIL_ID SrcImage;
            public MIL_ID DstImage;
            public MIL_ID DispImage;
            public MIL_INT DispOffsetX;
            public MIL_INT DispOffsetY;
            public MIL_ID ReadyEvent;
            public MIL_ID DoneEvent;
            public MIL_INT NumberOfIteration;
            public MIL_INT Radius;
            public MIL_INT Exit;
            public MIL_INT LicenseModules;
            public THREAD_PARAM SlaveThreadParam;
        }

        // Main function:
        // --------------
        static void Main(string[] args)
        {
            MIL_ID MilApplication = MIL.M_NULL;                 // Application identifier.
            MIL_ID MilRemoteApplication = MIL.M_NULL;           // Remote Application identifier if running on a remote computer
            MIL_ID MilSystem = MIL.M_NULL;                      // System identifier.
            MIL_ID MilDisplay = MIL.M_NULL;                     // Display identifier.
            MIL_ID MilImage = MIL.M_NULL;                       // Image buffer identifiers.
            MIL_ID MilOrgImage = MIL.M_NULL;
            THREAD_PARAM TParTopLeft = new THREAD_PARAM();      // Parameters passed to top-left thread.
            THREAD_PARAM TParBotLeft = new THREAD_PARAM();      // Parameters passed to bottom-left thread.
            THREAD_PARAM TParTopRight = new THREAD_PARAM();     // Parameters passed to top-right thread.
            THREAD_PARAM TParBotRight = new THREAD_PARAM();     // Parameters passed to bottom-right thread.
            double Time = 0.0;                                  // Timer variables.
            double FramesPerSecond = 0.0;
            MIL_INT LicenseModules = 0;                         // List of available MIL modules.

            // Allocate defaults.
            MIL.MappAllocDefault(MIL.M_DEFAULT, ref MilApplication, ref MilSystem, ref MilDisplay, MIL.M_NULL, MIL.M_NULL);

            // Allocate and display the main image buffer.
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH * 2, IMAGE_HEIGHT * 2, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC + MIL.M_DISP, ref MilImage);
            MIL.MbufClear(MilImage, 0);
            MIL.MdispSelect(MilDisplay, MilImage);
            MIL.MdispInquire(MilDisplay, MIL.M_SELECTED, ref TParTopLeft.DispImage);

            // Allocate an image buffer to keep the original.
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH, IMAGE_HEIGHT, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC, ref MilOrgImage);

            // Allocate a processing buffer for each thread.
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH, IMAGE_HEIGHT, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC, ref TParTopLeft.SrcImage);
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH, IMAGE_HEIGHT, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC, ref TParBotLeft.DstImage);
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH, IMAGE_HEIGHT, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC, ref TParTopRight.SrcImage);
            MIL.MbufAlloc2d(MilSystem, IMAGE_WIDTH, IMAGE_HEIGHT, 8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_PROC, ref TParBotRight.DstImage);

            // Allocate synchronization events.
            MIL.MthrAlloc(MilSystem, MIL.M_EVENT, MIL.M_DEFAULT, MIL.M_NULL, MIL.M_NULL, ref TParTopLeft.DoneEvent);
            MIL.MthrAlloc(MilSystem, MIL.M_EVENT, MIL.M_DEFAULT, MIL.M_NULL, MIL.M_NULL, ref TParBotLeft.DoneEvent);
            MIL.MthrAlloc(MilSystem, MIL.M_EVENT, MIL.M_DEFAULT, MIL.M_NULL, MIL.M_NULL, ref TParTopRight.DoneEvent);
            MIL.MthrAlloc(MilSystem, MIL.M_EVENT, MIL.M_DEFAULT, MIL.M_NULL, MIL.M_NULL, ref TParBotRight.DoneEvent);

            // Inquire MIL licenses.
            MIL.MsysInquire(MilSystem, MIL.M_OWNER_APPLICATION, ref MilRemoteApplication);
            MIL.MappInquire(MilRemoteApplication, MIL.M_LICENSE_MODULES, ref LicenseModules);

            // Initialize remaining thread parameters.
            TParTopLeft.System = MilSystem;
            TParTopLeft.OrgImage = MilOrgImage;
            TParTopLeft.DstImage = TParTopLeft.SrcImage;
            TParTopLeft.DispOffsetX = 0;
            TParTopLeft.DispOffsetY = 0;
            TParTopLeft.ReadyEvent = TParBotLeft.DoneEvent;
            TParTopLeft.NumberOfIteration = 0;
            TParTopLeft.Radius = 0;
            TParTopLeft.Exit = 0;
            TParTopLeft.LicenseModules = LicenseModules;
            TParTopLeft.SlaveThreadParam = TParBotLeft;

            TParBotLeft.System = MilSystem;
            TParBotLeft.OrgImage = 0;
            TParBotLeft.SrcImage = TParTopLeft.DstImage;
            TParBotLeft.DispImage = TParTopLeft.DispImage;
            TParBotLeft.DispOffsetX = 0;
            TParBotLeft.DispOffsetY = IMAGE_HEIGHT;
            TParBotLeft.ReadyEvent = TParTopLeft.DoneEvent;
            TParBotLeft.NumberOfIteration = 0;
            TParBotLeft.Radius = 0;
            TParBotLeft.Exit = 0;
            TParBotLeft.LicenseModules = LicenseModules;
            TParBotLeft.SlaveThreadParam = null;

            TParTopRight.System = MilSystem;
            TParTopRight.OrgImage = MilOrgImage;
            TParTopRight.DstImage = TParTopRight.SrcImage;
            TParTopRight.DispImage = TParTopLeft.DispImage;
            TParTopRight.DispOffsetX = IMAGE_WIDTH;
            TParTopRight.DispOffsetY = 0;
            TParTopRight.ReadyEvent = TParBotRight.DoneEvent;
            TParTopRight.NumberOfIteration = 0;
            TParTopRight.Radius = 0;
            TParTopRight.Exit = 0;
            TParTopRight.LicenseModules = LicenseModules;
            TParTopRight.SlaveThreadParam = TParBotRight;

            TParBotRight.System = MilSystem;
            TParBotRight.OrgImage = 0;
            TParBotRight.SrcImage = TParTopRight.DstImage;
            TParBotRight.DispImage = TParTopLeft.DispImage;
            TParBotRight.DispOffsetX = IMAGE_WIDTH;
            TParBotRight.DispOffsetY = IMAGE_HEIGHT;
            TParBotRight.ReadyEvent = TParTopRight.DoneEvent;
            TParBotRight.NumberOfIteration = 0;
            TParBotRight.Radius = 0;
            TParBotRight.Exit = 0;
            TParBotRight.LicenseModules = LicenseModules;
            TParBotRight.SlaveThreadParam = null;

            // Initialize the original image to process.
            MIL.MbufLoad(IMAGE_FILE, MilOrgImage);

            // Start the 4 threads.
            MIL_THREAD_FUNCTION_PTR TopThreadDelegate = new MIL_THREAD_FUNCTION_PTR(TopThread);
            MIL_THREAD_FUNCTION_PTR BotLeftThreadDelegate = new MIL_THREAD_FUNCTION_PTR(BotLeftThread);
            MIL_THREAD_FUNCTION_PTR BotRightThreadDelegate = new MIL_THREAD_FUNCTION_PTR(BotRightThread);

            GCHandle TParTopLeftHandle = GCHandle.Alloc(TParTopLeft);
            GCHandle TParBotLeftHandle = GCHandle.Alloc(TParBotLeft);
            GCHandle TParTopRightHandle = GCHandle.Alloc(TParTopRight);
            GCHandle TParBotRightHandle = GCHandle.Alloc(TParBotRight);

            MIL.MthrAlloc(MilSystem, MIL.M_THREAD, MIL.M_DEFAULT, TopThreadDelegate, GCHandle.ToIntPtr(TParTopLeftHandle), ref TParTopLeft.Id);
            MIL.MthrAlloc(MilSystem, MIL.M_THREAD, MIL.M_DEFAULT, BotLeftThreadDelegate, GCHandle.ToIntPtr(TParBotLeftHandle), ref TParBotLeft.Id);
            MIL.MthrAlloc(MilSystem, MIL.M_THREAD, MIL.M_DEFAULT, TopThreadDelegate, GCHandle.ToIntPtr(TParTopRightHandle), ref TParTopRight.Id);
            MIL.MthrAlloc(MilSystem, MIL.M_THREAD, MIL.M_DEFAULT, BotRightThreadDelegate, GCHandle.ToIntPtr(TParBotRightHandle), ref TParBotRight.Id);

            // Start the timer.
            MIL.MappTimer(MIL.M_DEFAULT, MIL.M_TIMER_RESET + MIL.M_SYNCHRONOUS, MIL.M_NULL);

            // Set events to start operation of top-left and top-right threads.
            MIL.MthrControl(TParTopLeft.ReadyEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);
            MIL.MthrControl(TParTopRight.ReadyEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);

            // Report that the threads are started and wait for a key press to stop them.
            Console.Write("\nMULTI-THREADING:\n");
            Console.Write("----------------\n\n");
            Console.Write("4 threads running...\n");
            Console.Write("Press <Enter> to stop.\n\n");
            Console.ReadKey();

            // Signal the threads to exit.
            TParTopLeft.Exit = 1;
            TParTopRight.Exit = 1;

            // Wait for all threads to terminate.
            MIL.MthrWait(TParTopLeft.Id, MIL.M_THREAD_END_WAIT);
            MIL.MthrWait(TParBotLeft.Id, MIL.M_THREAD_END_WAIT);
            MIL.MthrWait(TParTopRight.Id, MIL.M_THREAD_END_WAIT);
            MIL.MthrWait(TParBotRight.Id, MIL.M_THREAD_END_WAIT);

            // Stop the timer and calculate the number of frames per second processed.
            MIL.MappTimer(MIL.M_DEFAULT, MIL.M_TIMER_READ + MIL.M_SYNCHRONOUS, ref Time);
            FramesPerSecond = (TParTopLeft.NumberOfIteration + TParBotLeft.NumberOfIteration + TParTopRight.NumberOfIteration + TParBotRight.NumberOfIteration) / Time;

            // Print statistics.
            Console.Write("Top-left iterations done:     {0,4}.\n", TParTopLeft.NumberOfIteration);
            Console.Write("Bottom-left iterations done:  {0,4}.\n", TParBotLeft.NumberOfIteration);
            Console.Write("Top-right iterations done:    {0,4}.\n", TParTopRight.NumberOfIteration);
            Console.Write("Bottom-right iterations done: {0,4}.\n\n", TParBotRight.NumberOfIteration);
            Console.Write("Processing speed for the 4 threads: {0:0.0} Images/Sec.\n\n", FramesPerSecond);
            Console.Write("Press <Enter> to end.\n\n");
            Console.ReadKey();

            // Free threads.
            MIL.MthrFree(TParTopLeft.Id);
            MIL.MthrFree(TParBotLeft.Id);
            MIL.MthrFree(TParTopRight.Id);
            MIL.MthrFree(TParBotRight.Id);

            // Free events.
            MIL.MthrFree(TParTopLeft.DoneEvent);
            MIL.MthrFree(TParBotLeft.DoneEvent);
            MIL.MthrFree(TParTopRight.DoneEvent);
            MIL.MthrFree(TParBotRight.DoneEvent);

            // Free buffers.
            MIL.MbufFree(TParTopLeft.SrcImage);
            MIL.MbufFree(TParTopRight.SrcImage);
            MIL.MbufFree(TParBotLeft.DstImage);
            MIL.MbufFree(TParBotRight.DstImage);
            MIL.MbufFree(MilOrgImage);
            MIL.MbufFree(MilImage);

            // Free the GCHandles
            TParTopLeftHandle.Free();
            TParBotLeftHandle.Free();
            TParTopRightHandle.Free();
            TParBotRightHandle.Free();

            // Free defaults.
            MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MIL.M_NULL, MIL.M_NULL);
        }

        // Top-left and top-right threads' function (Add an offset):
        // ---------------------------------------------------------
        static uint TopThread(IntPtr ThreadParameters)
        {
            GCHandle threadParamHandle = GCHandle.FromIntPtr(ThreadParameters);
            THREAD_PARAM TPar = threadParamHandle.Target as THREAD_PARAM;

            while (TPar.Exit == 0)
            {
                // Wait for bottom ready event before proceeding.
                MIL.MthrWait(TPar.ReadyEvent, MIL.M_EVENT_WAIT);

                // For better visual effect, reset SrcImage to the original image regularly.
                if ((TPar.NumberOfIteration % 192) == 0)
                {
                    MIL.MbufCopy(TPar.OrgImage, TPar.SrcImage);
                }

                if ((TPar.LicenseModules & MIL.M_LICENSE_IM) != 0)
                {
                    // Add a constant to the image.
                    MIL.MimArith(TPar.SrcImage, 1L, TPar.DstImage, MIL.M_ADD_CONST + MIL.M_SATURATION);
                }
                else
                {
                    // Under MIL-Lite draw a variable size rectangle in the image.
                    TPar.Radius = TPar.SlaveThreadParam.Radius = (TPar.NumberOfIteration % DRAW_RADIUS_NUMBER) * DRAW_RADIUS_STEP;
                    MIL.MgraColor(MIL.M_DEFAULT, 0xff);
                    MIL.MgraRectFill(MIL.M_DEFAULT, TPar.DstImage, DRAW_CENTER_POSX - TPar.Radius, DRAW_CENTER_POSY - TPar.Radius, DRAW_CENTER_POSX + TPar.Radius, DRAW_CENTER_POSY + TPar.Radius);
                }

                // Increment iteration count and draw text.
                TPar.NumberOfIteration++;
                MIL.MgraColor(MIL.M_DEFAULT, 0xFF);
                MIL.MgraText(MIL.M_DEFAULT, TPar.DstImage, STRING_POS_X, STRING_POS_Y, String.Format("{0}", TPar.NumberOfIteration));

                // Update the display.
                if (TPar.DispImage != MIL.M_NULL)
                {
                    MIL.MbufCopyColor2d(TPar.DstImage, TPar.DispImage, MIL.M_ALL_BANDS, 0, 0, MIL.M_ALL_BANDS, TPar.DispOffsetX, TPar.DispOffsetY, IMAGE_WIDTH, IMAGE_HEIGHT);
                }

                // Signal to the bottom thread that the first part of the processing is completed.
                MIL.MthrControl(TPar.DoneEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);
            }

            // Require the bottom thread to exit.
            TPar.SlaveThreadParam.Exit = 1;

            // Signal the bottom thread to wake up.
            MIL.MthrControl(TPar.DoneEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);

            // Before exiting the thread, make sure that all the commands are executed.
            MIL.MthrWait(TPar.System, MIL.M_THREAD_WAIT, MIL.M_NULL);

            return 1;
        }


        // Bottom-left thread function (Rotate):
        // -------------------------------------
        static uint BotLeftThread(IntPtr ThreadParameters)
        {
            GCHandle threadParamHandle = GCHandle.FromIntPtr(ThreadParameters);
            THREAD_PARAM TPar = threadParamHandle.Target as THREAD_PARAM;
            double Angle = 0.0;
            double AngleIncrement = 0.5;

            while (TPar.Exit == 0)
            {
                // Wait for the event in top-left function to be ready before proceeding.
                MIL.MthrWait(TPar.ReadyEvent, MIL.M_EVENT_WAIT);

                if ((TPar.LicenseModules & MIL.M_LICENSE_IM) != 0)
                {
                    // Rotate the image.
                    MIL.MimRotate(TPar.SrcImage, TPar.DstImage, Angle, MIL.M_DEFAULT, MIL.M_DEFAULT, MIL.M_DEFAULT, MIL.M_DEFAULT, MIL.M_NEAREST_NEIGHBOR + MIL.M_OVERSCAN_CLEAR);
                    Angle += AngleIncrement;
                    if (Angle >= 360)
                    {
                        Angle -= 360;
                    }
                }
                else
                {
                    // Under MIL-Lite copy the top-left image and draw 
                    // a variable size filled circle in the image.
                    MIL.MbufCopy(TPar.SrcImage, TPar.DstImage);
                    MIL.MgraColor(MIL.M_DEFAULT, 0x80);
                    MIL.MgraArcFill(MIL.M_DEFAULT, TPar.DstImage, DRAW_CENTER_POSX, DRAW_CENTER_POSY, TPar.Radius, TPar.Radius, 0, 360);
                }

                // Increment iteration count and draw text.
                TPar.NumberOfIteration++;
                MIL.MgraColor(MIL.M_DEFAULT, 0xFF);
                MIL.MgraText(MIL.M_DEFAULT, TPar.DstImage, STRING_POS_X, STRING_POS_Y, String.Format("{0}", TPar.NumberOfIteration));

                // Update the display.
                if (TPar.DispImage != MIL.M_NULL)
                {
                    MIL.MbufCopyColor2d(TPar.DstImage, TPar.DispImage, MIL.M_ALL_BANDS, 0, 0, MIL.M_ALL_BANDS, TPar.DispOffsetX, TPar.DispOffsetY, IMAGE_WIDTH, IMAGE_HEIGHT);
                }

                // Signal to the top-left thread that the last part of the processing is completed.
                MIL.MthrControl(TPar.DoneEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);
            }

            // Before exiting the thread, make sure that all the commands are executed.
            MIL.MthrWait(TPar.System, MIL.M_THREAD_WAIT, MIL.M_NULL);

            return 1;
        }

        // Bottom-right thread function (Edge Detect):
        // -------------------------------------------
        static uint BotRightThread(IntPtr ThreadParameters)
        {
            GCHandle threadParamHandle = GCHandle.FromIntPtr(ThreadParameters);
            THREAD_PARAM TPar = threadParamHandle.Target as THREAD_PARAM;

            while (TPar.Exit == 0)
            {
                // Wait for the event in top-right function to be ready before proceeding.
                MIL.MthrWait(TPar.ReadyEvent, MIL.M_EVENT_WAIT);

                if ((TPar.LicenseModules & MIL.M_LICENSE_IM) != 0)
                {
                    // Perform an edge detection operation on the image.
                    MIL.MimConvolve(TPar.SrcImage, TPar.DstImage, MIL.M_EDGE_DETECT_SOBEL_FAST);
                }
                else
                {
                    // Under MIL-Lite copy the top-right image and draw
                    // a variable size filled circle in the image.
                    MIL.MbufCopy(TPar.SrcImage, TPar.DstImage);
                    MIL.MgraColor(MIL.M_DEFAULT, 0x40);
                    MIL.MgraArcFill(MIL.M_DEFAULT, TPar.DstImage, DRAW_CENTER_POSX, DRAW_CENTER_POSY, TPar.Radius / 2, TPar.Radius / 2, 0, 360);
                }

                // Increment iteration count and draw text.
                TPar.NumberOfIteration++;
                MIL.MgraColor(MIL.M_DEFAULT, 0xFF);
                MIL.MgraText(MIL.M_DEFAULT, TPar.DstImage, STRING_POS_X, STRING_POS_Y, String.Format("{0}", TPar.NumberOfIteration));

                // Update the display.
                if (TPar.DispImage != MIL.M_NULL)
                {
                    MIL.MbufCopyColor2d(TPar.DstImage, TPar.DispImage, MIL.M_ALL_BANDS, 0, 0, MIL.M_ALL_BANDS, TPar.DispOffsetX, TPar.DispOffsetY, IMAGE_WIDTH, IMAGE_HEIGHT);
                }

                // Signal to the top-right thread that the last part of the processing is completed.
                MIL.MthrControl(TPar.DoneEvent, MIL.M_EVENT_SET, MIL.M_SIGNALED);
            }

            // Before exiting the thread, make sure that all the commands are executed.
            MIL.MthrWait(TPar.System, MIL.M_THREAD_WAIT, MIL.M_NULL);

            return 1;
        }
    }
}