using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using lkfxdok.c;
using lkfxdok.LKF;
using lkfxdok.unittests.lkf;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace lkfxdok.unittests
{
    [TestClass]
    public class UnitTestFiles
    {
        public static string TestRoot = "C:\\lkf\\test\\";
        public static string MetaDataPath = TestRoot;
        public static string AlternateMetaData2025 = "C:\\lkf\\test\\alt2025\\";
        public static string AlternateMetaData2024 = "C:\\lkf\\test\\alt2024\\";
        public static string OutputPath = TestRoot + "out\\";
        public static string AlternateOutputPath = TestRoot + "altOut\\";
        public static string FailMetaData2024 = "C:\\lkf\\test\\fail2024\\";

        public static bool useMemoryTrace = false;
        public UnitTestFiles()
        {
            if (useMemoryTrace) LKFController.enableMemoryTrace();
            Controller.DataPath = MetaDataPath;
        }

        [TestMethod]
        public void TestMethod2026Single()
        {
            var path = CreatePath(TestRoot, "in2026\\single");
            var files = Directory.GetFiles(path);
            LKFController.enableMemoryTrace();
            foreach (var file in files)
            {
                var execution = new LKFFileExecution(file, 2024, 2026, 1, OutputPath);
                execution.execute();
            }
        }

        [TestMethod]
        public void TestMethod2025Single()
        {
            var path = CreatePath(TestRoot, "in2025\\single");
            var files = Directory.GetFiles(path);
            foreach (var file in files)
            {
                var execution = new LKFFileExecution(file, 2024, 2025, 1, OutputPath);
                execution.execute();
            }
        }

        [TestMethod]
        public void TestMethod2024Single()
        {
            var path = CreatePath(TestRoot, "in2024\\single");
            var files = Directory.GetFiles(path);

            foreach (var file in files)
            {
                var execution = new LKFFileExecution(file, 2024, 2024, 1, OutputPath);
                Console.WriteLine($"Single: {execution.ToString()}");
                execution.execute();
            }
        }

        void ThreadExecute(IEnumerable<LKFExecution> executions, int threadCount, int repeatCount = 1)
        {
            System.Threading.Semaphore semaphore = new System.Threading.Semaphore(threadCount, threadCount);

            var multi = 0;

            LKFController.activateMetaDataSemaphore();
            if (repeatCount > 1)
            {
                Console.WriteLine($"Multi-Execution with {threadCount} threads and {repeatCount} repetitions.");
            }
            else if (threadCount > 1)
            {
                Console.WriteLine($"Multi-Execution with {threadCount} threads.");
            }

            for (int i = 0; i < repeatCount; i++)
            {
                foreach (var execution in executions)
                {
                    semaphore.WaitOne();
                    var thread = new System.Threading.Thread(() =>
                    {
                        Console.WriteLine($"Multi #{++multi}: {execution.ToString()}");
                        execution.execute();
                        semaphore.Release();
                    });
                    thread.Start();
                }

            }

            for (int i = 0; i < threadCount; i++)
            {
                semaphore.WaitOne();
                Console.WriteLine($"Waiting for completion thread {i}");
            }

            semaphore.Dispose();
        }

        public string CreatePath (string root, string path)
        {
            if (System.IntPtr.Size == 4)
            {
                path += "32";
            }
             return Path.Combine(root, path);
        }


        [TestMethod]
        public void TestMethod2026Multi()
        {
            var path = CreatePath(TestRoot, "in2026\\multi");        
            var files = Directory.GetFiles(path);
            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2026, 1, OutputPath));
            ThreadExecute(executions, 4);
        }

        [TestMethod]
        public void TestMethod2025Multi()
        {
            var path = CreatePath(TestRoot, "in2025\\multi");
            var files = Directory.GetFiles(path);
            var executions = files.Select ( file => new LKFFileExecution(file, 2024, 2025, 1, OutputPath ));
            ThreadExecute(executions, 4);
        }

        [TestMethod]
        public void TestMethod2025AdHoc()
        {
            var path = CreatePath(TestRoot, "in2025\\adhoc");
            var files = Directory.GetFiles(path);
            var executions = files.Select(file => new LKFFileExecution(file, 2025, 2025, 1, OutputPath));
            ThreadExecute(executions, 1);
        }


        [TestMethod]
        public void TestMethod2025Memory()
        {
            if (!useMemoryTrace) return;
            var path = CreatePath(TestRoot, "in2025\\memory");
            var files = Directory.GetFiles(path);


            var memoryTracker = new MemoryTracker();
            memoryTracker.Assign();
            var executions = files.Select(file =>
            {
                var fex = new LKFFileExecution(file, 2024, 2025, 1, OutputPath);
                fex.KeepContext = false;
                return fex;
            });
            ThreadExecute(executions, 1);

            memoryTracker.Reset();
            memoryTracker.KeepKeys = false;

            ThreadExecute(executions, 1);

            // should not return any keys
            memoryTracker.PrintMemoryBlocksPerKey();
            memoryTracker.Deassign();

            
        }


        [TestMethod]
        public void TestMethod2024Multi()
        {
            var path = CreatePath(TestRoot, "in2024\\multi");
            var files = Directory.GetFiles(path);
            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2024, 1, OutputPath));
            ThreadExecute(executions, 4);
        }

        [TestMethod]
        public void TestMethod2026Repeat()
        {
            var path = CreatePath(TestRoot, "in2026\\repeat");
            var files = Directory.GetFiles(path);
            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2026, 1));
            ThreadExecute(executions, 4, 3);
        }

        [TestMethod]
        public void TestMethodMixed()
        {
            var path = CreatePath(TestRoot, "in2026\\mixed");
            var files = Directory.GetFiles(path);

            var executions = files.Select(file =>
            {
                if (file.EndsWith("24"))
                {
                    return new LKFFileExecution(file, 2024, 2024, 1);
                }
                if (file.EndsWith("25"))
                {
                    return new LKFFileExecution(file, 2024, 2025, 1);
                }
                return new LKFFileExecution(file, 2024, 2026, 1);
            });

            ThreadExecute(executions, 4, 3);
        }

        [TestMethod]
        public void TestMethod2026Large()
        {
            var path = CreatePath(TestRoot, "in2026\\large");
            var files = Directory.GetFiles(path);
            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2026, 1));
            ThreadExecute(executions, 2, 4);
        }


        [TestMethod]
        public void TestMethod2026Memory()
        {
            if (!useMemoryTrace) return;
            var path = CreatePath(TestRoot, "in2026\\memory");
            var files = Directory.GetFiles(path);

            var memoryTracker = new MemoryTracker();
            memoryTracker.Assign();

            var executions = files.Select(file =>
            {
                var fex = new LKFFileExecution(file, 2024, 2026, 1, OutputPath);
                fex.KeepContext = true;
                return fex;
            }).ToArray();
            ThreadExecute(executions, 1);

            Console.WriteLine($"*** Open Blocks after first iteration: {memoryTracker.BlockCount} ({memoryTracker.ByteCount} byte)");
            Debug.Assert(memoryTracker.FreedBlockCount == 0, $"{memoryTracker.FreedBlockCount} blocks were freed that were not allocated");

            foreach (var item in executions)
            {
                item.ClearContext();
                item.KeepContext = false;
            }
            Debug.Assert(memoryTracker.FreedBlockCount == 0, $"{memoryTracker.FreedBlockCount} blocks were freed that were not allocated");
            if (memoryTracker.FreedBlockCount != 0)
            {
                memoryTracker.PrintFreedBlocksPerKey();
            }

            memoryTracker.Reset();
            memoryTracker.KeepKeys = false;

            ThreadExecute(executions, 1);
            Debug.Assert(memoryTracker.FreedBlockCount == 0, $"{memoryTracker.FreedBlockCount} blocks were freed that were not allocated");
            if (memoryTracker.FreedBlockCount != 0)
            {
                memoryTracker.PrintFreedBlocksPerKey();
            }
            Debug.Assert(memoryTracker.ByteCount == 0 && memoryTracker.BlockCount == 0, $"*** Open Blocks after second iteration: {memoryTracker.BlockCount} ({memoryTracker.ByteCount} byte)");

            // should not return any keys
            if (memoryTracker.FreedBlockCount != 0)
            {
                memoryTracker.PrintFreedBlocksPerKey();
            }
            memoryTracker.Deassign();

            var first = LKFMemoryLimitSettings.first.GetLimitItems(memoryTracker).ToArray();
            var second = LKFMemoryLimitSettings.second.GetLimitItems(memoryTracker).ToArray();

            foreach (var execution in executions)
            {
                var count = 0;
                foreach (var item in first)
                {
                    /*
                    if (count < 83)
                    {
                        ++count;
                        continue;
                    }
                    */
                    try
                    {
                        LKFController.setMemoryDebugLimit(item.MinKB, 0, item.Key, item.Count);
                        execution.execute();
                       // Debug.Assert(false, $"Key: {item.Key}: no error");
                    }
                    catch (LKFMemoryException mex)
                    {
                        Console.WriteLine($"Key #{count}: {item.Key}: {mex.Message}");
                    }
                    catch (LKFException lex)
                    {
                        Console.WriteLine($"LLKFException for Key #{count}: {item.Key} ({item.Count})");
                        Console.WriteLine(lex.Message);
                        Console.WriteLine(lex.StackTrace);
                        throw;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"LKFException for Key #{count}: {item.Key} ({item.Count})");
                        Console.WriteLine(ex.Message);
                        Console.WriteLine(ex.StackTrace);
                        throw;
                    }
                    ++count;
                }

                count = 0;
                foreach (var item in second)
                {
                    try
                    {
                        LKFController.setMemoryDebugLimit(item.MinKB, 0, item.Key, item.Count);
                        execution.execute();
                      //  Debug.Assert(false, $"Key #B{count}: {item.Key}: no error");
                    }
                    catch (LKFMemoryException mex)
                    {
                        Console.WriteLine($"Key #B{count}: {item.Key}: {mex.Message}");
                    }
                    catch (LKFException lex)
                    {
                        Console.WriteLine($"LLKFException for Key #B{count}: {item.Key} ({item.Count})");
                        Console.WriteLine(lex.Message);
                        Console.WriteLine(lex.StackTrace);
                        throw;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        Console.WriteLine(ex.StackTrace);
                        throw;
                    }
                }
            }
        }


        [TestMethod]
        public void TestMethod2026Memory32()
        {
            LKFMemoryDelegate memoryDelegate;

            var path = CreatePath(TestRoot, "in2026\\memory");
            var files = Directory.GetFiles(path);

            /*
            memoryDelegate = new LKFMemoryDelegate((op, key, ptr, num, elem) =>
            {
                Console.WriteLine($"{op}.{key}: {ptr}");
            }, 0);
            memoryDelegate.Assign();
            */
            var executions = files.Select(file =>
            {
                var fex = new LKFFileExecution(file, 2024, 2026, 1, OutputPath);
                fex.KeepContext = false;
                return fex;
            }).ToArray();
            ThreadExecute(executions, 2);

            //memoryDelegate.Deassign();
        }


        [TestMethod]
        public void TestMethod2024Large()
        {
            var path = CreatePath(TestRoot, "in2024\\large");
            var files = Directory.GetFiles(path);

            var executions = files.Select(file => {
                var ex = new LKFFileExecution(file, 2024, 2024, 1);
                ex.KeepContext = true;
                return ex;
                }
            );

            var repeatedExecutions = LKFFileExecution.Repeat(executions, 8);

            ThreadExecute(repeatedExecutions, 1, 1);
        }


        [TestMethod]
        public void TestMethod2025Repeat()
        {
            var path = CreatePath(TestRoot, "in2025\\repeat");
            var files = Directory.GetFiles(path);

            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2025, 1));
            ThreadExecute(executions, 4, 3);
        }

        [TestMethod]
        public void TestMethod2024Repeat()
        {
            var path = CreatePath(TestRoot, "in2024\\repeat");
            var files = Directory.GetFiles(path);

            var executions = files.Select(file => new LKFFileExecution(file, 2024, 2024, 1));
            ThreadExecute(executions, 4, 3);
        }

        

        LKFExecution CreatePrivateMetaDataExecutions (string file, int year, int flags, int modell, LKFMetaData[] metaData)
        {
            var executions = new List<LKFFileExecution>();
            var enableCompare = false;

            if (modell > 0) {
                var fe = new LKFFileExecution(file, year, modell, flags, OutputPath);
                fe.KeepContext = true;
                enableCompare = true;
                executions.Add(fe);
            }
            if (metaData != null)
            {
                foreach (var item in metaData)
                {
                    var fe = new LKFFileExecution(file, year, 0, flags, AlternateOutputPath, item);
                    if (enableCompare && item.Modelljahr == modell)
                    {
                        fe.KeepContext = true;
                    }
                    executions.Add(fe);
                }
            }
            return new LKFBatchFileExecution(executions.ToArray());
        }

        [TestMethod]
        public void TestPrivateMetaData()
        {
            var path = CreatePath(TestRoot, "in2025\\multi");
            var files = Directory.GetFiles(path);
            LKFController.activateMetaDataSemaphore();

            var metaData = new LKFMetaData[]
            {
                new LKFMetaData(2024, AlternateMetaData2024, "Alternate-2024"),
                new LKFMetaData(2025, AlternateMetaData2025, "Alternate-2025")
            };

            var executions = files.Select(file => CreatePrivateMetaDataExecutions(file, 2024, 1, 2025, metaData)).ToArray();
            ThreadExecute(executions, 2);

            foreach (var item in executions)
            {
                item.validate();   
            }
        }
    }
}
