using Adaptation.Eaf.Management.ConfigurationData.CellAutomation;
using Adaptation.Ifx.Eaf.EquipmentConnector.File.Configuration;
using Adaptation.Shared.Methods;
using log4net;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;

namespace Adaptation.Shared;

public class FileRead : Properties.IFileRead
{

    protected string _NullData;
    protected readonly ILog _Log;
    protected long _MinFileLength;
    protected Logistics _Logistics;
    protected readonly ISMTP _SMTP;
    protected readonly int _Hyphens;
    protected readonly bool _IsEvent;
    protected string _ReportFullPath;
    protected long _LastTicksDuration;
    protected readonly bool _IsEAFHosted;
    protected readonly string _EventName;
    protected readonly string _MesEntity;
    protected readonly string _TracePath;
    protected readonly bool _IsDuplicator;
    protected readonly Calendar _Calendar;
    protected readonly bool _IsSourceTimer;
    protected readonly string _VillachPath;
    protected readonly string _ProgressPath;
    protected readonly string _EquipmentType;
    protected readonly long _BreakAfterSeconds;
    protected readonly string _ExceptionSubject;
    protected readonly string _CellInstanceName;
    protected readonly string _EventNameFileRead;
    protected readonly IDescription _Description;
    protected readonly bool _UseCyclicalForDescription;
    protected readonly string _CellInstanceConnectionName;
    protected readonly string _CellInstanceConnectionNameBase;
    protected readonly Dictionary<string, List<long>> _DummyRuns;
    protected readonly Dictionary<string, string> _FileParameter;
    protected readonly string _ParameterizedModelObjectDefinitionType;
    protected readonly FileConnectorConfiguration _FileConnectorConfiguration;
    protected readonly IList<ModelObjectParameterDefinition> _ModelObjectParameterDefinitions;

    bool Properties.IFileRead.IsEvent => _IsEvent;
    string Properties.IFileRead.NullData => _NullData;
    string Properties.IFileRead.EventName => _EventName;
    string Properties.IFileRead.MesEntity => _MesEntity;
    bool Properties.IFileRead.IsEAFHosted => _IsEAFHosted;
    string Properties.IFileRead.EquipmentType => _EquipmentType;
    string Properties.IFileRead.ReportFullPath => _ReportFullPath;
    string Properties.IFileRead.CellInstanceName => _CellInstanceName;
    string Properties.IFileRead.ExceptionSubject => _ExceptionSubject;
    bool Properties.IFileRead.UseCyclicalForDescription => _UseCyclicalForDescription;
    string Properties.IFileRead.CellInstanceConnectionName => _CellInstanceConnectionName;
    string Properties.IFileRead.ParameterizedModelObjectDefinitionType => _ParameterizedModelObjectDefinitionType;

