using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using BrightErrorsLed;
using Emgu.CV.Structure;
using Emgu.CV;
using Emgu.Util.TypeEnum;
using Emgu.CV.ML;
using Emgu.CV.Cuda;
using System.IO;

namespace BrightErrorsLed
{
    // Main Methods
    class Controller
    {
        private string saveFile;
        private Rectangle resolution = Screen.PrimaryScreen.Bounds;
        public Controller()
        { }

        public Bitmap _resize(Image image, int width, int height)
        {
            Bitmap newImage = new Bitmap(width, height);

            newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
            using (Graphics gr = Graphics.FromImage(newImage))
            {
                gr.DrawImage(image, new Rectangle(0, 0, width, height));
            }
            GC.Collect();
            return newImage;
        }

        public async Task<Bitmap> SetLineBrightness(int lightness, Bitmap bmap, Point[] Body)
        {
            Image<Hls, Byte> newBmap = new Image<Hls, byte>(bmap);
            await Task.Run(() =>
            {
                for (int i = 0; i < Body.Length; i++)
                {
                    var c = newBmap[Body[i].Y, Body[i].X];
                    c.Lightness += lightness;
                    newBmap[Body[i].Y, Body[i].X] = c;
                }
            });
            return newBmap.Bitmap;

        }
        private async Task<Image<Hls, Byte>> FindBitmapChanges(Image<Hls, Byte> org, Image<Hls, Byte> changed, Image<Hls, Byte> newBitmap, Nullable<PixelChanges>[] changes)
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < changes.Count(); i++)
                {
                    if (changes[i] == null)
                    {
                        return newBitmap;
                    }
                    var n = newBitmap[changes[i].Value.Y, changes[i].Value.X];
                    n.Lightness -= org[changes[i].Value.Y, changes[i].Value.X].Lightness - changed[changes[i].Value.Y, changes[i].Value.X].Lightness;
                    newBitmap[changes[i].Value.Y, changes[i].Value.X] = n;

                }
                return newBitmap;
            });
            return newBitmap;
        }
        public static async Task<PixelChanges?[]> FindChanged(Bitmap org, Bitmap changed)
        {
            Nullable<PixelChanges>[] changedPixels = new Nullable<PixelChanges>[Screen.PrimaryScreen.Bounds.Width * Screen.PrimaryScreen.Bounds.Height];
            await Task.Run(() =>
            {
                Image<Hls, Byte> _org = new Image<Hls, byte>(org);
                Image<Hls, Byte> _changed = new Image<Hls, byte>(changed);
                var c = Color.FromArgb(0, 0, 0);
                int n = 0;
                var diff = _org.Cmp(_changed, Emgu.CV.CvEnum.CmpType.NotEqual);
                var diffBtm = diff.Bitmap;
                for (int i = 1; i < org.Height; i++)
                    for (int j = 1; j < org.Width; j++)
                    {
                        if (diffBtm.GetPixel(j, i) != c)
                        {
                            changedPixels[n] = new PixelChanges(j, i);
                            n++;
                        }
                    }
                if (n != 0)
                    return changedPixels;
                else return null;
            });
            return changedPixels;
        }

        public async Task UpChangesToVideoFile(Bitmap org, Bitmap changed, Label label, IProgress<int> progress, char[] fourcc, bool change = false)
        {
            label.Text = "Loading..., please wait";
            label.Visible = true;
            label.Refresh();
            Image<Bgr, Byte> Org = new Image<Bgr, Byte>(org);
            Image<Hls, Byte> OrgHls = Org.Convert<Hls, Byte>();
            Image<Bgr, Byte> Ch = new Image<Bgr, Byte>(changed);
            Image<Hls, Byte> ChHls = Ch.Convert<Hls, Byte>();
            VideoWriter VW;
            Nullable<PixelChanges>[] Changes = null;

            Capture Video;
            if (change)
            {
                label.Text = "Finding changes in Bitmap, please wait...";
                label.Visible = true;
                BrightErrosLed.ActiveForm.Cursor = Cursors.WaitCursor;
                var dialog = MessageBox.Show("Program is looking for changes in image, plese wait", "WAIT PLEASE", MessageBoxButtons.OKCancel);
                if (dialog == DialogResult.Cancel)
                    return;
                label.Refresh();
                Changes = await FindChanged(org, changed);
                BrightErrosLed.ActiveForm.Cursor = Cursors.Default;
            }

            GC.Collect();
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Avi|*.avi|Wmv|*.wmv|Mp4|*.mp4";
            ofd.Title = "Open an Video File";
            ofd.ShowDialog();
            label.Text = "Starting video Capture, please wait...";
            label.Refresh();
            string image = ofd.FileName;
            if (image != "")
            {
                Video = new Capture(image);
                SaveFileDialog sfd = new SaveFileDialog();
                MessageBox.Show("Please choose directory to save your new video");
                sfd.Filter = "Avi|*.avi";
                sfd.Title = ("Name your video and choose directory to save it please");
                sfd.ShowDialog();
                if (fourcc == null || fourcc.Length != 4)
                    VW = new VideoWriter(sfd.FileName, VideoWriter.Fourcc('D', 'I', 'V', 'X')
                        , Convert.ToInt32(Video.GetCaptureProperty(Emgu.CV.CvEnum.CapProp.Fps)), new Size(resolution.Width, resolution.Height), true);
                else
                {
                    VW = new VideoWriter(sfd.FileName, VideoWriter.Fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3])
                        , Convert.ToInt32(Video.GetCaptureProperty(Emgu.CV.CvEnum.CapProp.Fps)), new Size(resolution.Width, resolution.Height), true);
                }
                saveFile = sfd.FileName;
                if (image == saveFile)
                {
                    MessageBox.Show("Source and target video can't be the same ones");
                    return;
                }
            }
            else
                return;
            label.Text = "Remastering the video. Please wait... ";
            label.Refresh();

            VW = await RemakeVideoAsync(Video, Changes, OrgHls, ChHls, VW, progress);

            if (!BrightErrosLed.Cancel)
            {
                VW.Dispose();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                MessageBox.Show("Video is succesfully remastered and saved on disk in directory: " + saveFile);
            }
            return;
        }


        private async Task<VideoWriter> RemakeVideoAsync(Capture Video, PixelChanges?[] Changes, Image<Hls, Byte> OrgHls, Image<Hls, Byte> ChHls, VideoWriter VW, IProgress<int> progress)
        {
            BrightErrosLed.Cancel = false;
            await Task.Run(async () =>
            {
                for (int i = 1; i < (Video.GetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameCount) - 1); i++)
                {
                    Image<Bgr, Byte> NewFrame = (Video.QueryFrame().ToImage<Bgr, Byte>());
                    Image<Hls, Byte> FrameHls = NewFrame.Convert<Hls, Byte>();
                    FrameHls = FrameHls.Resize(resolution.Width, resolution.Height, Emgu.CV.CvEnum.Inter.Nearest);

                    if (Changes != null)
                        FrameHls = await FindBitmapChanges(OrgHls, ChHls, FrameHls, Changes);
                    NewFrame = FrameHls.Convert<Bgr, Byte>();
                    VW.Write(NewFrame.Mat);

                    GC.Collect();
                    GC.WaitForPendingFinalizers();

                    progress.Report(Convert.ToInt32((i / (Video.GetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameCount) - 1)) * 100));

                    if (BrightErrosLed.Cancel)
                    {
                        break;
                    }
                }
                return VW;
            });
            return VW;
        }
    }


}

