using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Luxand;
using System.Diagnostics.PerformanceData;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;
using System.IO;
using System.Drawing.Drawing2D;

namespace PassiveLiveness
{
    public partial class MainForm : Form
    {
        String cameraName;
        bool needClose = false;

        Luxand.Tracker tracker = null;

        // WinAPI procedure to release HBITMAP handles returned by FSDKCam.GrabFrame
        [DllImport("gdi32.dll")]
        static extern bool DeleteObject(IntPtr hObject);

        // Store the current frame for drawing
        private Image currentFrame = null;

        public MainForm()
        {
            InitializeComponent();
            // Subscribe to Paint event
            pictureBox1.Paint += pictureBox1_Paint;
            // Allow the window to be freely resizable
            this.MinimumSize = new Size(640, 480);
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            if (FSDK.FSDKE_OK != FSDK.ActivateLibrary("INSERT THE LICENSE KEY HERE"))
            {
                MessageBox.Show("Please insert the license key in the FSDK.ActivateLibrary()", "Error activating FaceSDK", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Application.Exit();
                return;
            }

            FSDK.InitializeLibrary();
            FSDK.InitializeCapturing();

            string[] cameraList = FSDK.GetCameraList();

            if (cameraList.Length == 0) {
                MessageBox.Show("Please attach a camera", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Application.Exit();
            }
            cameraName = cameraList[0];

            Luxand.FSDK.VideoFormatInfo[] formatList = Luxand.Camera.GetVideoFormatList(cameraName);

            // Choose the best camera from the list
            int index = 0;
            int formatIndex = 0;
            int max_height = 0, max_width = 0;
            foreach (var format in formatList)
            {
                if (format.Width > max_width && format.Height > max_height)
                {
                    max_width = format.Width;
                    max_height = format.Height;
                    formatIndex = index;
                }
                index++;
            }

            Luxand.Camera.SetVideoFormat(cameraName, formatList[formatIndex]);
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            needClose = true;
        }

        private void StartCameraBtn_Click(object sender, EventArgs e)
        {
            this.StartBtn.Enabled = false;

            // Initialize camera
            Luxand.Camera camera = null;
            try
            {
                camera = new Luxand.Camera(cameraName);
            } catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error opening camera");
                Application.Exit();
            }

            // Creating tracker
            tracker = new Luxand.Tracker();
            if (tracker == null)
            {
                MessageBox.Show("Error creating tracker", "Error");
                Application.Exit();
            }

            // set realtime face detection parameters
            tracker.SetMultipleParameters("RecognizeFaces=false;HandleArbitraryRotations=false;DetermineFaceRotationAngle=false; InternalResizeWidth=100; FaceDetectionThreshold=5;", out var errorPosition);

            tracker.SetParameter("DetectLiveness", "true");
            tracker.SetParameter("LivenessFramesCount", "6");
            tracker.SetParameter("SmoothAttributeLiveness", "true");

            while (!needClose)
            {
                CImage frame = camera.GrabFrame();
                if (frame == null)
                {
                    Application.DoEvents();
                    continue;
                }

                Image frameImage = frame.ToCLRImage();

                // Process the frame with the tracker
                tracker.FeedFrame(frame, out long[] faceIds);

                // Make UI controls accessible (to find if the user clicked on a face)
                Application.DoEvents();

                if (faceIds != null)
                {
                    Graphics gr = Graphics.FromImage(frameImage);

                    foreach (var faceId in faceIds)
                    {
                        // Get the face position and create an ellipse for it
                        FSDK.TFacePosition facePos;
                        try { facePos = tracker.GetFacePosition(0, faceId); } catch { continue; }

                        float w = facePos.w;
                        float h = facePos.w * 1.4f;

                        int left = (int)(facePos.xc - w * 0.5);
                        int top = (int)(facePos.yc - w * 0.7f);
                        int right = (int)(facePos.xc + w * 0.5);
                        int bottom = (int)(facePos.yc + w * 0.7f);

                        // Get liveness
                        float liveness = 0.0f;
                        string livenessText = "";

                        try
                        {
                            tracker.GetFacialAttribute(faceId, "Liveness", out string livenessAttribute);
                            if (livenessAttribute != null && livenessAttribute != "")
                            {
                                FSDK.GetValueConfidence(livenessAttribute, "Liveness", out liveness);
                            }
                        }
                        catch { }

                        Pen pen = Pens.LightGreen;

                        if (liveness > 0.0f)
                        {
                            if (liveness < 0.5f)
                            {
                                // If liveness is low, mark the face as suspicious
                                pen = Pens.Red;
                            }
                            else
                            {
                                livenessText = $"Liveness: {liveness * 100.0f:0.00}%";
                            }
                        }


                        string labelText = "";
                        if (livenessText != "")
                        {
                            labelText += $" ({livenessText})";
                        }

                        gr.DrawEllipse(pen, left, top, w, h);

                        // Draw name
                        if (labelText != "")
                        {
                            StringFormat format = new StringFormat();
                            format.Alignment = StringAlignment.Center;

                            gr.DrawString(labelText, new System.Drawing.Font("Arial", 12, FontStyle.Bold),
                                new System.Drawing.SolidBrush(pen.Color),
                                facePos.xc, bottom, format);
                        }
                    }
                }

                // Save the current frame and redraw pictureBox1
                if (currentFrame != null)
                {
                    var old = currentFrame;
                    currentFrame = null;
                    old.Dispose();
                }

                currentFrame = frameImage;
                pictureBox1.Invalidate();
                frame.Dispose();
                GC.Collect();
            }
       
            camera.Close();

            tracker.Dispose();
            tracker = null;

            FSDK.FinalizeCapturing();
            FSDK.FinalizeLibrary();
        }

        // Draw the frame with aspect ratio preserved
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            if (currentFrame == null)
                return;

            var pb = pictureBox1;
            var g = e.Graphics;
            g.Clear(pb.BackColor);

            int imgW = currentFrame.Width;
            int imgH = currentFrame.Height;
            int boxW = pb.Width;
            int boxH = pb.Height;

            float ratio = Math.Min((float)boxW / imgW, (float)boxH / imgH);
            int drawW = (int)(imgW * ratio);
            int drawH = (int)(imgH * ratio);
            int offsetX = (boxW - drawW) / 2;
            int offsetY = (boxH - drawH) / 2;

            g.DrawImage(currentFrame, new Rectangle(offsetX, offsetY, drawW, drawH));
        }
    }
}