    public FileRead(IDescription description, bool isEvent, ISMTP smtp, Dictionary<string, string> fileParameter, string cellInstanceName, string cellInstanceConnectionName, FileConnectorConfiguration fileConnectorConfiguration, string equipmentTypeName, string parameterizedModelObjectDefinitionType, IList<ModelObjectParameterDefinition> modelObjectParameters, string equipmentDictionaryName, Dictionary<string, List<long>> dummyRuns, bool useCyclicalForDescription, bool isEAFHosted)
    {
        _SMTP = smtp;
        _IsEvent = isEvent;
        _DummyRuns = dummyRuns;
        _LastTicksDuration = 0;
        _IsEAFHosted = isEAFHosted;
        _Description = description;
        _FileParameter = fileParameter;
        _ReportFullPath = string.Empty;
        _CellInstanceName = cellInstanceName;
        _Calendar = new CultureInfo("en-US").Calendar;
        _Log = LogManager.GetLogger(typeof(FileRead));
        _UseCyclicalForDescription = useCyclicalForDescription;
        _CellInstanceConnectionName = cellInstanceConnectionName;
        _ModelObjectParameterDefinitions = modelObjectParameters;
        _FileConnectorConfiguration = fileConnectorConfiguration;
        _ParameterizedModelObjectDefinitionType = parameterizedModelObjectDefinitionType;
        _IsSourceTimer = fileConnectorConfiguration.SourceFileFilter.StartsWith("*Timer.txt");
        string cellInstanceConnectionNameBase = cellInstanceConnectionName.Replace("-", string.Empty);
        _Hyphens = cellInstanceConnectionName.Length - cellInstanceConnectionNameBase.Length;
        _ExceptionSubject = string.Concat("Exception:", _CellInstanceConnectionName, _FileConnectorConfiguration?.SourceDirectoryCloaking);
        string suffix;
        string[] segments = _ParameterizedModelObjectDefinitionType.Split('.');
        string @namespace = segments[0];
        string eventNameFileRead = "FileRead";
        string eventName = segments[segments.Length - 1];
        bool isDuplicator = segments[0] == cellInstanceName;
        _IsDuplicator = isDuplicator;
        _CellInstanceConnectionNameBase = cellInstanceConnectionNameBase;
        if (eventName == eventNameFileRead)
            suffix = string.Empty;
        else
            suffix = string.Concat('_', eventName.Split(new string[] { eventNameFileRead }, StringSplitOptions.RemoveEmptyEntries)[1]);
        string parameterizedModelObjectDefinitionTypeAppended = string.Concat(@namespace, suffix);
        if (!isEAFHosted)
        {
            if (string.IsNullOrEmpty(equipmentTypeName) || equipmentTypeName != parameterizedModelObjectDefinitionTypeAppended)
                throw new Exception(cellInstanceConnectionName);
            if (string.IsNullOrEmpty(equipmentDictionaryName) && isEvent)
                throw new Exception(cellInstanceConnectionName);
            if (!string.IsNullOrEmpty(equipmentDictionaryName) && !isEvent)
                throw new Exception(cellInstanceConnectionName);
            // if (string.IsNullOrEmpty(equipmentDictionaryName) && !isEvent)
            //     throw new Exception(cellInstanceConnectionName);
            // if (!string.IsNullOrEmpty(equipmentDictionaryName) && isEvent)
            //     throw new Exception(cellInstanceConnectionName);
        }
        ModelObjectParameterDefinition[] paths = GetProperties(cellInstanceConnectionName, modelObjectParameters, "Path.");
        if (paths.Length < 3)
            throw new Exception(cellInstanceConnectionName);
        if (isDuplicator)
            _MesEntity = string.Empty;
        else
            _MesEntity = GetPropertyValue(cellInstanceConnectionName, modelObjectParameters, string.Concat("CellInstance.", cellInstanceName, ".Alias"));
        _TracePath = (from l in paths where l.Name.EndsWith("Trace") select l.Value).FirstOrDefault();
        _VillachPath = (from l in paths where l.Name.EndsWith("Villach") select l.Value).FirstOrDefault();
        _ProgressPath = (from l in paths where l.Name.EndsWith("Progress") select l.Value).FirstOrDefault();
        _EventName = eventName;
        _EventNameFileRead = eventNameFileRead;
        _EquipmentType = parameterizedModelObjectDefinitionTypeAppended;
        long breakAfterSeconds;
        if (_FileConnectorConfiguration is null)
            breakAfterSeconds = 360;
        else
        {
            if (_FileConnectorConfiguration.FileScanningOption == FileConnectorConfiguration.FileScanningOptionEnum.TimeBased)
                breakAfterSeconds = 360;
            else
                breakAfterSeconds = Math.Abs(_FileConnectorConfiguration.FileScanningIntervalInSeconds.Value);
        }
        _BreakAfterSeconds = breakAfterSeconds;
        UpdateLastTicksDuration(breakAfterSeconds * 10000000);
        if (_IsDuplicator)
        {
            if (string.IsNullOrEmpty(_FileConnectorConfiguration.TargetFileLocation) || string.IsNullOrEmpty(_FileConnectorConfiguration.ErrorTargetFileLocation))
                throw new Exception("_Configuration is empty?");
            if (_FileConnectorConfiguration.TargetFileLocation.Contains('%') || _FileConnectorConfiguration.ErrorTargetFileLocation.Contains('%'))
                throw new Exception("_Configuration is incorrect for a duplicator!");
            if (_FileConnectorConfiguration is not null)
            {
                if (string.IsNullOrEmpty(_FileConnectorConfiguration.SourceDirectoryCloaking))
                    throw new Exception("SourceDirectoryCloaking is empty?");
                if (!_FileConnectorConfiguration.SourceDirectoryCloaking.StartsWith("~"))
                    throw new Exception("SourceDirectoryCloaking is incorrect for a duplicator!");
            }
        }
    }

    protected static string GetPropertyValue(string cellInstanceConnectionName, IList<ModelObjectParameterDefinition> modelObjectParameters, string propertyName)
    {
        string result;
        List<string> results = (from l in modelObjectParameters where l.Name == propertyName select l.Value).ToList();
        if (results.Count != 1)
            throw new Exception(cellInstanceConnectionName);
        result = results[0];
        return result;
    }

    protected static ModelObjectParameterDefinition[] GetProperties(string cellInstanceConnectionName, IList<ModelObjectParameterDefinition> modelObjectParameters, string propertyNamePrefix)
    {
        ModelObjectParameterDefinition[] results = (from l in modelObjectParameters where l.Name.StartsWith(propertyNamePrefix) select l).ToArray();
        if (!results.Any())
            throw new Exception(cellInstanceConnectionName);
        return results;
    }

    protected static ModelObjectParameterDefinition[] GetProperties(string cellInstanceConnectionName, IList<ModelObjectParameterDefinition> modelObjectParameters, string propertyNamePrefix, string propertyNameSuffix)
    {
        ModelObjectParameterDefinition[] results = (from l in modelObjectParameters where l.Name.StartsWith(propertyNamePrefix) && l.Name.EndsWith(propertyNameSuffix) select l).ToArray();
        if (!results.Any())
            throw new Exception(cellInstanceConnectionName);
        return results;
    }

