/*
Copyright © Ashish Derhgawen, 2008
 
Written by Ashish Derhagwen (http://ashishrd.blogspot.com)
for MSDN: Coding4Fun (http://msdn.microsoft.com/coding4fun/)

E-mail: ashish.derhgawen@gmail.com
*/
namespace motion {
    using System;
    using System.Drawing;

    using AForge.Imaging;
    using AForge.Imaging.Filters;
    using System.Collections;
    using System.Runtime.InteropServices;
    using System.Reflection;
    //using Tiger.Video.VFW;
    using System.Windows.Forms;
    using System.Drawing.Imaging;
    using System.Collections.Generic;
    using System.Diagnostics;

    /// <summary>
    /// Laser Tracking
    /// </summary>
    public class MotionDetector1 : IMotionDetector {
        public static MotionDetector1 motionForm { get; private set; }
        private bool leftMouseButtonDown = false;
        private int imageWidth, imageHeight;

        //private MainForm MainForm.mForm;
        // public static MainForm mForm { get; set; } // { return mForm; } set { mForm = value; } }
        public Rectangle videoCrop { get { return MainForm.mForm.videoCrop; } set { MainForm.mForm.videoCrop = value; } }

        public MotionDetector1() {
            Clear();
        }

        // Reset detector to initial state
        public void Reset() {
            Clear();
        }

        public void Clear() {
            PointHistory = new List<ValuePoint>();
            if (MainForm.mForm != null) MainForm.mForm.videoCrop = new Rectangle();

        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);

        private const int MOUSEEVENTF_LEFTDOWN = 0x02;
        private const int MOUSEEVENTF_LEFTUP = 0x04;
        private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
        private const int MOUSEEVENTF_RIGHTUP = 0x10;

       private struct ValuePoint { // Bright Point
            public Point Pos;
            public int Strength;
            public int X { get { return Pos.X; } }
            public int Y { get { return Pos.Y; } }
            public bool IsEmpty { get { return Pos.IsEmpty; } }
            public bool isHigherThan(ValuePoint other) {
                return this.Strength > other.Strength;
            }

            public bool IsLowerThan(ValuePoint other) {
                return this.Strength < other.Strength;
            }

            public bool IsBetterThan(ValuePoint other, bool bigIsGood) {
                if (bigIsGood)
                    return this.isHigherThan(other);
                return this.IsLowerThan(other);
            }

            public static implicit operator Point(ValuePoint bp) {
                return bp.Pos;
            }

            public static bool operator >(ValuePoint p1, ValuePoint p2) {
                return p1.Strength > p2.Strength;
            }

            public static bool operator <(ValuePoint p1, ValuePoint p2) {
                return p1.Strength < p2.Strength;
            }
        }
        private static List<ValuePoint> PointHistory;

        #region "Filter Support"

        public delegate bool dlgColorFilter(Utility.PixelData pix, out int val);
        public dlgColorFilter ColorFilter = RedFilter;

        public static bool RedFilter(Utility.PixelData pix, out int val) {  // Default filter
            // Hack
            if (!DistanceFrom.IsEmpty)
                return DistanceFilter(pix, out val);
            val = 0;
            if (!(pix.blue * 1.4 < pix.red && pix.green * 1.4 < pix.red && pix.red > 150))
                return false; //  ColorFilterResult.Fail;
            val = pix.red;
            return true; //  ColorFilterResult.Max;
        }

        // This is an experimental form
        public static Color DistanceFrom = Color.Empty;
        public static bool DistanceFilter(Utility.PixelData pix, out int val) {  // Default filter
            // http://www.compuphase.com/cmetric.htm
            var dist = val = pix.Distance(DistanceFrom);
            if (dist > MainForm.mForm.ColorThreshold)   // Arbitrary
                return false; //  ColorFilterResult.Fail;
            return true; //  ColorFilterResult.Succeed;
        }
        #endregion

