using Flowframes.Data;
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;

namespace Flowframes.Magick
{
    class Blend
    {
        public static async Task BlendSceneChanges(string framesFilePath, bool setStatus = true)
        {
            Stopwatch sw = new Stopwatch();
            sw.Restart();
            int totalFrames = 0;

            string keyword = "SCN:";
            string fileContent = File.ReadAllText(framesFilePath);

            if (!fileContent.Contains(keyword))
            {
                Logger.Log("Skipping BlendSceneChanges as there are no scene changes in this frames file.", true);
                return;
            }

            string[] framesLines = fileContent.SplitIntoLines();     // Array with frame filenames

            string oldStatus = Program.mainForm.GetStatus();

            if (setStatus)
                Program.mainForm.SetStatus("Blending scene transitions...");

            int amountOfBlendFrames = (int)Interpolate.current.interpFactor - 1;

            string[] frames = FrameRename.framesAreRenamed ? new string[0] : IOUtils.GetFilesSorted(Interpolate.current.framesFolder);

            List<Task> runningTasks = new List<Task>();
            int maxThreads = Environment.ProcessorCount * 2;

            foreach (string line in framesLines)
            {
                try
                {
                    if (line.Contains(keyword))
                    {
                        string trimmedLine = line.Split(keyword).Last();
                        string[] inputFrameNames = trimmedLine.Split('>');
                        string frameFrom = FrameRename.framesAreRenamed ? inputFrameNames[0] : frames[inputFrameNames[0].GetInt()];
                        string frameTo = FrameRename.framesAreRenamed ? inputFrameNames[1] : frames[inputFrameNames[1].GetInt()];

                        string img1 = Path.Combine(Interpolate.current.framesFolder, frameFrom);
                        string img2 = Path.Combine(Interpolate.current.framesFolder, frameTo);

                        string firstOutputFrameName = line.Split('/').Last().Remove("'").Split('#').First();
                        string ext = Path.GetExtension(firstOutputFrameName);
                        int firstOutputFrameNum = firstOutputFrameName.GetInt();
                        List<string> outputFilenames = new List<string>();

                        for (int blendFrameNum = 1; blendFrameNum <= amountOfBlendFrames; blendFrameNum++)
                        {
                            int outputNum = firstOutputFrameNum + blendFrameNum;
                            string outputPath = Path.Combine(Interpolate.current.interpFolder, outputNum.ToString().PadLeft(Padding.interpFrames, '0'));
                            outputPath = Path.ChangeExtension(outputPath, ext);
                            outputFilenames.Add(outputPath);
                        }

                        if (runningTasks.Count >= maxThreads)
                        {
                            do
                            {
                                await Task.Delay(10);
                                RemoveCompletedTasks(runningTasks);
                            } while (runningTasks.Count >= maxThreads);
                        }

                        Logger.Log($"Starting task for transition {inputFrameNames[0]} > {inputFrameNames[1]} ({runningTasks.Count}/{maxThreads} running)", true);
                        Task newTask = Task.Run(() => BlendImages(img1, img2, outputFilenames.ToArray()));
                        runningTasks.Add(newTask);
                        totalFrames += outputFilenames.Count;

                        await Task.Delay(1);
                    }
                }
                catch (Exception e)
                {
                    Logger.Log("Failed to blend scene changes: " + e.Message, true);
                }
            }

            while (true)
            {
                RemoveCompletedTasks(runningTasks);

                if (runningTasks.Count < 1)
                    break;

                await Task.Delay(10);
            }

            Logger.Log($"Created {totalFrames} blend frames in {FormatUtils.TimeSw(sw)} ({(totalFrames / (sw.ElapsedMilliseconds / 1000f)).ToString("0.00")} FPS)", true);

            if (setStatus)
                Program.mainForm.SetStatus(oldStatus);
        }

        static void RemoveCompletedTasks(List<Task> runningTasks)
        {
            foreach (Task task in new List<Task>(runningTasks))
            {
                if (task.IsCompleted)
                    runningTasks.Remove(task);
            }
        }

        public static void BlendImages(string img1Path, string img2Path, string imgOutPath)
        {
            MagickImage img1 = new MagickImage(img1Path);
            MagickImage img2 = new MagickImage(img2Path);
            img2.Alpha(AlphaOption.Opaque);
            img2.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(50));
            img1.Composite(img2, Gravity.Center, CompositeOperator.Over);
            img1.Format = MagickFormat.Png24;
            img1.Quality = 10;
            img1.Write(imgOutPath);
        }

        public static async Task BlendImages(string img1Path, string img2Path, string[] imgOutPaths)
        {
            try
            {
                MagickImage img1 = new MagickImage(img1Path);
                MagickImage img2 = new MagickImage(img2Path);

                int alphaFraction = (100f / (imgOutPaths.Length + 1)).RoundToInt();   // Alpha percentage per image
                int currentAlpha = alphaFraction;

                foreach (string imgOutPath in imgOutPaths)
                {
                    string outPath = imgOutPath.Trim();

                    MagickImage img1Inst = new MagickImage(img1);
                    MagickImage img2Inst = new MagickImage(img2);

                    img2Inst.Alpha(AlphaOption.Opaque);
                    img2Inst.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(currentAlpha));
                    currentAlpha += alphaFraction;

                    img1Inst.Composite(img2Inst, Gravity.Center, CompositeOperator.Over);
                    img1Inst.Format = MagickFormat.Png24;
                    img1Inst.Quality = 10;
                    img1Inst.Write(outPath);
                    await Task.Delay(1);
                }
            }
            catch (Exception e)
            {
                Logger.Log("BlendImages Error: " + e.Message);
            }
        }
    }
}