    protected void UpdateLastTicksDuration(long ticksDuration)
    {
        if (ticksDuration < 50000000)
            ticksDuration = 50000000;
        _LastTicksDuration = (long)Math.Ceiling(ticksDuration * .667);
    }

    protected void WaitForThread(Thread thread, List<Exception> threadExceptions)
    {
        if (thread is not null)
        {
            ThreadState threadState;
            for (short i = 0; i < short.MaxValue; i++)
            {
                if (thread is null)
                    break;
                else
                {
                    threadState = thread.ThreadState;
                    if (threadState is not ThreadState.Running and not ThreadState.WaitSleepJoin)
                        break;
                }
                Thread.Sleep(500);
            }
            lock (threadExceptions)
            {
                if (threadExceptions.Any())
                {
                    foreach (Exception item in threadExceptions)
                        _Log.Error(string.Concat(item.Message, Environment.NewLine, Environment.NewLine, item.StackTrace));
                    Exception exception = threadExceptions[0];
                    threadExceptions.Clear();
                    throw exception;
                }
            }
        }
    }

    protected void CreateProgressDirectory(string[] exceptionLines)
    {
        string progressDirectory;
        StringBuilder stringBuilder = new();
        if (_Hyphens == 0)
            progressDirectory = Path.Combine(_ProgressPath, _CellInstanceConnectionName);
        else
        {
            _ = stringBuilder.Clear();
            for (int i = 0; i < _Hyphens; i++)
            {
                if (i > 0 && (i % 2) == 0)
                    _ = stringBuilder.Append(' ');
                _ = stringBuilder.Append('-');
            }
            progressDirectory = string.Concat(_ProgressPath, @"\", (_Hyphens + 1).ToString().PadLeft(2, '0'), " ", stringBuilder).Trim();
        }
        DateTime dateTime = DateTime.Now;
        string weekOfYear = _Calendar.GetWeekOfYear(dateTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
        progressDirectory = string.Concat(progressDirectory, @"\", dateTime.ToString("yyyy"), "_Week_", weekOfYear, @"\", _Logistics.MID, "_", _Logistics.Sequence, "_", DateTime.Now.Ticks - _Logistics.Sequence);
        if (!Directory.Exists(progressDirectory))
            _ = Directory.CreateDirectory(progressDirectory);
        if (exceptionLines is not null)
        {
            string fileName = string.Concat(progressDirectory, @"\readme.txt");
            try
            { File.WriteAllLines(fileName, exceptionLines); }
            catch (Exception) { }
        }
    }

    protected string[] Move(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, string to, string from, string resolvedFileLocation, Exception exception)
    {
        string[] results;
        bool isErrorFile = exception is not null;
        if (!to.EndsWith(@"\"))
            _ = string.Concat(to, @"\");
        if (!isErrorFile)
            results = Array.Empty<string>();
        else
        {
            results = new string[] { _Logistics.Sequence.ToString(), _Logistics.ReportFullPath, from, resolvedFileLocation, to, string.Empty, string.Empty, exception.Message, string.Empty, string.Empty, exception.StackTrace };
            Shared0449(to, results);
        }
        if (extractResults is not null && extractResults.Item4 is not null && extractResults.Item4.Any())
        {
            string itemFile;
            List<string> directories = new();
            foreach (FileInfo sourceFile in extractResults.Item4)
            {
                if (sourceFile.FullName != _Logistics.ReportFullPath)
                {
                    itemFile = sourceFile.FullName.Replace(from, to);
                    Shared1880(itemFile, directories, sourceFile, isErrorFile);
                }
                else if (!isErrorFile && _Logistics is not null)
                    Shared1811(to, sourceFile);
            }
            Shared0231(directories);
        }
        return results;
    }

    protected static IEnumerable<string> GetDirectoriesRecursively(string path, string directoryNameSegment = null)
    {
        Queue<string> queue = new();
        queue.Enqueue(path);
        while (queue.Count > 0)
        {
            path = queue.Dequeue();
            foreach (string subDirectory in Directory.GetDirectories(path))
            {
                queue.Enqueue(subDirectory);
                if (string.IsNullOrEmpty(directoryNameSegment) || Path.GetFileName(subDirectory).Contains(directoryNameSegment))
                    yield return subDirectory;
            }
        }
    }

    protected string GetProcessedDirectory(string progressPath, Logistics logistics, DateTime dateTime, string duplicateDirectory)
    {
        string result = duplicateDirectory;
        string logisticsSequence = logistics.Sequence.ToString();
        string[] matchDirectories;
        if (!_IsEAFHosted)
            matchDirectories = new string[] { Path.GetDirectoryName(Path.GetDirectoryName(logistics.ReportFullPath)) };
        else
            matchDirectories = new string[] { GetDirectoriesRecursively(Path.GetDirectoryName(progressPath), logisticsSequence).FirstOrDefault() };
        if (matchDirectories.Length == 0 || string.IsNullOrEmpty(matchDirectories[0]))
            matchDirectories = Directory.GetDirectories(duplicateDirectory, string.Concat('*', logisticsSequence, '*'), SearchOption.AllDirectories);
        if ((matchDirectories is null) || matchDirectories.Length != 1)
            throw new Exception("Didn't find directory by logistics sequence");
        if (!matchDirectories[0].Contains("_processed"))
        {
            result = string.Concat(matchDirectories[0].Split(new string[] { logisticsSequence }, StringSplitOptions.None)[0], logistics.DateTimeFromSequence.ToString("yyyy-MM-dd_hh;mm_tt_"), dateTime.Ticks - logistics.Sequence, "_processed");
            Directory.Move(matchDirectories[0], result);
            result = string.Concat(result, @"\", logistics.Sequence);
            if (!Directory.Exists(result))
                _ = Directory.CreateDirectory(result);
        }
        return result;
    }

    protected string WriteScopeInfo(string progressPath, Logistics logistics, DateTime dateTime, string duplicateDirectory, List<Tuple<Properties.IScopeInfo, string>> tuples)
    {
        string result = GetProcessedDirectory(progressPath, logistics, dateTime, duplicateDirectory);
        string tupleFile;
        string fileName = Path.GetFileNameWithoutExtension(logistics.ReportFullPath);
        string duplicateFile = string.Concat(result, @"\", fileName, ".pdsf");
        foreach (Tuple<Properties.IScopeInfo, string> tuple in tuples)
        {
            if (tuple.Item1.FileName.StartsWith(@"\"))
                tupleFile = tuple.Item1.FileName;
            else
                tupleFile = string.Concat(result, @"\", fileName, "_", tuple.Item1.FileNameWithoutExtension, ".pdsfc");
            File.WriteAllText(tupleFile, tuple.Item2);
        }
        File.Copy(logistics.ReportFullPath, duplicateFile, overwrite: true);
        return result;
    }

    protected static string GetTupleFile(Logistics logistics, Properties.IScopeInfo scopeInfo, string duplicateDirectory)
    {
        string result;
        string rds;
        string dateValue;
        string datePlaceholder;
        string[] segments = logistics.MID.Split('-');
        if (segments.Length < 2)
            rds = "%RDS%";
        else
            rds = segments[1];
        segments = scopeInfo.FileName.Split(new string[] { "DateTime:" }, StringSplitOptions.RemoveEmptyEntries);
        if (segments.Length == 0)
            result = string.Concat(duplicateDirectory, @"\", scopeInfo.FileNameWithoutExtension.Replace("%RDS%", rds));
        else
        {
            datePlaceholder = "%DateTime%";
            segments = segments[1].Split('%');
            dateValue = logistics.DateTimeFromSequence.ToString(segments[0]);
            foreach (string segment in scopeInfo.FileName.Split('%'))
            {
                if (!segment.Contains(segments[0]))
                    continue;
                datePlaceholder = string.Concat('%', segment, '%');
            }
            result = string.Concat(duplicateDirectory, @"\", scopeInfo.FileName.Replace("%RDS%", rds).Replace(datePlaceholder, dateValue));
        }
        if (result.Contains('%'))
            throw new Exception("Placeholder exists!");
        return result;
    }

    protected void WaitForFileConsumption(string sourceDirectoryCloaking, Logistics logistics, DateTime dateTime, string successDirectory, string duplicateDirectory, string duplicateFile, List<Tuple<Properties.IScopeInfo, string>> tuples)
    {
        bool check;
        long preWait;
        string tupleFile;
        List<int> consumedFileIndices = new();
        List<string> duplicateFiles = new();
        bool moreThanAnHour = (_BreakAfterSeconds > 3600);
        StringBuilder stringBuilder = new();
        long breakAfter = dateTime.AddSeconds(_BreakAfterSeconds).Ticks;
        if (moreThanAnHour)
            preWait = dateTime.AddSeconds(30).Ticks;
        else
            preWait = dateTime.AddTicks(_LastTicksDuration).Ticks;
        if (!tuples.Any())
            duplicateFiles.Add(duplicateFile);
        string fileName = Path.GetFileNameWithoutExtension(logistics.ReportFullPath);
        string successFile = string.Concat(successDirectory, @"\", Path.GetFileName(logistics.ReportFullPath));
        foreach (Tuple<Properties.IScopeInfo, string> tuple in tuples)
        {
            if (tuple.Item1.FileName.StartsWith(@"\"))
                tupleFile = tuple.Item1.FileName;
            else if (!tuple.Item1.FileName.Contains('%'))
                tupleFile = string.Concat(duplicateDirectory, @"\", fileName, "_", tuple.Item1.FileNameWithoutExtension, ".pdsfc");
            else
                tupleFile = GetTupleFile(logistics, tuple.Item1, duplicateDirectory);
            duplicateFiles.Add(tupleFile);
            File.WriteAllText(tupleFile, tuple.Item2);
        }
        for (short i = 0; i < short.MaxValue; i++)
        {
            if (DateTime.Now.Ticks > preWait)
                break;
            Thread.Sleep(500);
        }
        if (!moreThanAnHour)
        {
            for (short z = 0; z < short.MaxValue; z++)
            {
                try
                {
                    check = (string.IsNullOrEmpty(successDirectory) || File.Exists(successFile));
                    if (check)
                    {
                        consumedFileIndices.Clear();
                        for (int i = 0; i < duplicateFiles.Count; i++)
                        {
                            if (!File.Exists(duplicateFiles[i]))
                                consumedFileIndices.Add(i);
                        }
                        if (consumedFileIndices.Count == duplicateFiles.Count)
                            break;
                    }
                }
                catch (Exception) { }
                if (DateTime.Now.Ticks > breakAfter)
                {
                    for (int i = 0; i < duplicateFiles.Count; i++)
                    {
                        if (File.Exists(duplicateFiles[i]))
                        {
                            try
                            { File.Delete(duplicateFiles[i]); }
                            catch (Exception) { }
                            _ = stringBuilder.Append('<').Append(duplicateFiles[i]).Append("> ");
                        }
                    }
                    throw new Exception(string.Concat("After {", _BreakAfterSeconds, "} seconds, right side of {", sourceDirectoryCloaking, "} didn't consume file(s) ", stringBuilder));
                }
                Thread.Sleep(500);
            }
        }
    }

    protected void SetFileParameter(string key, string value)
    {
        if (_FileConnectorConfiguration is null || _FileConnectorConfiguration.TargetFileLocation.Contains(string.Concat("%", key, "%")) || _FileConnectorConfiguration.ErrorTargetFileLocation.Contains(string.Concat("%", key, "%")) || _FileConnectorConfiguration.TargetFileName.Contains(string.Concat("%", key, "%")) || _FileConnectorConfiguration.ErrorTargetFileName.Contains(string.Concat("%", key, "%")))
        {
            if (_FileParameter.ContainsKey(key))
                _FileParameter[key] = value;
            else
                _FileParameter.Add(key, value);
        }
    }

    protected void SetFileParameterLotIDToLogisticsMID(bool includeLogisticsSequence = true)
    {
        string key;
        if (!includeLogisticsSequence)
            key = "LotID";
        else
            key = "LotIDWithLogisticsSequence";
        string value = string.Concat(_Logistics.MID, "_", _Logistics.Sequence, "_", DateTime.Now.Ticks - _Logistics.Sequence);
        SetFileParameter(key, value);
    }

    protected void SetFileParameterLotID(string value, bool includeLogisticsSequence = true)
    {
        string key;
        if (!includeLogisticsSequence)
            key = "LotID";
        else
        {
            key = "LotIDWithLogisticsSequence";
            value = string.Concat(value, "_", _Logistics.Sequence, "_", DateTime.Now.Ticks - _Logistics.Sequence);
        }
        SetFileParameter(key, value);
    }

    protected void WritePDSF(IFileRead fileRead, JsonElement[] jsonElements)
    {
        string directory;
        if (!_CellInstanceConnectionName.StartsWith(_CellInstanceName) && _CellInstanceConnectionNameBase == _EquipmentType)
            directory = Path.Combine(_VillachPath, _EquipmentType, "Target");
        else
            directory = Path.Combine(_TracePath, _EquipmentType, "Source", _CellInstanceName, _CellInstanceConnectionName);
        if (!Directory.Exists(directory))
            _ = Directory.CreateDirectory(directory);
        string file = Path.Combine(directory, string.Concat(_Logistics.MesEntity, "_", _Logistics.Sequence, ".ipdsf"));
        string lines = ProcessDataStandardFormat.GetPDSFText(fileRead, _Logistics, jsonElements, logisticsText: string.Empty);
        File.WriteAllText(file, lines);
        if (_Logistics.TotalSecondsSinceLastWriteTimeFromSequence > 600)
        {
            try
            { File.SetLastWriteTime(file, _Logistics.DateTimeFromSequence); }
            catch (Exception) { }
        }
    }

    protected void Move(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, Exception exception)
    {
        bool isErrorFile = exception is not null;
        if (!isErrorFile && _IsDuplicator)
        {
            if (_IsEAFHosted && !string.IsNullOrEmpty(_ProgressPath))
                CreateProgressDirectory(exceptionLines: null);
        }
        if (!_IsEAFHosted)
        {
            string to;
            if (!_FileConnectorConfiguration.TargetFileLocation.EndsWith(Path.DirectorySeparatorChar.ToString()))
                to = _FileConnectorConfiguration.TargetFileLocation;
            else
                to = Path.GetDirectoryName(_FileConnectorConfiguration.TargetFileLocation);
            foreach (KeyValuePair<string, string> keyValuePair in _FileParameter)
                to = to.Replace(string.Concat('%', keyValuePair.Key, '%'), keyValuePair.Value);
            if (to.Contains('%'))
                _Log.Debug("Can't debug without EAF Hosting");
            else
                _ = Move(extractResults, to, _FileConnectorConfiguration.SourceFileLocation, resolvedFileLocation: string.Empty, exception: null);
        }
    }

    protected void TriggerEvents(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, List<string> headerNames, Dictionary<string, string> keyValuePairs)
    {
        object value;
        string description;
        List<object[]> list;
        for (int i = 0; i < extractResults.Item3.Length; i++)
        {
            _Log.Debug(string.Concat("TriggerEvent - {", _Logistics.ReportFullPath, "} ", i, " of ", extractResults.Item3.Length));
            foreach (JsonProperty jsonProperty in extractResults.Item3[i].EnumerateObject())
            {
                if (jsonProperty.Value.ValueKind != JsonValueKind.String || !keyValuePairs.ContainsKey(jsonProperty.Name))
                    description = string.Empty;
                else
                    description = keyValuePairs[jsonProperty.Name].Split('|')[0];
                if (!_UseCyclicalForDescription || headerNames.Contains(jsonProperty.Name))
                    value = jsonProperty.Value.ToString();
                else
                {
                    list = new List<object[]>();
                    for (int z = 0; z < extractResults.Item3.Length; z++)
                        list.Add(new object[] { z, extractResults.Item3[z].GetProperty(jsonProperty.Name).ToString() });
                    value = list;
                }
            }
            if (_UseCyclicalForDescription)
                break;
        }
    }

    protected Tuple<string, Test[], JsonElement[], List<FileInfo>> ReExtract(IFileRead fileRead, List<string> headerNames, Dictionary<string, string> keyValuePairs)
    {
        Tuple<string, Test[], JsonElement[], List<FileInfo>> results;
        if (!Directory.Exists(_FileConnectorConfiguration.SourceFileLocation))
            results = null;
        else
        {
            string[] segments;
            string[] matches = null;
            foreach (string subSourceFileFilter in _FileConnectorConfiguration.SourceFileFilters)
            {
                segments = subSourceFileFilter.Split('\\');
                if (_FileConnectorConfiguration.IncludeSubDirectories.Value)
                    matches = Directory.GetFiles(_FileConnectorConfiguration.SourceFileLocation, segments.Last(), SearchOption.AllDirectories);
                else
                    matches = Directory.GetFiles(_FileConnectorConfiguration.SourceFileLocation, segments.Last(), SearchOption.TopDirectoryOnly);
                if (matches.Any())
                    break;
            }
            if (matches is null || !matches.Any())
                results = null;
            else
            {
                _ReportFullPath = matches[0];
                results = fileRead.GetExtractResult(_ReportFullPath, _EventName);
                if (!_IsEAFHosted)
                    TriggerEvents(results, headerNames, keyValuePairs);
            }
        }
        return results;
    }

    protected static Dictionary<Test, List<Properties.IDescription>> GetKeyValuePairs(List<Properties.IDescription> descriptions)
    {
        Dictionary<Test, List<Properties.IDescription>> results = new();
        Test testKey;
        for (int i = 0; i < descriptions.Count; i++)
        {
            testKey = (Test)descriptions[i].Test;
            if (!results.ContainsKey(testKey))
                results.Add(testKey, new List<Properties.IDescription>());
            results[testKey].Add(descriptions[i]);
        }
        return results;
    }

    protected static List<Properties.IDescription> GetDuplicatorDescriptions(JsonElement[] jsonElements)
    {
        List<Properties.IDescription> results = new();
        Duplicator.Description description;
        JsonSerializerOptions jsonSerializerOptions = new() { NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString };
        foreach (JsonElement jsonElement in jsonElements)
        {
            if (jsonElement.ValueKind != JsonValueKind.Object)
                throw new Exception();
            description = JsonSerializer.Deserialize<Duplicator.Description>(jsonElement.ToString(), jsonSerializerOptions);
            results.Add(description);
        }
        return results;
    }

    protected static Tuple<Test[], Dictionary<Test, List<Properties.IDescription>>> GetTuple(IFileRead fileRead, IEnumerable<Properties.IDescription> descriptions, bool extra = false)
    {
        Tuple<Test[], Dictionary<Test, List<Properties.IDescription>>> result;
        Dictionary<Test, List<Properties.IDescription>> keyValuePairs = GetKeyValuePairs(descriptions.ToList());
        Test[] tests = (from l in keyValuePairs select l.Key).ToArray();
        fileRead.CheckTests(tests, extra);
        result = new Tuple<Test[], Dictionary<Test, List<Properties.IDescription>>>(tests, keyValuePairs);
        return result;
    }

    protected void Shared0449(string to, string[] exceptionLines)
    {
        if (_IsDuplicator)
            CreateProgressDirectory(exceptionLines: null);
        else
        {
            string fileName = string.Concat(to, @"\readme.txt");
            try
            {
                if (!Directory.Exists(to))
                    _ = Directory.CreateDirectory(to);
                File.WriteAllLines(fileName, exceptionLines);
            }
            catch (Exception ex) { _Log.Error(ex.Message); }
        }
    }

    protected void Shared1880(string itemFile, List<string> directories, FileInfo sourceFile, bool isErrorFile)
    {
        string itemDirectory;
        directories.Add(Path.GetDirectoryName(sourceFile.FullName));
        itemDirectory = Path.GetDirectoryName(itemFile);
        FileConnectorConfiguration.PostProcessingModeEnum processingModeEnum;
        if (!isErrorFile)
            processingModeEnum = _FileConnectorConfiguration.PostProcessingMode.Value;
        else
            processingModeEnum = _FileConnectorConfiguration.ErrorPostProcessingMode.Value;
        if (processingModeEnum != FileConnectorConfiguration.PostProcessingModeEnum.Delete && !Directory.Exists(itemDirectory))
        {
            _ = Directory.CreateDirectory(itemDirectory);
            FileInfo fileInfo = new(_Logistics.ReportFullPath);
            Directory.SetCreationTime(itemDirectory, fileInfo.LastWriteTime);
        }
        if (_IsEAFHosted)
        {
            switch (processingModeEnum)
            {
                case FileConnectorConfiguration.PostProcessingModeEnum.Move:
                    File.Move(sourceFile.FullName, itemFile);
                    break;
                case FileConnectorConfiguration.PostProcessingModeEnum.Copy:
                    File.Copy(sourceFile.FullName, itemFile);
                    break;
                case FileConnectorConfiguration.PostProcessingModeEnum.Delete:
                    File.Delete(sourceFile.FullName);
                    break;
                default:
                    throw new Exception();
            }
        }
    }

    protected void Shared1811(string to, FileInfo sourceFile)
    {
        if (!_IsDuplicator && _FileConnectorConfiguration.SourceFileFilter != "*" && sourceFile.Exists && sourceFile.Length < _MinFileLength)
        {
            string directoryName = Path.GetFileName(to);
            string jobIdDirectory = Path.GetDirectoryName(to);
            DateTime dateTime = DateTime.Now.AddMinutes(-15);
            string weekOfYear = _Calendar.GetWeekOfYear(_Logistics.DateTimeFromSequence, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
            string weekDirectory = string.Concat(_Logistics.DateTimeFromSequence.ToString("yyyy"), "_Week_", weekOfYear, @"\", _Logistics.DateTimeFromSequence.ToString("yyyy-MM-dd"));
            string destinationDirectory = string.Concat(jobIdDirectory, @"\_ Ignore 100 bytes\", weekDirectory, @"\", directoryName);
            if (!Directory.Exists(destinationDirectory))
                _ = Directory.CreateDirectory(destinationDirectory);
            File.Move(sourceFile.FullName, string.Concat(destinationDirectory, @"\", sourceFile.Name));
            try
            {
                string[] checkDirectories = Directory.GetDirectories(jobIdDirectory, "*", SearchOption.TopDirectoryOnly);
                foreach (string checkDirectory in checkDirectories)
                {
                    if (!checkDirectory.Contains('_'))
                        continue;
                    if (Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly).Any())
                        continue;
                    if (Directory.GetFiles(checkDirectory, "*", SearchOption.TopDirectoryOnly).Any())
                        continue;
                    if (Directory.GetDirectories(checkDirectory, "*", SearchOption.AllDirectories).Any())
                        continue;
                    if (Directory.GetFiles(checkDirectory, "*", SearchOption.AllDirectories).Any())
                        continue;
                    if (new DirectoryInfo(checkDirectory).CreationTime > dateTime)
                        continue;
                    Directory.Delete(checkDirectory, recursive: false);
                }
            }
            catch (Exception) { throw; }
        }
    }

    protected void Shared0231(List<string> directories)
    {
        if (_FileConnectorConfiguration.PostProcessingMode != FileConnectorConfiguration.PostProcessingModeEnum.Copy)
        {
            foreach (string directory in (from l in directories orderby l.Split('\\').Length descending select l).Distinct())
            {
                if (Directory.Exists(directory) && !Directory.GetFiles(directory).Any())
                    Directory.Delete(directory);
            }
        }
    }

    protected void Shared0413(DateTime dateTime, bool isDummyRun, string successDirectory, string duplicateDirectory, List<Tuple<Properties.IScopeInfo, string>> tuples, string duplicateFile)
    {
        if (!isDummyRun && _IsEAFHosted)
            WaitForFileConsumption(_FileConnectorConfiguration.SourceDirectoryCloaking, _Logistics, dateTime, successDirectory, duplicateDirectory, duplicateFile, tuples);
        else
        {
            long breakAfter = DateTime.Now.AddSeconds(_FileConnectorConfiguration.ConnectionRetryInterval.Value).Ticks;
            for (short i = 0; i < short.MaxValue; i++)
            {
                if (!_IsEAFHosted || DateTime.Now.Ticks > breakAfter)
                    break;
                Thread.Sleep(500);
            }
        }
    }

    protected static void Shared0607(string reportFullPath, string duplicateDirectory, string logisticsSequence, string destinationDirectory)
    {
        if (destinationDirectory == duplicateDirectory)
            throw new Exception("Check Target File Folder for %LotIDWithLogisticsSequence%_in process on CI (not Duplicator)");
        if (destinationDirectory.EndsWith(logisticsSequence))
            destinationDirectory = Path.GetDirectoryName(destinationDirectory);
        string[] deleteFiles = Directory.GetFiles(destinationDirectory, "*", SearchOption.AllDirectories);
        if (deleteFiles.Length > 250)
            throw new Exception("Safety net!");
        foreach (string file in deleteFiles)
            File.Delete(file);
        Directory.Delete(destinationDirectory, recursive: true);
        File.Delete(reportFullPath);
    }

    protected string[] Shared1567(string reportFullPath, List<Tuple<Properties.IScopeInfo, string>> tuples)
    {
        string[] results;
        string historicalText;
        string logisticsSequence = _Logistics.Sequence.ToString();
        string jobIdDirectory = string.Concat(Path.GetDirectoryName(Path.GetDirectoryName(_FileConnectorConfiguration.TargetFileLocation)), @"\", _Logistics.JobID);
        if (!Directory.Exists(jobIdDirectory))
            _ = Directory.CreateDirectory(jobIdDirectory);
        string[] matchDirectories;
        if (!_IsEAFHosted)
            matchDirectories = new string[] { Path.GetDirectoryName(Path.GetDirectoryName(reportFullPath)) };
        else
            matchDirectories = Directory.GetDirectories(jobIdDirectory, string.Concat(_Logistics.MID, '*', logisticsSequence, '*'), SearchOption.TopDirectoryOnly);
        if ((matchDirectories is null) || matchDirectories.Length != 1)
            throw new Exception("Didn't find directory by logistics sequence");
        string fileName = Path.GetFileNameWithoutExtension(reportFullPath);
        string sequenceDirectory = string.Concat(matchDirectories[0], @"\", logisticsSequence);
        if (!Directory.Exists(sequenceDirectory))
            _ = Directory.CreateDirectory(sequenceDirectory);
        foreach (Tuple<Properties.IScopeInfo, string> tuple in tuples)
        {
            fileName = string.Concat(sequenceDirectory, @"\", fileName, "_", tuple.Item1.FileNameWithoutExtension, ".pdsfc");
            if (_IsEAFHosted)
                File.WriteAllText(fileName, tuple.Item2);
            else
            {
                if (File.Exists(fileName))
                {
                    historicalText = File.ReadAllText(fileName);
                    if (tuple.Item2 != historicalText)
                        throw new Exception("File doesn't match historical!");
                }
            }
        }
        results = matchDirectories;
        return results;
    }

    protected void Shared1277(string reportFullPath, string destinationDirectory, string logisticsSequence, string jobIdDirectory, string json)
    {
        string ecCharacterizationSi = Path.GetDirectoryName(Path.GetDirectoryName(jobIdDirectory));
        string destinationJobIdDirectory = string.Concat(ecCharacterizationSi, @"\Processed\", _Logistics.JobID);
        if (!Directory.Exists(destinationJobIdDirectory))
            _ = Directory.CreateDirectory(destinationJobIdDirectory);
        destinationJobIdDirectory = string.Concat(destinationJobIdDirectory, @"\", Path.GetFileName(destinationDirectory).Split(new string[] { logisticsSequence }, StringSplitOptions.None)[0], _Logistics.DateTimeFromSequence.ToString("yyyy-MM-dd_hh;mm_tt_"), DateTime.Now.Ticks - _Logistics.Sequence);
        string sequenceDirectory = string.Concat(destinationJobIdDirectory, @"\", logisticsSequence);
        string jsonFileName = string.Concat(sequenceDirectory, @"\", Path.GetFileNameWithoutExtension(reportFullPath), ".json");
        Directory.Move(destinationDirectory, destinationJobIdDirectory);
        if (!Directory.Exists(sequenceDirectory))
            _ = Directory.CreateDirectory(sequenceDirectory);
        File.Copy(reportFullPath, string.Concat(sequenceDirectory, @"\", Path.GetFileName(reportFullPath)), overwrite: true);
        File.WriteAllText(jsonFileName, json);
    }

}

// 2022-02-14 -> Shared - FileRead