        // Process new frame
        public void ProcessFrame(ref Bitmap image) {
            try {
                if (MainForm.mForm.leftMouseButtonDown) {
                    //Code to draw a rubberband selection rectangle

                    //Compute width and height of rectangle
                    int w = Math.Abs((int)MainForm.mForm.x2 - (int)MainForm.mForm.x1);
                    int h = Math.Abs((int)MainForm.mForm.y2 - (int)MainForm.mForm.y1);
                    int x = Math.Min((int)MainForm.mForm.x1, (int)MainForm.mForm.x2);
                    int y = Math.Min((int)MainForm.mForm.y1, (int)MainForm.mForm.y2);
                    Rectangle rect = new Rectangle(x, y, w, h);

                    using (var g = Graphics.FromImage((System.Drawing.Image)image)) {
                        using (var br = new SolidBrush(Color.FromArgb(60, Color.Yellow)))
                            g.FillRectangle(br, rect); //Draws a semi-transparent rectangle using alpha blending
                        g.DrawRectangle(Pens.Goldenrod, rect);
                    }
                    return;   // Didn't do anything with it?
                }

                if (videoCrop.IsEmpty)               //Set or reset video cropping so that nothing gets trimmed out
                    MainForm.mForm.videoCrop = new Rectangle(0, 0, image.Width, image.Height);

                var tmpImage0 = image.Clone(MainForm.mForm.videoCrop, PixelFormat.Format24bppRgb);  //Crop the bitmap

                //Resize the bitmap
                var resize = new Resize(image.Width / MainForm.mForm.resizeFactor, image.Height / MainForm.mForm.resizeFactor, InterpolationMethod.NearestNeighbor);
                var tmpImage1 = resize.Apply(tmpImage0);

                //Get width and height of Bitmap
                imageWidth = tmpImage1.Width;
                imageHeight = tmpImage1.Height;

                tmpImage0.Dispose();

                using (var uBitmap = new Utility.UnsafeBitmap(tmpImage1)) {
                    //Locate the brightest pixel in the bitmap
                    uBitmap.LockBitmap();

                    var BrightPos = new ValuePoint();
                    var Brights = new List<ValuePoint>();

                    var ColorPos = new ValuePoint();
                    var Coloreds = new List<ValuePoint>();

                    // RMF fixed to get the max
                    // Should do match averaging adjacent pixels or something like that

                    for (int y = 0; y < imageHeight; y++) {             // Scan line by scan line (Y is vertical)
                        for (int x = 0; x < imageWidth; x++) {          // Across -- X is horizontal
                            Point pos = new Point(x, y);
                            var pix = uBitmap[pos]; //  .GetPixel(pos);

                            if (pix.Brightness > MainForm.mForm.threshold) { // && brightness > brightest) {
                                var nPos = new ValuePoint() { Pos = pos, Strength = pix.Brightness };
                                if (BrightPos.IsEmpty || nPos > BrightPos)
                                    BrightPos = nPos;
                                Brights.Add(nPos);
                            }

                            //if (ColorFilter != null) {
                            //    int val;
                            //    if (ColorFilter(pix, out val)) {
                            //        var nPos = new BPoint() { Pos = pos, Strength = val };
                            //        if (xPos.IsEmpty || xPos.Higher(nPos))
                            //            xPos = BPos;
                            //        Coloreds.Add(nPos);
                            //    }
                            //}
                        } // x loop
                    } // y loop

                    // Version two -- create a map
                    if (MainForm.mForm.ckColorScan.Checked) {
                        if (ColorFilter != null) {
                            var cmap = new int[imageWidth, imageHeight];
                            for (int y = 0; y < imageHeight; y++)
                                for (int x = 0; x < imageWidth; x++) {
                                    int val;
                                    ColorFilter(uBitmap[new Point(x, y)], out val);
                                    cmap[x, y] = val;
                                }

                            // Now get the pixels that meet the average
                            // We can make this run faster and more sophisticated by precomputing the neighborhood incidies with weights
                            var dn = 2;
                            for (int x = dn; x < imageWidth - dn; x++)
                                for (int y = dn; y < imageHeight - dn; y++) {
                                    //var sum = cmap[x - 1, y - 1] + cmap[x - 1, y] + cmap[x - 1, y + 1] +
                                    //          cmap[x, y - 1] + cmap[x, y] + cmap[x, y + 1] +
                                    //          cmap[x + 1, y - 1] + cmap[x + 1, y] + cmap[x + 1, y + 1];
                                    var sum = 0;
                                    var cnt = 0;
                                    for (var dx = -dn; dx <= dn; dx++)
                                        for (var dy = -dn; dy <= dn; dy++) {
                                            if ((dx == -dn || dx == dn) && (dy == dn || dy == -dn)) continue; // Ignore the corners
                                            sum += cmap[x + dx, y + dy];
                                            cnt++;
                                        }
                                    var avg = sum / cnt;
                                    if (DistanceFrom.IsEmpty ? avg > MainForm.mForm.threshold : avg < MainForm.mForm.ColorThreshold) {
                                        var nPos = new ValuePoint() { Pos = new Point(x, y), Strength = avg };
                                        if (ColorPos.IsEmpty || !ColorPos.IsBetterThan(nPos, DistanceFrom.IsEmpty))
                                            ColorPos = nPos;
                                        Coloreds.Add(nPos);
                                    }
                                }
                        }
                    }

                    if (!BrightPos.IsEmpty || !ColorPos.IsEmpty) {
                        uBitmap.UnlockBitmap();

                        //PointHistory.Add(Pos);
                        //Encircle the brightest pixel
                        using (var dc = Graphics.FromImage(tmpImage1)) {
                            Pen p = new Pen(Color.LimeGreen, 1);

                            if (!BrightPos.IsEmpty)

                                //Draw birght points??
                                PointHistory.Add(BrightPos);
                            dc.DrawEllipse(p, BrightPos.X - 5, BrightPos.Y - 5, 10, 10);
                            if (!ColorPos.IsEmpty) {

                                //Draw color points??
                                PointHistory.Add(ColorPos);
                                dc.DrawEllipse(Pens.Orange, ColorPos.X - 5, ColorPos.Y - 5, 10, 10);
                                //draws outer circle of matched color.
                                //{
                                //    uBitmap.LockBitmap();
                                //    var px = uBitmap[xPos];
                                //    using (var pn = new Pen(px))
                                //        dc.DrawEllipse(pn, xPos.X - 6, xPos.Y - 6, 12, 12);
                                //    uBitmap.UnlockBitmap();
                                //}
                            }
                            //Thresholding stuff
                            if (MainForm.mForm.ckShowBrightThresholding.Checked) {
                                using (var br = new SolidBrush(Color.FromArgb(0x80, Color.Blue)))
                                    foreach (var bp in Brights)
                                        dc.FillRectangle(br, bp.X, bp.Y, 1, 1);
                            }

                            if (MainForm.mForm.ckShowColorThresholding.Checked) {
                                using (var br = new SolidBrush(Color.FromArgb(0x80, Color.Chartreuse)))
                                    foreach (var bp in Coloreds)
                                        dc.FillRectangle(br, bp.X, bp.Y, 1, 1);
                            }

                            // Show history

                            if (!BrightPos.IsEmpty) {                  //Show the x and y coordinates of the brightest pixel
                                dc.FillRectangle(Brushes.Transparent, 0, 0, 100, 20);
                                dc.DrawString(string.Format("X: {0}, Y: {1}, {2}", BrightPos.X, BrightPos.Y, "BrightPos"),//,ColorPos.Strength),
                                    new Font("Verdana", 6, FontStyle.Regular), Brushes.Yellow, new Point(0, 0)); // new Point(xPos, yPos + 8));
                            }

                            if (!ColorPos.IsEmpty) {                  //Show the x and y coordinates of the color pixel
                                dc.FillRectangle(Brushes.Transparent, 0, 70, 100, 20);
                                dc.DrawString(string.Format("X: {0}, Y: {1},{2}", ColorPos.X, ColorPos.Y, "ColorPos"),//,ColorPos.Strength),
                                    new Font("Verdana", 6, FontStyle.Regular), Brushes.Pink, new Point(0, 65)); // new Point(xPos, yPos + 8));
                            }


                            //if (MainForm.mForm.ckColorPaint.Checked && !ColorPos.IsEmpty)
                            //    PaintPos(ColorPos, MainForm.mForm.enableClick); //Set cursor position

                            //if (MainForm.mForm.ckBrightPaint.Checked && !BrightPos.IsEmpty)
                            //    PaintPos(BrightPos, MainForm.mForm.enableClick);

                            switch (MainForm.PaintWhat) {
                                case MainForm.PaintWhatEnum.brightPaint:
                                    if (!BrightPos.IsEmpty)
                                        PaintPos(BrightPos);
                                    break;
                                case MainForm.PaintWhatEnum.colorPaint:
                                    if (!ColorPos.IsEmpty)
                                        PaintPos(ColorPos);
                                    break;
                            }


                        }
                        uBitmap.LockBitmap();
                    }
                    else {
                        if (MainForm.mForm.enableClick == true && leftMouseButtonDown == true) {
                            mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);     //Generate a left mouse button click
                            leftMouseButtonDown = false;
                        }
                    }

                    uBitmap.UnlockBitmap();
                }

                image.Dispose();
                image = tmpImage1;  // return copy;
            }
            catch (Exception ex) {
                Debug.WriteLine(ex.Message);
                Debugger.Break();
                //return image;z   // Just keep it unscathed
            }
        }

        private void PaintPos(Point pos) {
            //Get screen width and height
            int screenWidth = Screen.PrimaryScreen.WorkingArea.Width;
            int screenHeight = Screen.PrimaryScreen.WorkingArea.Height + 34;    // 34??

            //Compute location of laser dot into camera coordinates
            var cursorX = (((float)screenWidth / imageWidth) * pos.X);
            var cursorY = (((float)screenHeight / imageHeight) * pos.Y);

            if (MainForm.mForm.invertCursorMovements) {
                cursorX = screenWidth - cursorX;
                cursorY = screenHeight - cursorY;
            }
            //MainForm.mForm.Text = string.Format("X={0} Y={1}", cursorX, cursory);

            //Set cursor position
            //Cursor.Position = new Point((int)cursorX, (int)cursorY);
            if (MainForm.PForm != null)
                MainForm.PForm.MotionPosition(new Point((int)cursorX, (int)cursorY)); //If a new point is recorded draw the line. 

            //if (click == true) {
            //    leftMouseButtonDown = true;
            //}

        }
    }
}