using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

using lkfxdok.c;
using lkfxdok.LKF;
using lkfxdok.LKF.errors;
using lkfxdok.LKF.stat;

namespace test.LKF
{
    public class FileTest
    {
        int _modellJahr;
        int _abrechJahr;
        string _fileName;
        public int Flags { get; }

        ContextWithData _context;

        public static string TestRoot = "C:\\lkf\\test\\";
        public static string MetaDataPath = TestRoot;

        public FileTest(int modell, int abr, string fileName, int flags = 0)
        {
            _modellJahr = modell;
            _abrechJahr = abr;
            _fileName = fileName;
            Flags = flags;
        }
         public void RunStat ()
        {
            if (_abrechJahr >= 2019)
                Controller.DataPath = "C:\\den_pfad_gibt_es_nicht\\";
            else
                Controller.DataPath = "C:\\lkf\\";
            _context = new ContextForStat();

            try
            {
                _context.Abrechnungsjahr = _abrechJahr;
                _context.Flags = (int) 0;
            }
            catch (LKFException ex)
            {
                Console.WriteLine(ex.Message);
            }
            var content = File.ReadAllBytes(_fileName);
            _context.AddBuffer(content);

            uint aufenthaltCount = _context.AufenthaltCount;
            Console.WriteLine("Aufenthalte: {0}", aufenthaltCount);

            _context.PlausCheck();
            _context.Scoring();

            var stat = _context.StatContext;

            foreach (var a in stat.Abteilungen)
            {
                Console.WriteLine("{0}:{1}", a.Value.ABTFC, a.Value.BT);
            }

            Console.WriteLine("GESBD={0}, AMBULANT={1}", stat.KA.BTGES, stat.GetIntValue(StatType.KA, 0, StatField.Amb));
        }

        public void RunRepeated(int repeat, int flags = 0)
        {
            int c;
            for (c = 0; c < 1; c++)
            {
                String s = Controller.Version;
                if (c == 0) {
                    Console.WriteLine(string.Format("LKFXDOK Version: {0}", s));
                }
                

                Controller.DataPath = "C:\\lkf\\";

                _context = new ContextWithData();

                try
                {
                    _context.Flags = flags;
                    _context.Modelljahr = _modellJahr;
                }
                catch (LKFException ex)
                {
                    Console.WriteLine(ex.Message);
                }

                _context.SetAbrechnungsPeriode(_abrechJahr, 1, 1, LKFContextPeriode.Jahr, -1);

                /* load file to byte array */
                var content = File.ReadAllBytes(_fileName);
                _context.AddBuffer(content);


                //_context.LoadFile("C:\\LKF\\k999j001.s17");

                if (c == 0) {
                    uint aufenthaltCount = _context.AufenthaltCount;
                    Console.WriteLine("Aufenthalte: {0}", aufenthaltCount);
                }

                for (int l = 0; l < 50; l++)
                {
                    _context.PlausCheck();
                    Console.WriteLine($"iteration #{l}: lC:{GC.GetTotalMemory(true)}, private Bytes:{Process.GetCurrentProcess().PrivateMemorySize64}, C-Bytes:{LKFController.getDebugMemoryBytes()}, C-Blocks:{LKFController.getDebugMemoryBlocks()}");
                }
                _context.Scoring();

            }

        }

        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
            {
                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);
        }


        public void RunUnitTest()
        {
            Controller.DataPath = "C:\\lkf\\";
            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);
        }

        public void Run(int flags = 0)
        {
            LKFController.enableMemoryTrace();

            var mt = new MemoryTracker();
            mt.Assign();
            String s = Controller.Version;
            Console.WriteLine(string.Format("LKFXDOK Version: {0}", s));

            Controller.DataPath = MetaDataPath;

            _context = new ContextWithData();

            try
            {
                _context.Modelljahr = _modellJahr;
            }
            catch (LKFException ex)
            {
                Console.WriteLine(ex.Message);
            }

            _context.SetAbrechnungsPeriode(_abrechJahr, 1, 1, LKFContextPeriode.Jahr, flags);

            /* load file to byte array */
            var content = File.ReadAllBytes(_fileName);
            _context.AddBuffer(content);
            mt.PrintMemoryBlocksPerKey();
            Console.WriteLine($"byte={LKFController.getDebugMemoryBytes()}, blocks={LKFController.getDebugMemoryBlocks()}");

            Console.WriteLine(string.Format ("Import: {0} byte -> {1} lines imported ({2} chars), {3} errors", content.Length, _context.CD.LastImportLineCount, _context.CD.LastImportCharCount, _context.CD.LastImportErrorCount));


            //_context.LoadFile("C:\\LKF\\k999j001.s17");

            uint aufenthaltCount = _context.AufenthaltCount;

            Console.WriteLine("Aufenthalte: {0}", aufenthaltCount);

            //_context.C.EnableDebugTrace();
            //_context.C.SetTraceAufenthalt("123");

            _context.PlausCheck();
            _context.Scoring();

            _context.WriteFile(LKFContextFileType.Result, "C:\\LKF\\out\\result.txt");
            _context.WriteFile(LKFContextFileType.Error, "C:\\LKF\\out\\dotneterrors.txt");
            _context.WriteFile(LKFContextFileType.Warning, "C:\\LKF\\out\\dotnetwarnings.txt");

            var akwarnresult = new AkWarnResult(_context.CD);

            foreach (var nhl in akwarnresult.NoHitLines)
            {
                Console.WriteLine(nhl.Line);
            }

            foreach (var aufenthalt in _context.Aufenthalte)
            {

                var aufenthaltResult = aufenthalt.Result;
         //       Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5} TK={6},BD={7}-{8}({9:0000.0}, GESBD={10})", aufenthalt.C.KANR, aufenthalt.C.AUFNR, aufenthalt.C.PlausFlag, aufenthaltResult.GESAMT, aufenthaltResult.MELZU, aufenthaltResult.PLK,
           //         aufenthalt.C.TK, aufenthalt.C.BDU, aufenthalt.C.BDO, aufenthalt.C.BDMW, aufenthalt.C.GESBD));


                var intAufenthalt = _context.IntContext.GetIntAufenthalt(aufenthalt);

                if (intAufenthalt != null)
                {
                    foreach (var abt in intAufenthalt.Abteilungen)
                    {
        //                Console.WriteLine (string.Format ("{0}-{1}: {2}", aufenthalt.AUFNR, abt.ABTPOS, abt.Score));
                    }
                }


                //Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5}", aufenthalt.C.KANR, aufenthalt.C.AUFNR, aufenthalt.C.PlausFlag, aufenthalt.C.GRUPPE, aufenthalt.C.KNOTEN, aufenthalt.C.GesamtPunkte));
                /*
                var lineCount = aufenthalt.C.OutputLineCount;
                //                for (var j = 0; j < lineCount; j++)
                //                {
                //                     Console.WriteLine(aufenthalt.GetOutputLine(j))
                //                }
                */
                if (aufenthalt.C.IsOptimizable)
                {
                    var subgruppeCount = aufenthalt.C.SubgruppeCount;
                    if (subgruppeCount > 1)
                    {
                        for (uint j = 0; j < subgruppeCount; j++)
                        {
                            var subgruppe = aufenthalt.C.GetSubgruppe(j);
      //                      Console.WriteLine(string.Format("     {0}{1}: {2} ({3})", subgruppe.GRUPPE, subgruppe.KNOTEN, subgruppe.Punkte, subgruppe.DIAGIDENT));
                        }
                    }
                }
                /*
                foreach (var abt in aufenthalt.Abteilungen)
                {
                    var abtscor = abt.Scoring;
                    if (abtscor != null && abt.ABTBD > 0)
                    {
                        Console.WriteLine(string.Format("{0}-{1}: {2}/{3}, bd={4}", aufenthalt.AUFNR, abt.ABTPOS, abtscor.KP, abtscor.LP, abt.ABTBD));
                    }
                }

                foreach (var leist in aufenthalt.Leistungen)
                {
                    if (!string.IsNullOrEmpty (leist.MELPOS))
                        Console.WriteLine("   MEL: {0} ANZ: {1}, Punkte={2}, POS={3}", leist.MEL, leist.MELANZ, leist.MELLDFPunkte, leist.MELPOS);
                }
                
                foreach (var diag in aufenthalt.Diagnosen)
                {
                    Console.WriteLine("   DIAG: {0} TYP: {1}, POS={2}", diag.DIAG, diag.DIAGTYP, diag.DIAGPOS);
                }

            */
                foreach (var error in aufenthalt.Warnings)
                {
           //         Console.WriteLine("   Code: {0} Wert: {1} Text: {2}", error.ErrorCode, error.Value, error.ErrorText);
                }
                
            }

            var stat = _context.StatContext;

            foreach (var b in stat.Bundeslaender)
            {
                Console.WriteLine("{0}:{1}", b.Name, b.CountAufenthalte);
            }

            var traceLineCount = _context.C.TraceLineCount;
            for (uint i = 0; i < traceLineCount; i++)
                Console.WriteLine(_context.C.GetTraceLine(i));


            // Error Counts
            Console.WriteLine("errors: {0}, warnings: {1}, infos: {2}", _context.ErrorCount, _context.WarningCount, _context.InfoCount);

            // KA Errors as Object
            var errorFormat = new ContextErrorField[] { ContextErrorField.kanr, ContextErrorField.aufnr, ContextErrorField.gebdat, ContextErrorField.geschl, ContextErrorField.aufdat,
                                                        ContextErrorField.entdat, ContextErrorField.abtfc, ContextErrorField.satzart, ContextErrorField.feldLang, ContextErrorField.code, ContextErrorField.text150char };
            var contextErrors = _context.GetErrors(ContextErrorFlags.warningKasKore, errorFormat);

            foreach (var e in contextErrors.Errors)
            {
                Console.WriteLine("First(KANR)={0}, SATZART={1}, CODE={2}, WERT={3}", e[0], e.GetValueForErrorField(ContextErrorField.satzart), e.GetValueForErrorField(102), e.GetValueForErrorField(ContextErrorField.feldLang));
            }

            var acceptedWarningCount = _context.AcceptedWarningCount;

            // KA Errors as String
            var errorFormatInt = new int[] { 2, 106, 102, 110, 72, 101 };
            var contextStringErrors = _context.GetErrorStrings(ContextErrorFlags.errorKasKore | ContextErrorFlags.errorBericht | ContextErrorFlags.warningKasKore, errorFormatInt);

            foreach (var es in contextStringErrors.Errors)
            {
                Console.WriteLine(es);
            }

            if (_context.AufenthaltCount > 0)
            {
                // load aufenthalt
                var specificAufenthalt = _context.GetAufenthalt(0);
                var firstMel = specificAufenthalt.GetLeistung(0);
                Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5}", specificAufenthalt.C.KANR, specificAufenthalt.C.AUFNR, specificAufenthalt.C.PlausFlag, specificAufenthalt.C.GRUPPE, specificAufenthalt.C.KNOTEN, specificAufenthalt.C.GesamtPunkte));

                if (firstMel != null)
                {
                    firstMel.MEL = "JC060";
                }
                // change data
                specificAufenthalt.AUFNR = "567";

                // execute with changed data
                specificAufenthalt.Execute();
                Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5}", specificAufenthalt.C.KANR, specificAufenthalt.C.AUFNR, specificAufenthalt.C.PlausFlag, specificAufenthalt.C.GRUPPE, specificAufenthalt.C.KNOTEN, specificAufenthalt.C.GesamtPunkte));

                // print
                foreach (var l in specificAufenthalt.Lines)
                {
                    Console.WriteLine(l);
                }

                // Resultat
                var result = specificAufenthalt.Result;
                Console.WriteLine(string.Format("GRUPPE={0}, KNOTEN={1}, PUNKTE={2}", result.GRUPPE, result.KNOTEN, result.GESAMT));

                // compute with other context
                var secondContext = new Context();
                try
                {
                    secondContext.Modelljahr = 2018;
                    secondContext.SetAbrechnungsPeriode(2017, 1, 1, LKFContextPeriode.Jahr, -1);
                    if (firstMel != null) firstMel.MELANZ = "2";
                    specificAufenthalt.Execute(secondContext);
                    Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5}", specificAufenthalt.C.KANR, specificAufenthalt.C.AUFNR, specificAufenthalt.C.PlausFlag, specificAufenthalt.C.GRUPPE, specificAufenthalt.C.KNOTEN, specificAufenthalt.C.GesamtPunkte));

                    // print
                    foreach (var l in specificAufenthalt.Lines)
                    {
                        Console.WriteLine(l);
                    }
                }
                catch (LKFException ex)
                {
                    Console.WriteLine(ex.Message);
                }

                // move to other context
                var thirdContext = new ContextWithData();
                try
                {
                    thirdContext.Modelljahr = 2019;
                    thirdContext.SetAbrechnungsPeriode(2017, 1, 1, LKFContextPeriode.Jahr, -1);
                    var copiedAufenthalt = specificAufenthalt.CopyTo(thirdContext);
                    var copiedFirstMel = copiedAufenthalt.GetLeistung(0);
                    copiedAufenthalt.KANR = "K901";
                    copiedAufenthalt.AUFNR = "2018010101";
                    copiedFirstMel.MEL = "KA010";
                    copiedFirstMel.MELANZ = "1";
                    copiedAufenthalt.Synchronize(); // implicit??

                    thirdContext.PlausCheck();
                    thirdContext.Scoring();

                    foreach (var aufenthalt in thirdContext.Aufenthalte)
                    {
                        foreach (var l in aufenthalt.Lines)
                        {
                            Console.WriteLine(l);
                        }
                        Console.WriteLine(string.Format("{0}-{1}: {2}/{3}{4}: {5}", aufenthalt.C.KANR, aufenthalt.C.AUFNR, aufenthalt.C.PlausFlag, aufenthalt.C.GRUPPE, aufenthalt.C.KNOTEN, aufenthalt.C.GesamtPunkte));
                    }
                }
                catch (LKFException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }


            // kaskore
            var kalines = _context.GetKALines();
            var k01 = kalines.KASKORE.K01;
            foreach (var l in k01.Lines)
            {
                Console.WriteLine(l[0]);
            }
        }
    }
}
