//******************************************************************************//
// LogDashboardViewModel.cs
// 12/4/2023
//
// RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
// PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
// AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
// UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
// RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
// CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
// OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
// COMPANY.
//
// THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S. GOVERNMENT
//
// UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
//
// WARNING: THIS DOCUMENT CONTAINS TECHNICAL DATA AND / OR TECHNOLOGY WHOSE
// EXPORT OR DISCLOSURE TO NON-U.S. PERSONS, WHEREVER LOCATED, IS RESTRICTED
// BY THE INTERNATIONAL TRAFFIC IN ARMS REGULATIONS (ITAR) (22 C.F.R. SECTION
// 120-130) OR THE EXPORT ADMINISTRATION REGULATIONS (EAR) (15 C.F.R. SECTION
// 730-774). THIS DOCUMENT CANNOT BE EXPORTED (E.G., PROVIDED TO A SUPPLIER
// OUTSIDE OF THE UNITED STATES) OR DISCLOSED TO A NON-U.S. PERSON, WHEREVER
// LOCATED, UNTIL A FINAL JURISDICTION AND CLASSIFICATION DETERMINATION HAS
// BEEN COMPLETED AND APPROVED BY RAYTHEON, AND ANY REQUIRED U.S. GOVERNMENT
// APPROVALS HAVE BEEN OBTAINED. VIOLATIONS ARE SUBJECT TO SEVERE CRIMINAL
// PENALTIES.
//
// DOD 5220.22-M, INDUSTRIAL SECURITY MANUAL, CHAPTER 5, SECTION 1 THROUGH 9 :
// FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN OR DOD 5200.1-R,
// INFORMATION SECURITY PROGRAM, CHAPTER 6. FOR UNCLASSIFIED, LIMITED DOCUMENTS
// DESTROY BY ANY  METHOD THAT WILL PREVENT DISCLOSURE OF CONTENTS OR
// RECONSTRUCTION OF THE DOCUMENT.
// POC: Alex Kravchenko (1118268)
//******************************************************************************//

// Ignore Spelling: Taskbar Dont

using GalaSoft.MvvmLight;
using NLog;
using Raytheon.LogDashboard.Helpers;
using Raytheon.LogDashboard.Model;
using Raytheon.LogDashboard.View;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using System.Windows.Media;
using GalaSoft.MvvmLight.CommandWpf;

using Application = System.Windows.Application;
using CheckBox = System.Windows.Controls.CheckBox;
using Clipboard = System.Windows.Clipboard;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
using System.Globalization;
using System.Xml;
using Settings = Raytheon.LogDashboard.Model.Settings;
using Raytheon.LogDashboard.Parsers;
using GalaSoft.MvvmLight.Messaging;
using System.Text.RegularExpressions;
using ControlzEx.Theming;

namespace Raytheon.LogDashboard.ViewModel
{
    public class LogDashboardViewModel : ViewModelBase, IDisposable
    {
        #region Properties

        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        private readonly object _logsLockObj = new object();
        private const int RECEIVER_COLUMN_WIDTH = 15;
        private const string TRANSPARENT_COLOR = "#00FFFFFF";

        private bool _filterChanged = false;

        private HashSet<string> _availableLoggers = new HashSet<string>();
        private HashSet<string> _exceptLoggers = new HashSet<string>();
        private HashSet<string> _exceptLoggersWithBuffer = new HashSet<string>();

        private readonly List<Receiver> _receivers;

        private AsyncObservableCollection<LogMessage> _allLogs = new AsyncObservableCollection<LogMessage>();

        private AsyncObservableCollection<LogMessage> _logs = new AsyncObservableCollection<LogMessage>();

        private bool _startIsEnabled = true;
        private bool _pauseIsEnabled = true;
        private bool _startReadFromFileIsEnabled = true;
        private bool _cleanIsEnabled = false;
        private bool _clearSearchResultIsEnabled = false;
        private bool _isVisibleLoader = false;
        private bool _isVisibleProcessBar = false;
        private bool _isEnableLogList = true;
        private bool _isMatchCase = false;
        private bool _isMatchWholeWord = false;
        private bool _isMatchLogLevel = true;
        private bool _useRegularExpressions = false;
        private LogMessage _selectedLog;
        private DashboardLogLevel _selectedMinLogLevel = DashboardLogLevel.Trace;
        private Node _selectedNode;
        private int _receiverColorColumnWidth = 0;
        private bool _allowMaxMessageBufferSize = false;
        private bool _isEnableFindPrevious;
        private int _maxMessageBufferSize = 0;
        private int _deletedMessagesCount = 0;
        private int _processBarValue = 0;
        private string _searchText = string.Empty;
        private string _highlightSearchText = string.Empty;
        private string _loggerHighlightText = string.Empty;
        //private SolidColorBrush _iconColor = (SolidColorBrush)new BrushConverter().ConvertFrom("#3F51B5");
        private SolidColorBrush _fontColor = new SolidColorBrush(Colors.White);
        private bool _isSourceVisible = false;
        private bool _isThreadVisible = true;
        private bool _isEnableClearSearchLoggers;
        private string _searchLoggerText = string.Empty;
        private DateTime _goToTimestampDateTime;
        private DateTime _fromTimeInverval;
        private DateTime _toTimeInverval;
        private bool _isSearchProcess = false;
        private bool _isShowTaskbarProgress = false;
        private readonly List<LogMessage> _nearbyLastLogMessages = new List<LogMessage>();
        private LogMessage _lastLogMessage;
        static readonly DateTime _s1970 = new DateTime(1970, 1, 1);
        protected static LogDashViewModelLocator LocatorInstance => (Application.Current.Resources["LogDashLocator"] as LogDashViewModelLocator);

        private readonly Dictionary<string, List<LogMessage>> _importData = new Dictionary<string, List<LogMessage>>();
        private CancellationTokenSource _cancelImportLogTokenSource = new CancellationTokenSource();

        private SemaphoreSlim _importLogSemaphoreSlim;

        public LogMessage LastLogMessage
        {
            get => _lastLogMessage;
            set
            {
                _lastLogMessage = value;
                _nearbyLastLogMessages.Clear();
                int index = Logs.IndexOf(_lastLogMessage);
                if(index != -1)
                {
                    if(index != 0)
                    {
                        _nearbyLastLogMessages.Add(Logs[index - 1]);
                    }

                    if(Logs.Count > index + 1)
                    {
                        _nearbyLastLogMessages.Add(Logs[index + 1]);
                    }

                    var nearDebug = FindNearMessageByLogLevel(index, DashboardLogLevel.Debug);
                    if(nearDebug != null)
                    {
                        _nearbyLastLogMessages.Add(nearDebug);
                    }

                    var nearWarn = FindNearMessageByLogLevel(index, DashboardLogLevel.Warn);
                    if(nearWarn != null)
                    {
                        _nearbyLastLogMessages.Add(nearWarn);
                    }

                    var nearError = FindNearMessageByLogLevel(index, DashboardLogLevel.Error);
                    if(nearError != null)
                    {
                        _nearbyLastLogMessages.Add(nearError);
                    }

                    var nearFatal = FindNearMessageByLogLevel(index, DashboardLogLevel.Fatal);
                    if(nearFatal != null)
                    {
                        _nearbyLastLogMessages.Add(nearFatal);
                    }
                }
            }
        }

        public static string[] LogTypeArray = {
            "Fatal",
            "FATAL",
            "Error",
            "ERROR",
            "Warn",
            "WARN",
            "Trace",
            "TRACE",
            "Debug",
            "DEBUG",
            "Info",
            "INFO",
            "LOG_INFO_VERBOSE",
            "LOG_INFORMATION",
            "LOG_WARNING",
            "LOG_DEBUG",
            "LOG_ERROR" };

        public static string[] LogTypeArrayAnywhere = {
            "[,|;]\\s*Fatal\\s*[,|;]",
            "[,|;]\\s*FATAL\\s*[,|;]",
            "[,|;]\\s*Error\\s*[,|;]",
            "[,|;]\\s*ERROR\\s*[,|;]",
            "[,|;]\\s*Warn\\s*[,|;]",
            "[,|;]\\s*WARN\\s*[,|;]",
            "[,|;]\\s*Trace\\s*[,|;]",
            "[,|;]\\s*TRACE\\s*[,|;]",
            "[,|;]\\s*Debug\\s*[,|;]",
            "[,|;]\\s*DEBUG\\s*[,|;]",
            "[,|;]\\s*Info\\s*[,|;]",
            "[,|;]\\s*INFO\\s*[,|;]",
            "[,|;]\\s*LOG_INFO_VERBOSE\\s*[,|;]",
            "[,|;]\\s*LOG_INFORMATION\\s*[,|;]",
            "[,|;]\\s*LOG_WARNING\\s*[,|;]",
            "[,|;]\\s*LOG_DEBUG\\s*[,|;]",
            "[,|;]\\s*LOG_ERROR\\s*[,|;]" };

        #endregion

        #region Constructor
        public LogDashboardViewModel()
        {
            Logs = new AsyncObservableCollection<LogMessage>();

            //IconColor = Settings.Instance.CurrentColorTheme.Color;
            FontColor = FontColor.FromARGB(Settings.Instance.FontColor);
            _allowMaxMessageBufferSize = Settings.Instance.IsEnabledMaxMessageBufferSize;
            _maxMessageBufferSize = Settings.Instance.MaxMessageBufferSize;
            _deletedMessagesCount = Settings.Instance.DeletedMessagesCount;
            IsSourceVisible = Settings.Instance.IsShowSourceColumn;
            IsThreadVisible = Settings.Instance.IsShowThreadColumn;

            _receivers = Settings.Instance.Receivers;

            Loggers.Add(new Node
            {
                Logger = "Root",
                Text = "Root",
                IsExpanded = true,
                IsChecked = true,
                Source = "-"
            });

            CreateParsers();

            ColorReceiverColumnWidth = _receivers.Count == 1 || _receivers.Where(r => r.IsActive).All(x => x.Color.Color == Colors.White) ? 0 : RECEIVER_COLUMN_WIDTH;

            Messenger.Default.Register<bool>(this, "SaveSettings", SaveSettingsHandler);

            if(Settings.Instance.AutoStartInStartup)
            {
                Start();
            }

            Messenger.Default.Register<string>(this, "ColorChange", ColorChangeHandler);
            Messenger.Default.Register<string>(this, "ThemeChange", ThemeChangeHandler);

            LocatorInstance.SelectedColorTheme = Settings.Instance.CurrentColorTheme;
            LocatorInstance.SelectedTheme = Settings.Instance.CurrentTheme;
        }

        #endregion

        #region Public Functions
        
        /// <summary>
        /// Clean up resources and stop monitoring files
        /// </summary>
        public void Dispose()
        {
            RemoveAllFileWatchers();

            LogDashViewModelLocator.Settings.StopMonitoringErrorLogger();
            LogDashViewModelLocator.Settings.StopPersistingLogMessages();

            foreach(var receiver in _receivers)
            {
                receiver?.Dispose();
            }
        }

        /// <summary>
        /// if there is an active Error Logger return the default location for the ini file
        /// else return the default location for the 32 bit error logger
        /// </summary>
        /// <returns></returns>
        public string GetErrorLoggerLocation()
        {
            Receiver receiver = _receivers.FirstOrDefault(r => r.ReceiverType == ReceiverType.ErrorLogger && r.IsActive);

            if(receiver != null)
            {
                if(receiver.ErrorLoggerType == ErrorLoggerType.BIT_32)
                {
                    return LocatorInstance.ErrorLogger32DefaultLocation;
                }
                else
                {
                    return LocatorInstance.ErrorLogger64DefaultLocation;
                }
            }
            else
            {
                return LocatorInstance.ErrorLogger32DefaultLocation;
            }
        }

        public List<FileWatcher> FileWatchers { get; set; } = new List<FileWatcher>();

        public bool IsSearchProcess
        {
            get => _isSearchProcess;
            set
            {
                _isSearchProcess = value;
                ClearSearchResultIsEnabled = _isSearchProcess || SearchText.Length > 0;
            }
        }

        public bool StartIsEnabled
        {
            get => _startIsEnabled;
            set
            {
                IsShowTaskbarProgress = !value || !_startReadFromFileIsEnabled;
                Set(() => StartIsEnabled, ref _startIsEnabled, value);
            }
        }

        public bool PauseIsEnabled
        {
            get => _pauseIsEnabled;
            set
            {
                IsShowTaskbarProgress = !value || !_startReadFromFileIsEnabled;
                Set(() => PauseIsEnabled, ref _pauseIsEnabled, value);
            }
        }


        public bool StartReadFromFileIsEnabled
        {
            get => _startReadFromFileIsEnabled;
            set
            {
                Set(() => StartReadFromFileIsEnabled, ref _startReadFromFileIsEnabled, value);

                if(!_startReadFromFileIsEnabled || !StartIsEnabled)
                {
                    IsShowTaskbarProgress = true;
                }
                else
                {
                    IsShowTaskbarProgress = false;
                }
            }
        }

        public bool IsShowTaskbarProgress
        {
            get => _isShowTaskbarProgress;
            set
            {
                Set(() => IsShowTaskbarProgress, ref _isShowTaskbarProgress, value);

                if(value && Settings.Instance.IsShowTaskbarProgress)
                {
                    _isShowTaskbarProgress = true;
                }
                else
                {
                    _isShowTaskbarProgress = false;
                }
            }
        }

        public bool CleanIsEnabled
        {
            get => _cleanIsEnabled;
            set => Set(() => CleanIsEnabled, ref _cleanIsEnabled, value);
        }

        public bool ClearSearchResultIsEnabled
        {
            get => _clearSearchResultIsEnabled;
            set => Set(() => ClearSearchResultIsEnabled, ref _clearSearchResultIsEnabled, value);
        }

        public AsyncObservableCollection<LogMessage> Logs
        {
            get => _logs;
            set => Set(() => Logs, ref _logs, value);
        }

        public AsyncObservableCollection<Node> Loggers { get; set; } = new AsyncObservableCollection<Node>();

        public LogMessage SelectedLog
        {
            get => _selectedLog;
            set
            {
                Set(() => SelectedLog, ref _selectedLog, value);

                IsEnableFindPrevious = !string.IsNullOrEmpty(_searchText) && SelectedLog != null;
                UpdateSelectedNode();
            }
        }

        public Node SelectedNode
        {
            get => _selectedNode;
            set
            {
                Set(() => SelectedNode, ref _selectedNode, value);

                if(_selectedNode != null)
                {
                    UpdateNodeSelections(_selectedNode, false);
                }

                if(_selectedNode != null)
                {
                    UpdateNodeSelections(_selectedNode, true);
                }
            }
        }

        public IEnumerable<DashboardLogLevel> LogLevels => Enum.GetValues(typeof(DashboardLogLevel)).Cast<DashboardLogLevel>().OrderByDescending(x => x);

        public DashboardLogLevel SelectedMinLogLevel
        {
            get => _selectedMinLogLevel;
            set
            {
                Set(() => SelectedMinLogLevel, ref _selectedMinLogLevel, value);

                Task.Run(() =>
                {
                    IsVisibleLoader = true;
                    LastLogMessage = SelectedLog;

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        lock(_logsLockObj)
                        {
                            if(IsSearchProcess)
                            {
                                var searchResult = Logs.Filter(SearchText, IsMatchCase, IsMatchWholeWord, UseRegularExpressions,
                                    IsMatchLogLevel ? SelectedMinLogLevel : DashboardLogLevel.Trace);
                                Application.Current.Dispatcher.Invoke(() =>
                                {
                                    Logs = new AsyncObservableCollection<LogMessage>(searchResult);
                                });
                            }
                            else
                            {
                                switch(SelectedMinLogLevel)
                                {
                                    case DashboardLogLevel.Trace:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                    case DashboardLogLevel.Debug:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.Level.HasFlag(DashboardLogLevel.Trace) && !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                    case DashboardLogLevel.Info:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.Level.HasFlag(DashboardLogLevel.Debug) && !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                    case DashboardLogLevel.Warn:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.Level.HasFlag(DashboardLogLevel.Info) && !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                    case DashboardLogLevel.Error:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.Level.HasFlag(DashboardLogLevel.Warn) && !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                    case DashboardLogLevel.Fatal:
                                        Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.Level.HasFlag(DashboardLogLevel.Error) && !_exceptLoggers.Contains(x.FullPath)));
                                        break;
                                }
                            }
                        }
                    });

                    IsVisibleLoader = false;
                    SelectedLog = GetLastSelecterOrNearbyMessage();
                });
            }
        }

        public bool IsVisibleLoader
        {
            get => _isVisibleLoader;
            set
            {
                Set(() => IsVisibleLoader, ref _isVisibleLoader, value);
                IsEnableLogList = !_isVisibleLoader;
            }
        }

        public bool IsVisibleProcessBar
        {
            get => _isVisibleProcessBar;
            set
            {
                Set(() => IsVisibleProcessBar, ref _isVisibleProcessBar, value);

                if(!_isVisibleProcessBar)
                {
                    ProcessBarValue = 0;
                }

                IsVisibleLoader = _isVisibleProcessBar;
            }
        }

        public bool IsEnableLogList
        {
            get => _isEnableLogList;
            set => Set(() => IsEnableLogList, ref _isEnableLogList, value);
        }

        public bool IsMatchCase
        {
            get => _isMatchCase;
            set
            {
                Set(() => IsMatchCase, ref _isMatchCase, value);

                prevFindNext = string.Empty;
                _filterChanged = true;
            }
        }

        public bool IsMatchWholeWord
        {
            get => _isMatchWholeWord;
            set
            {
                Set(() => IsMatchWholeWord, ref _isMatchWholeWord, value);

                prevFindNext = string.Empty;
                _filterChanged = true;
            }
        }

        public bool IsMatchLogLevel
        {
            get => _isMatchLogLevel;
            set
            {
                Set(() => IsMatchLogLevel, ref _isMatchLogLevel, value);

                _isMatchLogLevel = value;
                _filterChanged = true;
            }
        }

        public bool UseRegularExpressions
        {
            get => _useRegularExpressions;
            set
            {
                _filterChanged = true;
                Set(() => UseRegularExpressions, ref _useRegularExpressions, value);
            }
        }

        public int ColorReceiverColumnWidth
        {
            get => _receiverColorColumnWidth;
            set => Set(() => ColorReceiverColumnWidth, ref _receiverColorColumnWidth, value);
        }

        public string SearchText
        {
            get => _searchText;
            set
            {
                ClearSearchResultIsEnabled = IsSearchProcess || SearchText.Length > 0;
                IsEnableFindPrevious = !string.IsNullOrEmpty(value) && SelectedLog != null;
                Set(() => SearchText, ref _searchText, value);
            }
        }

        public string HighlightSearchText
        {
            get => _highlightSearchText;
            set => Set(() => HighlightSearchText, ref _highlightSearchText, value);
        }

        public string LoggerHighlightText
        {
            get => _loggerHighlightText;
            set => Set(() => LoggerHighlightText, ref _loggerHighlightText, value);
        }

        public bool IsSourceVisible
        {
            get => _isSourceVisible;
            set
            {
                _isSourceVisible = value;
                RaisePropertyChanged(nameof(SourceColumnWidth));
                RaisePropertyChanged();
            }
        }

        public bool IsThreadVisible
        {
            get => _isThreadVisible;
            set
            {
                _isThreadVisible = value;
                RaisePropertyChanged(nameof(ThreadColumnWidth));
                RaisePropertyChanged();
            }
        }

        public double SourceColumnWidth => IsSourceVisible ? 115 : 0;

        public double ThreadColumnWidth => IsThreadVisible ? Double.NaN : 0;

        public SolidColorBrush FontColor
        {
            get => _fontColor;
            set => Set(() => FontColor, ref _fontColor, value);
        }

        public int ProcessBarValue
        {
            get => _processBarValue;
            set
            {
                _processBarValue = value;
                TaskBarFileLoadProgress = _processBarValue == 0 ? 0 : (double)_processBarValue / 100;
                RaisePropertyChanged(nameof(TaskBarFileLoadProgress));
                RaisePropertyChanged();
            }
        }

        public double TaskBarFileLoadProgress { get; set; }

        public bool IsEnableFindPrevious
        {
            get => _isEnableFindPrevious;
            set => Set(() => IsEnableFindPrevious, ref _isEnableFindPrevious, value);
        }

        public bool IsEnableClearSearchLoggers
        {
            get => _isEnableClearSearchLoggers;
            set => Set(() => IsEnableClearSearchLoggers, ref _isEnableClearSearchLoggers, value);
        }

        public string SearchLoggerText
        {
            get => _searchLoggerText;
            set
            {
                _searchLoggerText = value;
                IsEnableClearSearchLoggers = isSearchLoggersProcess || _searchLoggerText.Any();
                RaisePropertyChanged();
            }
        }

        #endregion

        #region Props

        public SolidColorBrush IconColor
        {
            get => SelectedColorTheme.Color;
        }

        private bool showSettings;
        public bool ShowSettings
        {
            get => showSettings;
            set => Set(() => ShowSettings, ref showSettings, value);
        }

        private bool showTree;
        public bool ShowTree
        {
            get => showTree;
            set => Set(() => ShowTree, ref showTree, value);
        }

        private ColorTheme selectedColorTheme;
        public ColorTheme SelectedColorTheme
        {
            get => LocatorInstance.SelectedColorTheme;
            set
            {
                Set(() => SelectedColorTheme, ref selectedColorTheme, value);
                //SelectedFontColor = SelectedColorTheme.Color;
            }
        }

        //public SolidColorBrush SelectedFontColor
        //{
        //    get => SelectedColorTheme.Color;
        //    set
        //    {
        //        Set(() => SelectedFontColor, ref fontColor, value);
        //    }
        //}

        #endregion

        #region Commands

        private RelayCommand startCommand;
        private RelayCommand pauseCommand;
        private RelayCommand pauseFileReadingCommand;
        private RelayCommand startFileReadingCommand;
        private RelayCommand cleanCommand;
        private RelayCommand<object> searchLogCommand;
        private RelayCommand findNextCommand;
        private RelayCommand findPreviousCommand;
        private RelayCommand<object> treeViewElementCheckCommand;
        private RelayCommand openSettingsCommand;
        private RelayCommand openTreeCommand;
        private RelayCommand copyMessageCommand;
        private RelayCommand clearLoggersCommand;
        private RelayCommand collapsLoggersCommand;
        private RelayCommand<object> expandChildrenCommand;
        private RelayCommand<object> collapseChildrenCommand;
        private RelayCommand<object> clearChildrenCommand;
        private RelayCommand<object> showOnlyThisLoggerCommand;
        private RelayCommand<object> dontReceiveThisLoggerCommand;
        private RelayCommand<object> ignoreThisIPCommand;
        private RelayCommand<object> dontShowThisLoggerCommand;
        private RelayCommand findNextWarningCommand;
        private RelayCommand findNextErrorCommand;
        private RelayCommand<object> importCommand;
        private RelayCommand<object> exportCommand;
        private RelayCommand сlearSearchResultCommand;
        private RelayCommand searchLoggersCommand;
        private RelayCommand clearSearchLoggerResultCommand;
        private RelayCommand goToTimestampCommand;
        private RelayCommand setTimeIntervalCommand;
        private RelayCommand<object> toggleMarkCommand;
        private RelayCommand findInTreeCommand;

        public RelayCommand StartCommand => startCommand ?? (startCommand = new RelayCommand(Start));
        public RelayCommand PauseCommand => pauseCommand ?? (pauseCommand = new RelayCommand(Pause));
        public RelayCommand StartFileReadingCommand => startFileReadingCommand ?? (startFileReadingCommand = new RelayCommand(StartFileReading));
        public RelayCommand PauseFileReadingCommand => pauseFileReadingCommand ?? (pauseFileReadingCommand = new RelayCommand(StopFileReading));
        public RelayCommand CleanCommand => cleanCommand ?? (cleanCommand = new RelayCommand(Clean));
        public RelayCommand<object> SearchLogCommand => searchLogCommand ?? (searchLogCommand = new RelayCommand<object>(Search));
        public RelayCommand FindNextCommand => findNextCommand ?? (findNextCommand = new RelayCommand(FindNext));
        public RelayCommand FindPreviousCommand => findPreviousCommand ?? (findPreviousCommand = new RelayCommand(FindPrevious));
        public RelayCommand CopyMessageCommand => copyMessageCommand ?? (copyMessageCommand = new RelayCommand(CopyMessage));
        public RelayCommand ClearLoggersCommand => clearLoggersCommand ?? (clearLoggersCommand = new RelayCommand(ClearLoggers));
        public RelayCommand CollapseLoggersCommand => collapsLoggersCommand ?? (collapsLoggersCommand = new RelayCommand(CollapseAllLoggers));
        public RelayCommand<object> TreeViewElementCheckCommand => treeViewElementCheckCommand ?? (treeViewElementCheckCommand = new RelayCommand<object>(TreeViewElementCheck));
        public RelayCommand<object> ExpandChildrenCommand => expandChildrenCommand ?? (expandChildrenCommand = new RelayCommand<object>(ExpandTreeViewChildren));
        public RelayCommand<object> CollapseChildrenCommand => collapseChildrenCommand ?? (collapseChildrenCommand = new RelayCommand<object>(CollapseTreeViewChildren));
        public RelayCommand<object> ClearChildrenCommand => clearChildrenCommand ?? (clearChildrenCommand = new RelayCommand<object>(ClearChildrenLoggers));
        public RelayCommand OpenSettingsCommand => openSettingsCommand ?? (openSettingsCommand = new RelayCommand(OpenSettings));
        public RelayCommand OpenTreeCommand => openTreeCommand ?? (openTreeCommand = new RelayCommand(OpenTree));
        public RelayCommand<object> ShowOnlyThisLoggerCommand => showOnlyThisLoggerCommand ?? (showOnlyThisLoggerCommand = new RelayCommand<object>(ShowOnlyThisLogger));
        public RelayCommand<object> DontReceiveThisLoggerCommand => dontReceiveThisLoggerCommand ?? (dontReceiveThisLoggerCommand = new RelayCommand<object>(DontReceiveThisLogger));
        public RelayCommand<object> IgnoreThisIPCommand => ignoreThisIPCommand ?? (ignoreThisIPCommand = new RelayCommand<object>(IgnoreThisIP));
        public RelayCommand<object> DontShowThisLoggerCommand => dontShowThisLoggerCommand ?? (dontShowThisLoggerCommand = new RelayCommand<object>(DontShowThisLogger));
        public RelayCommand FindNextWarningCommand => findNextWarningCommand ?? (findNextWarningCommand = new RelayCommand(FindNextWarning));
        public RelayCommand FindNextErrorCommand => findNextErrorCommand ?? (findNextErrorCommand = new RelayCommand(FindNextError));
        public RelayCommand<object> ImportCommand => importCommand ?? (importCommand = new RelayCommand<object>(ImportLogs));
        public RelayCommand<object> ExportCommand => exportCommand ?? (exportCommand = new RelayCommand<object>(ExportLogs));
        public RelayCommand ClearSearchResultCommand => сlearSearchResultCommand ?? (сlearSearchResultCommand = new RelayCommand(ClearSearchResult));
        public RelayCommand SearchLoggersCommand => searchLoggersCommand ?? (searchLoggersCommand = new RelayCommand(SearchLoggers));
        public RelayCommand ClearSearchLoggerResultCommand => clearSearchLoggerResultCommand ?? (clearSearchLoggerResultCommand = new RelayCommand(ClearSearchLoggersResult));
        public RelayCommand GoToTimestampCommand => goToTimestampCommand ?? (goToTimestampCommand = new RelayCommand(GoToTimestamp));
        public RelayCommand SetTimeIntervalCommand => setTimeIntervalCommand ?? (setTimeIntervalCommand = new RelayCommand(SetTimeInterval));
        public RelayCommand<object> ToggleMarkCommand => toggleMarkCommand ?? (toggleMarkCommand = new RelayCommand<object>(ToggleMark));
        public RelayCommand FindInTreeCommand => findInTreeCommand ?? (findInTreeCommand = new RelayCommand(FindLoggerInTreeByMessage));

        #endregion

        #region Command Handlers

        private void ThemeChangeHandler(string theme)
        {
            string changedTheme = Settings.Themes.Contains(theme) ? theme : "BaseLight";
            LocatorInstance.SelectedTheme = changedTheme;

            Settings.Instance.CurrentTheme = changedTheme;
            Settings.Instance.Save();
        }

        private void ColorChangeHandler(string selectedColor)
        {
            var colorTheme = Settings.ColorThemes.FirstOrDefault(c => c.Name == selectedColor);
            if(colorTheme != null)
            {
                LocatorInstance.SelectedColorTheme = colorTheme;
                Settings.Instance.CurrentColorTheme = colorTheme;
                Settings.Instance.Save();

                RaisePropertyChanged(() => IconColor);
            }
        }

        private void Start()
        {
            if(!_receivers.Any())
            {
                Messenger.Default.Send("No Receivers Defined in Settings.\nPlease defined at least one receiver", "DisplayCustomMessage");
                return;
            }

            Task.Run(() =>
            {
                try
                {
                    foreach(var receiver in _receivers.Where(r => r.IsActive))
                    {
                        receiver.Parser?.Init();
                    }
                    // no active parsers
                    if(_receivers.All(x => x.Parser == null || !x.Parser.IsInitialized))
                    {
                        return;
                    }

                    StartIsEnabled = false;

                    Parallel.ForEach(_receivers.Where(r => r.Parser != null && r.Parser.IsInitialized), receiver =>
                    {
                        ReadLogs(receiver.Parser);
                    });
                }
                catch(Exception e)
                {
                    _logger.Warn(e, "An error occurred while ReadLogs");
                }
            }).ContinueWith((r) =>
            {
                Parallel.ForEach(_receivers.Where(rsc => rsc.Parser != null && rsc.Parser.IsInitialized), receiver =>
                {
                    receiver.Dispose();
                });
            });
        }

        private void Pause()
        {
            foreach(var receiver in _receivers.Where(r => r.Parser != null && r.Parser.IsInitialized))
            {
                receiver.Dispose();
            }
            StartIsEnabled = true;
        }

        private void StartFileReading()
        {
            StartReadFromFileIsEnabled = false;
            foreach(var fileWatcher in FileWatchers)
            {
                fileWatcher.StartWatch();
                fileWatcher.FileChanged += FileWatcherOnFileChanged;
            }
        }

        private void StopFileReading()
        {
            StartReadFromFileIsEnabled = true;
            foreach(var fileWatcher in FileWatchers)
            {
                fileWatcher.StopWatch();
                fileWatcher.FileChanged -= FileWatcherOnFileChanged;
            }
        }

        private void Clean()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            if(Logs.Any())
            {
                Logs.Clear();
            }

            nextMessages.Clear();
            previousMessages.Clear();
            currentWarnLoggers.Clear();
            currentErrorLoggers.Clear();
            _importData.Clear();
            currentExceptLoggers.Clear();
            _exceptLoggersWithBuffer.Clear();

            _allLogs.Clear();

            lastSelectedMessageCounter = 0;
            lastSelectedPreviousMessageCounter = 0;
            warnSearchCounter = 0;

            SearchText = string.Empty;
            HighlightSearchText = string.Empty;
            prevFindNext = string.Empty;
            prevFindPrevious = string.Empty;

            findNextPrevSelectedLog = null;
            findPrevousPrevSelectedLog = null;
            currentSearchLogger = null;
            prevSelectedWarnLog = null;
            prevSelectedErrorLog = null;

            RemoveAllFileWatchers();

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

            CleanIsEnabled = false;
        }

        private string currentSearch = string.Empty;

        /// <summary>
        /// search logs
        /// </summary>
        /// <param name="obj"></param>
        private void Search(object obj)
        {
            _logger.Debug($"Search with {SearchText}");

            if(IsVisibleLoader)
            {
                return;
            }

            Task searchTask = Task.Run(() =>
            {
                try
                {
                    IsVisibleLoader = true;

                    if(string.IsNullOrEmpty(SearchText))
                    {
                        Application.Current.Dispatcher.Invoke(ClearSearchResult);
                        return;
                    }

                    LastLogMessage = SelectedLog;

                    currentSearch = SearchText;

                    bool isOpenInAnotherWinow = (bool)obj;

                    IsSearchProcess = !isOpenInAnotherWinow;

                    if(Logs.Any())
                    {
                        IEnumerable<LogMessage> searchResult =
                            Logs.Filter(SearchText, IsMatchCase, IsMatchWholeWord, UseRegularExpressions,
                                IsMatchLogLevel ? SelectedMinLogLevel : DashboardLogLevel.Trace);

                        if(isOpenInAnotherWinow)
                        {
                            var logMessages = searchResult as List<LogMessage> ?? searchResult.ToList();
                            if(logMessages.Any())
                            {
                                Application.Current.Dispatcher.Invoke(() =>
                                {
                                    SearchResult sr = new SearchResult(logMessages, SearchText, IsMatchCase);
                                    sr.Show();
                                    sr.ShowLogEvent += delegate (object sender, LogMessage message)
                                    {
                                        SelectedLog = message;
                                    };
                                });
                            }
                            else
                            {
                                Messenger.Default.Send("Nothing found by the search criteria", "DisplayCustomMessage");
                            }
                        }
                        else
                        {
                            HighlightSearchText = SearchText;
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                Logs = new AsyncObservableCollection<LogMessage>(searchResult);
                            });
                        }
                    }
                }
                catch(Exception e)
                {
                    _logger.Warn(e, "An error occurred while Search");
                }
            });
            searchTask.ContinueWith(x =>
            {
                IsVisibleLoader = false;
            });
        }

        private string prevFindNext = string.Empty;
        private List<LogMessage> nextMessages = new List<LogMessage>();
        private int lastSelectedMessageCounter;
        private LogMessage findNextPrevSelectedLog = new LogMessage();

        private void FindNext()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            Task.Run(() =>
            {
                try
                {
                    IsVisibleLoader = true;

                    if(string.IsNullOrEmpty(SearchText) && SelectedLog != null)
                    {
                        SearchText = _selectedLog.Message;
                        HighlightSearchText = SearchText;
                    }

                    if(!string.IsNullOrEmpty(SearchText))
                    {
                        HighlightSearchText = SearchText;

                        if(prevFindNext != SearchText || _filterChanged)
                        {
                            _filterChanged = false;
                            lastSelectedMessageCounter = 0;
                            prevFindNext = SearchText;
                            nextMessages = new List<LogMessage>();
                        }

                        if(lastSelectedMessageCounter >= nextMessages.Count || findNextPrevSelectedLog != SelectedLog)
                        {
                            if(SelectedLog != null)
                            {
                                lastSelectedMessageCounter = 0;
                                int selectedLogIndex = Logs.IndexOf(SelectedLog);
                                lock(_logsLockObj)
                                {
                                    nextMessages = Logs.TakeLast(Logs.Count - selectedLogIndex - 1).ToList();
                                }
                            }

                            if(!nextMessages.Any())
                            {
                                lock(_logsLockObj)
                                {
                                    nextMessages = Logs.Filter(SearchText, IsMatchCase, IsMatchWholeWord, UseRegularExpressions,
                                        IsMatchLogLevel ? SelectedMinLogLevel : DashboardLogLevel.Trace).ToList();
                                }
                            }
                            else
                            {
                                nextMessages = nextMessages.Filter(SearchText, IsMatchCase, IsMatchWholeWord, UseRegularExpressions,
                                        IsMatchLogLevel ? SelectedMinLogLevel : DashboardLogLevel.Trace).ToList();
                            }

                            if(!nextMessages.Any() || nextMessages.Count <= lastSelectedMessageCounter)
                            {
                                if(nextMessages.Count <= lastSelectedMessageCounter)
                                {
                                    return;
                                }

                                SelectedLog = null;
                                return;
                            }
                        }

                        SelectedLog = nextMessages[lastSelectedMessageCounter];
                        findNextPrevSelectedLog = SelectedLog;
                        lastSelectedMessageCounter++;
                    }
                }
                catch(Exception e)
                {
                    _logger.Warn(e, "An error occurred while FindNext");
                }
            }).ContinueWith(x =>
            {
                IsVisibleLoader = false;
            });
        }

        private string prevFindPrevious = string.Empty;
        private List<LogMessage> previousMessages = new List<LogMessage>();
        private int lastSelectedPreviousMessageCounter;
        private LogMessage findPrevousPrevSelectedLog = new LogMessage();

        private void FindPrevious()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            Task.Run(() =>
            {
                try
                {
                    IsVisibleLoader = true;

                    if(string.IsNullOrEmpty(SearchText) && SelectedLog != null)
                    {
                        SearchText = _selectedLog.Message;
                        HighlightSearchText = SearchText;
                    }

                    if(!string.IsNullOrEmpty(SearchText))
                    {
                        HighlightSearchText = SearchText;

                        if(prevFindPrevious != SearchText || _filterChanged)
                        {
                            _filterChanged = false;
                            prevFindPrevious = SearchText;
                            previousMessages = new List<LogMessage>();
                            lastSelectedPreviousMessageCounter = -1;
                        }

                        if(lastSelectedPreviousMessageCounter == -1 || findPrevousPrevSelectedLog != SelectedLog)
                        {
                            int selectedLogIndex = Logs.IndexOf(SelectedLog);
                            lock(_logsLockObj)
                            {
                                previousMessages = Logs.Take(selectedLogIndex).ToList();
                            }

                            if(previousMessages.Any())
                            {
                                previousMessages = previousMessages.Filter(SearchText, IsMatchCase, IsMatchWholeWord, UseRegularExpressions,
                                    IsMatchLogLevel ? SelectedMinLogLevel : DashboardLogLevel.Trace).ToList();
                            }
                            else
                            {
                                return;
                            }

                            lastSelectedPreviousMessageCounter = previousMessages.Count - 1;

                            if(!previousMessages.Any() || lastSelectedPreviousMessageCounter == -1)
                            {
                                return;
                            }
                        }

                        SelectedLog = previousMessages[lastSelectedPreviousMessageCounter];
                        findPrevousPrevSelectedLog = SelectedLog;
                        lastSelectedPreviousMessageCounter--;
                    }
                }
                catch(Exception e)
                {
                    _logger.Warn(e, "An error occurred while FindNext");
                }
            }).ContinueWith(x =>
            {
                IsVisibleLoader = false;
            });
        }

        private void TreeViewElementCheck(object obj)
        {
            IsVisibleLoader = true;
            CheckBox currentCheckBox = (CheckBox)obj;
            Node node = (Node)currentCheckBox.DataContext;
            currentExceptLoggers.Clear();
            LastLogMessage = SelectedLog;

            if(currentCheckBox.IsChecked.HasValue && currentCheckBox.IsChecked.Value)
            {
                if(node.Parent == null)
                {
                    _exceptLoggers.Clear();
                    _exceptLoggersWithBuffer.Clear();
                }
                else
                {
                    UpdateAllChildInExceptLoggers(node, true);
                    _exceptLoggers.Remove(node.Logger);
                    _exceptLoggersWithBuffer.Remove(node.Logger);
                }
                Task.Run(() =>
                {
                    try
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                        {
                            IEnumerable<LogMessage> remainLogs;
                            lock(_logsLockObj)
                            {
                                remainLogs = _allLogs.Where(x => SelectedMinLogLevel.HasFlag(x.Level) && !_exceptLoggers.Contains(x.FullPath));
                            }

                            Logs = new AsyncObservableCollection<LogMessage>(remainLogs);
                        });
                    }
                    catch(Exception e)
                    {
                        _logger.Warn(e, "An error occurred while TreeViewElementCheck");
                    }
                    finally
                    {
                        IsVisibleLoader = false;
                        SelectedLog = GetLastSelecterOrNearbyMessage();
                    }
                });
            }
            else
            {
                UpdateAllChildInExceptLoggers(node);
                currentExceptLoggers.Add(node.Logger);
                _exceptLoggers.Add(node.Logger);
                Task.Run(() =>
                {
                    try
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                        {
                            if(node.Parent == null)
                            {
                                Logs.Clear();
                                return;
                            }

                            if(node.IsRoot)
                            {
                                lock(_logsLockObj)
                                {
                                    Logs = new AsyncObservableCollection<LogMessage>(Logs.Where(x => x.Address != node.Source));
                                }

                                return;
                            }

                            IEnumerable<LogMessage> messagesForShow;
                            lock(_logsLockObj)
                            {
                                messagesForShow = Logs.Where(x => !currentExceptLoggers.Contains(x.FullPath));
                            }

                            Logs = new AsyncObservableCollection<LogMessage>(messagesForShow);
                        });
                    }
                    catch(Exception e)
                    {
                        _logger.Warn(e, "An error occurred while TreeViewElementCheck");
                    }
                    finally
                    {
                        IsVisibleLoader = false;
                        SelectedLog = GetLastSelecterOrNearbyMessage();
                    }
                });
            }
        }

        private void OpenSettings()
        {
            ShowSettings = true;
        }
        private void OpenTree()
        {
            ShowTree = true;
        }

        /// <summary>
        /// Runs after Save Settings button pressed
        /// </summary>
        /// <param name="saveResult"></param>
        private void SaveSettingsHandler(bool saveResult)
        {
            if(!saveResult)
            {
                ShowSettings = false;
                return;
            }

            bool isProgress = !StartIsEnabled;
            Pause();

            Task.Run(() =>
            {
                try
                {
                    IsVisibleLoader = true;

                    _allowMaxMessageBufferSize = Settings.Instance.IsEnabledMaxMessageBufferSize;
                    _maxMessageBufferSize = Settings.Instance.MaxMessageBufferSize;
                    _deletedMessagesCount = Settings.Instance.DeletedMessagesCount;
                    IsSourceVisible = Settings.Instance.IsShowSourceColumn;
                    IsThreadVisible = Settings.Instance.IsShowThreadColumn;

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        FontColor = FontColor.FromARGB(Settings.Instance.FontColor);
                    });

                    //if(Settings.Instance.CurrentColorTheme != null && !Equals(Settings.Instance.CurrentColorTheme.Color, IconColor))
                    //{
                    //    //IconColor = Settings.Instance.CurrentColorTheme.Color;
                    //}

                    foreach(var receiver in _receivers.Where(r => r.Parser != null))
                    {
                        receiver.Parser.IgnoredIPs = Settings.Instance.IgnoredIPs;
                    }

                    foreach(var receiver in Settings.Instance.Receivers)
                    {
                        var foundReceiver = _receivers.FirstOrDefault(x => x.Name == receiver.Name);
                        if(foundReceiver == null)
                        {
                            receiver.Parser = CreateParserBasedOnType(receiver);
                            _receivers.Add(receiver);
                        }
                        else
                        {
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                if(foundReceiver.Color.Color != receiver.Color.Color)
                                {
                                    foundReceiver.Color = receiver.Color;

                                    lock(_logsLockObj)
                                    {
                                        foreach(var logMessage in _allLogs.Where(x => x.Logger == foundReceiver.Name))
                                        {
                                            logMessage.Color = foundReceiver.Color;
                                        }

                                        foreach(var logMessage in Logs.Where(x => x.Logger == foundReceiver.Name))
                                        {
                                            logMessage.Color = foundReceiver.Color;
                                        }
                                    }
                                }
                            });

                            if(foundReceiver.Name != receiver.Name)
                            {
                                foundReceiver.Name = receiver.Name;

                                Parallel.ForEach(_allLogs, log =>
                                {
                                    log.Logger = foundReceiver.Name;
                                });

                                Parallel.ForEach(Logs, log =>
                                {
                                    log.Logger = foundReceiver.Name;
                                });
                            }
                        }
                    }

                    foreach(var receiver in _receivers.ToList())
                    {
                        var foundReceiver = Settings.Instance.Receivers.FirstOrDefault(x => x.Name == receiver.Name);
                        if(foundReceiver == null)
                        {
                            receiver.Dispose();
                            _receivers.Remove(receiver);
                        }
                    }

                    CreateParsers();

                    ColorReceiverColumnWidth = _receivers.Count(res => res.IsActive) == 1 || _receivers.Where(r => r.IsActive).All(x => x.ColorString == Colors.White.ToString())
                                               ? 0 : RECEIVER_COLUMN_WIDTH;
                }
                catch(Exception e)
                {
                    _logger.Warn(e, "An error occurred while save settings");
                }
                finally
                {
                    IsVisibleLoader = false;
                    if(isProgress)
                    {
                        Start();
                    }

                    ShowSettings = false;
                }
            });
        }

        private void CopyMessage()
        {
            if(SelectedLog == null)
            {
                return;
            }

            Clipboard.SetDataObject(SelectedLog.Message);
        }

        private void ClearLoggers()
        {
            bool? isCheckedRoot = Loggers.First().IsChecked;
            bool isExpandedRoot = Loggers.First().IsExpanded;

            Loggers.Clear();
            _availableLoggers.Clear();
            _exceptLoggers.Clear();
            _exceptLoggersWithBuffer.Clear();
            showOnlyThisLoggers.Clear();

            toggledMarksCount = 0;

            Loggers.Add(new Node
            {
                Logger = "Root",
                Text = "Root",
                IsExpanded = isExpandedRoot,
                IsChecked = isCheckedRoot,
                Source = "-"
            });
        }

        private void ExpandTreeViewChildren(object obj)
        {
            if(obj is Node node)
            {
                node.IsExpanded = true;
                if(node.Children.Any())
                {
                    ExpandChild(node.Children.ToList());
                }
            }
        }

        private void CollapseTreeViewChildren(object obj)
        {
            if(!(obj is Node node))
            {
                return;
            }

            if(node.Parent == null && node.Logger == "Root")
            {
                CollapseAllLoggers();
                return;
            }

            node.IsExpanded = false;
            if(node.Children.Any())
            {
                CollapseChild(node.Children.ToList());
            }
        }

        private void CollapseAllLoggers()
        {
            foreach(var child in Loggers[0].Children)
            {
                CollapseTreeViewChildren(child);
            }
        }

        private readonly HashSet<string> currentClearChildrenLoggers = new HashSet<string>();

        private void ClearChildrenLoggers(object obj)
        {
            _logger.Debug($"ClearChildrenLoggers with {obj}");
            Task.Run(() =>
            {
                currentClearChildrenLoggers.Clear();
                Node node = obj as Node;
                try
                {
                    IsVisibleLoader = true;

                    if(node != null)
                    {
                        var currentFileWatcher = FileWatchers.FirstOrDefault(x => x.FilePath.EndsWith(node.Text));
                        if(currentFileWatcher != null)
                        {
                            currentFileWatcher.StopWatch();
                            currentFileWatcher.FileChanged -= FileWatcherOnFileChanged;
                            FileWatchers.Remove(currentFileWatcher);
                            RaisePropertyChanged(nameof(FileWatchers));
                        }

                        if(node.Parent != null)
                        {
                            UpdateLoggersAfterClear(node);

                            _exceptLoggers = _exceptLoggers.Except(currentClearChildrenLoggers).ToHashSet();
                            _exceptLoggersWithBuffer = _exceptLoggersWithBuffer.Except(currentClearChildrenLoggers).ToHashSet();
                            _availableLoggers = _availableLoggers.Except(currentClearChildrenLoggers).ToHashSet();

                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                lock(_logsLockObj)
                                {
                                    _allLogs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !x.FullPath.Contains(node.Logger)));
                                    Logs = new AsyncObservableCollection<LogMessage>(Logs.Where(x => !x.FullPath.Contains(node.Logger)));
                                }
                                var parentNode = node.Parent;
                                parentNode.Children.Remove(node);
                            });
                        }
                        else
                        {
                            ClearLoggers();
                            Clean();
                        }

                        if(_importData.ContainsKey(node.Logger))
                        {
                            _importData.Remove(node.Logger);
                        }
                    }
                }
                catch(Exception ex)
                {
                    _logger.Warn(ex, $"An error occurred while ClearChildrenLoggers with {node}");
                }
            }).ContinueWith(x =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                IsVisibleLoader = false;
            });
        }

        private void UpdateLoggersAfterClear(Node node)
        {
            currentClearChildrenLoggers.Add(node.Logger);
            foreach(var nodeChild in node.Children)
            {
                UpdateLoggersAfterClear(nodeChild);
            }
        }

        private void ShowOnlyThisLogger(object obj)
        {
            _logger.Debug($"ShowOnlyThisLogger with {obj}");
            if(obj is Node node)
            {
                Task.Run(() =>
                {
                    try
                    {
                        LastLogMessage = SelectedLog;
                        IsVisibleLoader = true;

                        _exceptLoggers.Clear();
                        _exceptLoggersWithBuffer.Clear();
                        CheckLoggers(node);

                        if(node.Logger == "Root")
                        {
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                Logs = new AsyncObservableCollection<LogMessage>(_allLogs);
                            });
                        }
                        else
                        {
                            UncheckAllLoggers(Loggers.First(), node.Logger);

                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                _exceptLoggers = _availableLoggers.Except(showOnlyThisLoggers).ToHashSet();
                                lock(_logsLockObj)
                                {
                                    Logs = new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => x.FullPath.Contains(node.Logger)));
                                }
                            });
                        }

                        showOnlyThisLoggers.Clear();
                    }
                    catch(Exception e)
                    {
                        _logger.Warn(e, "An error occurred while ShowOnlyThisLogger.");
                    }
                    finally
                    {
                        IsVisibleLoader = false;
                        SelectedLog = GetLastSelecterOrNearbyMessage();
                    }
                });
            }
        }

        private void DontReceiveThisLogger(object obj)
        {
            Node node = SelectedNode;
            if(obj is Node paramNode)
            {
                node = paramNode;
            }

            if(node == null)
            {
                return;
            }

            node.IsChecked = false;
            _exceptLoggers.Add(node.Logger);
            _exceptLoggersWithBuffer.Add(node.Logger);

            if(node.Children.Any())
            {
                foreach(var nodeChild in node.Children)
                {
                    DontReceiveThisLogger(nodeChild);
                }
            }
        }

        private void IgnoreThisIP(object obj)
        {
            _logger.Debug($"IgnoreThisIP with {obj}");
            var node = SelectedNode;
            if(obj is Node paramNode)
            {
                node = paramNode;
            }

            if(node == null || (node.Parent == null && node.Text == "Root"))
            {
                return;
            }

            HideSelectedNodeAndChild(node);

            while(!node.IsRoot)
            {
                node = node.Parent;
            }

            IPAddress.TryParse(node.Text, out IPAddress ip);

            if(ip == null)
            {
                return;
            }

            var existingIgnoreIP = Settings.Instance.IgnoredIPs.FirstOrDefault(x => x.IP == ip.ToString());
            if(existingIgnoreIP != null)
            {
                existingIgnoreIP.IsActive = true;
            }
            else
            {
                Settings.Instance.IgnoredIPs.Add(new IgnoredIPAddr
                {
                    IP = ip.ToString(),
                    IsActive = true
                });
            }

            foreach(var receiver in _receivers)
            {
                receiver.Parser.IgnoredIPs = Settings.Instance.IgnoredIPs;
            }
            Settings.Instance.Save();
        }

        private void DontShowThisLogger(object obj)
        {
            try
            {
                if(SelectedNode != null)
                {
                    UncheckAndHideLoggers(SelectedNode);
                }
            }
            catch(Exception e)
            {
                _logger.Warn(e, "An error occurred while DontReceiveLogger");
            }
        }

        List<LogMessage> currentWarnLoggers = new List<LogMessage>();
        private LogMessage currentSearchLogger;
        private int warnSearchCounter = 0;
        private LogMessage prevSelectedWarnLog = new LogMessage();

        private void FindNextWarning()
        {
            if(!currentWarnLoggers.Any() || warnSearchCounter >= currentWarnLoggers.Count || prevSelectedWarnLog != SelectedLog)
            {
                if(SelectedLog != null)
                {
                    warnSearchCounter = 0;
                    int selectedLogIndex = Logs.IndexOf(SelectedLog);
                    lock(_logsLockObj)
                    {
                        currentWarnLoggers = Logs.TakeLast(Logs.Count - selectedLogIndex).Where(x => x.Level == DashboardLogLevel.Warn).ToList();
                    }
                }
                else
                {
                    lock(_logsLockObj)
                    {
                        currentWarnLoggers = Logs.Where(x => x.Level == DashboardLogLevel.Warn).ToList();
                    }
                }
            }

            if(currentWarnLoggers.Any() && warnSearchCounter < currentWarnLoggers.Count)
            {
                currentSearchLogger = currentWarnLoggers[warnSearchCounter];
                warnSearchCounter++;
                SelectedLog = currentSearchLogger;
                prevSelectedWarnLog = SelectedLog;
            }
        }

        List<LogMessage> currentErrorLoggers = new List<LogMessage>();
        private int errorSearchCounter = 0;
        private LogMessage prevSelectedErrorLog = new LogMessage();

        private void FindNextError()
        {
            if(!currentErrorLoggers.Any() || errorSearchCounter >= currentErrorLoggers.Count || prevSelectedErrorLog != SelectedLog)
            {
                if(SelectedLog != null)
                {
                    errorSearchCounter = 0;
                    int selectedLogIndex = Logs.IndexOf(SelectedLog);
                    lock(_logsLockObj)
                    {
                        currentErrorLoggers = Logs.TakeLast(Logs.Count - selectedLogIndex).Where(x => x.Level == DashboardLogLevel.Error).ToList();
                    }
                }
                else
                {
                    lock(_logsLockObj)
                    {
                        currentErrorLoggers = Logs.Where(x => x.Level == DashboardLogLevel.Error).ToList();
                    }
                }
            }

            if(currentErrorLoggers.Any() && errorSearchCounter < currentErrorLoggers.Count)
            {
                currentSearchLogger = currentErrorLoggers[errorSearchCounter];
                errorSearchCounter++;
                SelectedLog = currentSearchLogger;
                prevSelectedErrorLog = SelectedLog;
            }
        }

        /// <summary>
        /// Importing log files handler
        /// </summary>
        /// <param name="obj"></param>
        public void ImportLogs(object obj)
        {
            List<ImportLogFile> importLogFiles = new List<ImportLogFile>();
            _cancelImportLogTokenSource = new CancellationTokenSource();

            if(obj is IEnumerable<string> filesPath && filesPath.All(x => !string.IsNullOrEmpty(x) && File.Exists(x)))
            {
                AddImportedLogFiles(filesPath, importLogFiles);
            }
            else if(obj is string logPath && !string.IsNullOrEmpty(logPath) && File.Exists(logPath))
            {
                if(CheckFileExistsInImportLogs(logPath))
                {
                    return;
                }

                AddImportedLogFile(logPath, importLogFiles);
            }
            else
            {
                OpenFileDialog fileDialog = new OpenFileDialog { Filter = "Text files (*.txt;*.log;*.csv)|*.txt;*.log;*.csv| All files (*.*)|*.*", Multiselect = true };
                if(fileDialog.ShowDialog() == true)
                {
                    AddImportedLogFiles(fileDialog.FileNames, importLogFiles);
                }
            }

            if(!importLogFiles.Any())
            {
                return;
            }

            _importLogSemaphoreSlim = new SemaphoreSlim(2, 2);

            Pause();

            List<FileWatcher> currentFileWatchers = new List<FileWatcher>();

            ImportLogsProcessDialog importLogsProcessDialog = null;

            if(importLogFiles.Count > 1)
            {
                ImportLogsProcessViewModel logImportProcessViewModel = LogDashViewModelLocator.ImportLogsProcess;
                logImportProcessViewModel.ImportFiles = new ObservableCollection<ImportLogFile>(importLogFiles);
                importLogsProcessDialog = new ImportLogsProcessDialog();
                importLogsProcessDialog.Show();
                logImportProcessViewModel.ImportProcessDialogResult += (sender, result) =>
                {
                    if(!result)
                    {
                        _cancelImportLogTokenSource.Cancel();
                    }
                };
            }

            IsVisibleProcessBar = true;
            
            List<Task> importLogTasks = BuildLogParsingTask(importLogFiles, currentFileWatchers);

            importLogTasks.ForEach(t => t.Start());

            Task.WhenAll(importLogTasks).ContinueWith(x =>
            {
                try
                {
                    if(_cancelImportLogTokenSource.IsCancellationRequested)
                    {
                        foreach(var importLogFile in importLogFiles)
                        {
                            var currentNode = Loggers[0].Children.FirstOrDefault(l => l.Logger == importLogFile.FilePath);
                            if(currentNode != null)
                            {
                                ClearChildrenLoggers(currentNode);
                            }
                        }
                        _importData.Clear();
                        return;
                    }

                    List<LogMessage> allLogsTemp = _allLogs.ToList();
                    List<LogMessage> logsTemp = Logs.ToList();

                    foreach(var importLog in importLogFiles)
                    {
                        if(!_importData.ContainsKey(importLog.FilePath))
                        {
                            continue;
                        }

                        allLogsTemp.AddRange(_importData[importLog.FilePath]);
                        logsTemp.AddRange(_importData[importLog.FilePath].Where(l => SelectedMinLogLevel.HasFlag(l.Level) && !_exceptLoggers.Contains(l.FullPath)));
                    }

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        Logs = new AsyncObservableCollection<LogMessage>(logsTemp.OrderBy(l => l.Time));
                        _allLogs = new AsyncObservableCollection<LogMessage>(allLogsTemp.OrderBy(l => l.Time));
                    });
                }
                catch(Exception ex)
                {
                    _logger.Error(ex, "An error occurred while add imported logs to list");
                }
                finally
                {
                    foreach(var idata in _importData)
                    {
                        idata.Value.Clear();
                    }

                    CleanIsEnabled = _allLogs.Any();
                    GC.Collect();
                    GC.WaitForFullGCComplete();
                    IsVisibleProcessBar = false;

                    StartReadFromFileIsEnabled = false;
                    foreach(var fileWatcher in currentFileWatchers)
                    {
                        FileWatchers.Add(fileWatcher);
                        fileWatcher.FileChanged += FileWatcherOnFileChanged;
                        fileWatcher.StartWatch();
                        RaisePropertyChanged(nameof(FileWatchers));
                    }

                    Application.Current.Dispatcher.Invoke(() => importLogsProcessDialog?.Close());
                }
            });
        }

        /// <summary>
        /// the main parsing function for several types of files
        /// can be invoked for the new file (just opened) or called from file watcher when something change
        /// </summary>
        /// <param name="importLogFiles"></param>
        /// <param name="currentFileWatchers"></param>
        /// <param name="logFileType"></param>
        /// <param name="position"></param>
        /// <returns></returns>
        private List<Task> BuildLogParsingTask( List<ImportLogFile> importLogFiles, 
                                                List<FileWatcher> currentFileWatchers, 
                                                LogFileType logFileType = LogFileType.Undefined, 
                                                long position = 0)
        {
            List<Task> importLogTasks = new List<Task>();
            foreach(var importLog in importLogFiles)
            {
                Task importLogTask = new Task(() =>
                {
                    LogFileType capturedLogFileType = logFileType;

                    try
                    {
                        _importLogSemaphoreSlim.Wait();

                        LogTemplate template = new LogTemplate();

                        using(FileStream stream = File.Open(importLog.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {
                            using(StreamReader sr = new StreamReader(stream))
                            {

                                if(capturedLogFileType == LogFileType.Undefined)
                                {
                                    // detect the first line and return to the beginning
                                    string firstLine = sr.ReadLine();
                                    sr.BaseStream.Seek(0, SeekOrigin.Begin);
                                    sr.DiscardBufferedData();

                                    capturedLogFileType = template.TryDetectTemplate(firstLine);

                                    if(FileWatchers.All(x => x.FilePath != importLog.FilePath))
                                    {
                                        FileWatcher fileWatcher = new FileWatcher
                                        {
                                            FilePath = importLog.FilePath,
                                            Position = stream.Length,
                                            Template = template,
                                            ImportLogFile = importLog
                                        };
                                        currentFileWatchers.Add(fileWatcher);
                                    }
                                }
                                else
                                {
                                    sr.BaseStream.Position = position;
                                }

                                if(capturedLogFileType == LogFileType.Log4j)
                                {
                                    ParseLog4J(importLog, sr);
                                }
                                else if(capturedLogFileType == LogFileType.EPTS)
                                {
                                    ParseEPTS(importLog, sr);
                                }
                                else if(capturedLogFileType == LogFileType.ErrorLogger)
                                {
                                    ParseErrorLogger(importLog, sr);
                                }
                                else
                                {
                                    ParseCSV(importLog, sr, template);
                                }
                            }
                        }
                    }
                    catch(Exception e)
                    {
                        Messenger.Default.Send($"An error occurred while reading log file {importLog.FilePath}.\n{e.Message}", "DisplayCustomMessage");
                        _logger.Error(e, $"An error occurred while reading log file {importLog.FilePath}\n{e}");
                    }
                    finally
                    {
                        _importLogSemaphoreSlim.Release();
                    }
                });

                importLogTasks.Add(importLogTask);
            }

            return importLogTasks;
        }

        private void ParseLog4J(ImportLogFile importLog, StreamReader sr)
        {
            string line;
            StringBuilder sb = new StringBuilder();

            while((line = sr.ReadLine()) != null && !_cancelImportLogTokenSource.IsCancellationRequested)
            {
                sb.Append(line);
                sb.Append(Environment.NewLine);


                if(line.Contains("</log4j:event>") && line.Length != 0)
                {
                    try
                    {
                        LogParseXml(sb.ToString(), importLog.FilePath);

                        importLog.Process = (int)((double)sr.BaseStream.Position / sr.BaseStream.Length * 100);
                        //ProcessBarValue = (int)(importLogFiles.Sum(x => x.Process) / importLogFiles.Count);
                    }
                    catch(OutOfMemoryException ex)
                    {
                        _logger.Error(ex, "An error occurred while LogParse");
                        throw;
                    }
                    catch(Exception e)
                    {
                        _logger.Error(e, "An error occurred while LogParse");

                        _importData.Remove(importLog.FilePath);
                        var currentNode = Loggers[0].Children.FirstOrDefault(l => l.Logger == importLog.FilePath);
                        if(currentNode != null)
                        {
                            ClearChildrenLoggers(currentNode);
                        }

                        Messenger.Default.Send($"Incorrect log message template!\n{importLog.FilePath}", "DisplayCustomMessage");

                        //throw;
                    }
                    sb = new StringBuilder();
                }
            }
        }
        private void ParseEPTS(ImportLogFile importLog, StreamReader sr)
        {
            string line;
            StringBuilder sb = new StringBuilder();
            ulong sequenceNumber = 0;

            while(((line = sr.ReadLine()) != null || sb.Length > 0) && !_cancelImportLogTokenSource.IsCancellationRequested)
            {
                if(StartsWithDateEPTS(line) || (string.IsNullOrEmpty(line) && sb.Length > 0))
                {
                    string lineToParse = sb.ToString();
                    if(StartsWithDateEPTS(lineToParse))
                    {
                        try
                        {
                            LogParseEPTS(lineToParse, importLog.FilePath, importLog.FileName.Replace(".", "_"), sequenceNumber++);

                            importLog.Process = (int)((double)sr.BaseStream.Position / sr.BaseStream.Length * 100);
                            //ProcessBarValue = (int)(importLogFiles.Sum(x => x.Process) / importLogFiles.Count);
                        }
                        catch(OutOfMemoryException ex)
                        {
                            _logger.Error(ex, "An error occurred while LogParse");
                            throw;
                        }
                        catch(Exception e)
                        {
                            _logger.Error(e, "An error occurred while LogParse");

                            _importData.Remove(importLog.FilePath);
                            var currentNode = Loggers[0].Children.FirstOrDefault(l => l.Logger == importLog.FilePath);
                            if(currentNode != null)
                            {
                                ClearChildrenLoggers(currentNode);
                            }

                            Messenger.Default.Send($"Incorrect log message template!\n{importLog.FilePath}", "DisplayCustomMessage");

                            //throw;
                        }
                    }
                    sb = new StringBuilder();
                }

                if(sb.Length == 0)
                {
                    sb.Append(line);
                }
                else
                {
                    sb.Append($"\n{line}");
                }
            }

        }
        private void ParseErrorLogger(ImportLogFile importLog, StreamReader sr)
        {
            string line;
            StringBuilder sb = new StringBuilder();

            ulong sequenceNumber = 0;
            while(((line = sr.ReadLine()) != null || sb.Length > 0) && !_cancelImportLogTokenSource.IsCancellationRequested)
            {
                if(StartsWithDateErrorLogger(line) || (string.IsNullOrEmpty(line) && sb.Length > 0))
                {
                    string lineToParse = sb.ToString();
                    if(StartsWithDateErrorLogger(lineToParse))
                    {
                        try
                        {
                            LogParseErrorLogger(lineToParse, importLog.FilePath, importLog.FileName.Replace(".", "_"), sequenceNumber++);

                            importLog.Process = (int)((double)sr.BaseStream.Position / sr.BaseStream.Length * 100);
                            //ProcessBarValue = (int)(importLogFiles.Sum(x => x.Process) / importLogFiles.Count);
                        }
                        catch(OutOfMemoryException ex)
                        {
                            _logger.Error(ex, "An error occurred while LogParse");
                            throw;
                        }
                        catch(Exception e)
                        {
                            _logger.Error(e, "An error occurred while LogParse");

                            _importData.Remove(importLog.FilePath);
                            var currentNode = Loggers[0].Children.FirstOrDefault(l => l.Logger == importLog.FilePath);
                            if(currentNode != null)
                            {
                                ClearChildrenLoggers(currentNode);
                            }
                            Messenger.Default.Send($"Incorrect log message template!\n{importLog.FilePath}", "DisplayCustomMessage");
                            //throw;
                        }
                    }
                    sb = new StringBuilder();
                }

                if(sb.Length == 0)
                {
                    sb.Append(line);
                }
                else
                {
                    sb.Append($"\n{line}");
                }
            }

        }
        private void ParseCSV(ImportLogFile importLog, StreamReader sr, LogTemplate template)
        {
            string line;
            StringBuilder sb = new StringBuilder();

            string fileName = new FileInfo(importLog.FilePath).Name;

            while((line = sr.ReadLine()) != null && !_cancelImportLogTokenSource.IsCancellationRequested)
            {
                if(line.ContainsAnyOfPattern(LogTypeArrayAnywhere, true))
                {
                    if(line.Length != 0)
                    {
                        try
                        {
                            LogParse(sb.ToString(), template, importLog.FilePath, fileName);
                            importLog.Process = (int)((double)sr.BaseStream.Position / sr.BaseStream.Length * 100);
                            //ProcessBarValue = (int)(importLogFiles.Sum(x => x.Process) / importLogFiles.Count);
                        }
                        catch(OutOfMemoryException ex)
                        {
                            _logger.Error(ex, "An error occurred while LogParse");
                            throw;
                        }
                        catch(Exception e)
                        {
                            _logger.Error(e, "An error occurred while LogParse");

                            _importData.Remove(importLog.FilePath);
                            var currentNode = Loggers[0].Children.FirstOrDefault(l => l.Logger == importLog.FilePath);
                            if(currentNode != null)
                            {
                                ClearChildrenLoggers(currentNode);
                            }

                            Messenger.Default.Send($"Incorrect log message template!\n{importLog.FilePath}", "DisplayCustomMessage");
                            //throw;
                        }
                        sb = new StringBuilder();
                    }
                    sb.Append(line);
                }
                else
                {
                    sb.AppendLine(line);
                }
            }
            LogParse(sb.ToString(), template, importLog.FilePath, fileName);
        }

        private static bool StartsWithDateEPTS(string inputString)
        {
            return inputString.StartsWithPattern("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}");
        }
        private static bool StartsWithDateErrorLogger(string inputString)
        {
            return inputString.StartsWithPattern("\\d{10}\\.\\d{3}");
        }

        private void AddImportedLogFiles(IEnumerable<string> filesPath, List<ImportLogFile> importLogFiles)
        {
            foreach(string filePath in filesPath)
            {
                if(CheckFileExistsInImportLogs(filePath))
                {
                    continue;
                }

                AddImportedLogFile(filePath, importLogFiles);
            }
        }

        private void AddImportedLogFile(string filePath, List<ImportLogFile> importLogFiles)
        {
            _importData.Add(filePath, new List<LogMessage>());
            importLogFiles.Add(new ImportLogFile
            {
                FilePath = filePath,
                Process = 0,
                FileName = Path.GetFileName(filePath)
            });
        }

        private void ExportLogs(object obj)
        {
            _logger.Debug($"ExportLogs with {obj}");

            if(IsVisibleLoader)
            {
                return;
            }

            Pause();

            Node node = null;
            if(obj is Node paramNode)
            {
                node = paramNode;
            }

            string filePrefix = node == null ? string.Empty : $"{node.Text}_";
            SaveFileDialog saveDialog = new SaveFileDialog { DefaultExt = ".txt", Filter = "Text document|*.txt", FileName = $"LogDashboardExportLog_{filePrefix}{DateTime.Now:MM-dd-yy}" };

            if(saveDialog.ShowDialog() == true)
            {
                Task.Run(() =>
                {
                    IsVisibleLoader = true;

                    var logsToExport = node == null || node.Logger == "Root"
                        ? _allLogs
                        : _allLogs.Where(x => x.FullPath.Contains(node.Logger));

                    List<string> txtLogs = logsToExport.Select(logMessage => $"{logMessage.Time:MM-dd-yy HH:mm:ss.ffff};{logMessage.Level};{CheckNullableIntExists(logMessage.ProcessID)}{logMessage.Thread};{logMessage.Address};{logMessage.Logger};{logMessage.Message}").ToList();

                    File.WriteAllLines(saveDialog.FileName, txtLogs, Encoding.UTF8);
                    IsVisibleLoader = false;

                    Process.Start(Path.GetDirectoryName(saveDialog.FileName));
                });
            }
        }

        private void ClearSearchResult()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            if(IsSearchProcess)
            {
                IsSearchProcess = false;
                isTimeIntervalProcess = false;
                Logs = new AsyncObservableCollection<LogMessage>(_allLogs.ToList()
                    .Where(x => SelectedMinLogLevel.HasFlag(x.Level) && !_exceptLoggers.Contains(x.FullPath)));
                SelectedLog = GetLastSelecterOrNearbyMessage();
            }

            SearchText = string.Empty;
            HighlightSearchText = string.Empty;
        }

        private void SearchLoggers()
        {
            if(string.IsNullOrEmpty(SearchLoggerText))
            {
                ClearSearchLoggersResult();
                return;
            }
            LoggerHighlightText = SearchLoggerText;
            exceptParents.Clear();
            FindLastNodesAndUpdateVisibility(Loggers[0]);
            exceptParents.ForEach(x => x.IsVisible = true);
        }

        private void ClearSearchLoggersResult()
        {
            if(isSearchLoggersProcess)
            {
                SetAllLoggersVisible(Loggers[0]);
                SearchLoggerText = string.Empty;
                LoggerHighlightText = string.Empty;
                isSearchLoggersProcess = false;
                IsEnableClearSearchLoggers = false;
            }
            SearchLoggerText = string.Empty;
            LoggerHighlightText = string.Empty;
        }

        private void GoToTimestamp()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            SelectTimestampDialog selectTimestampDialog = new SelectTimestampDialog(SelectedLog?.Time);
            selectTimestampDialog.ShowDialog();

            if(selectTimestampDialog.DialogResult.HasValue && selectTimestampDialog.DialogResult.Value)
            {
                _goToTimestampDateTime = ((SelectTimeIntervalViewModel)selectTimestampDialog.DataContext).SelectedDateTimeTo;
                TimeSpan truncateValue = TimeSpan.FromMinutes(1);

                if(_goToTimestampDateTime.Second != 0)
                {
                    truncateValue = TimeSpan.FromSeconds(1);
                }

                if(_goToTimestampDateTime.Millisecond != 0)
                {
                    truncateValue = TimeSpan.FromMilliseconds(1);
                }

                var firstTimestampLog = Logs.FirstOrDefault(x => x.Time.Truncate(truncateValue) == _goToTimestampDateTime);
                if(firstTimestampLog != null)
                {
                    SelectedLog = firstTimestampLog;
                }
                else
                {
                    Messenger.Default.Send($"Did not find any messages with the following date: {_goToTimestampDateTime.ToString(Settings.Instance.DataFormat)}", "DisplayCustomMessage");
                }
            }
        }

        private bool isTimeIntervalProcess = false;

        private void SetTimeInterval()
        {
            if(IsVisibleLoader)
            {
                return;
            }

            SelectTimeIntervalDialog selectTimeIntervalDialog = new SelectTimeIntervalDialog(SelectedLog?.Time);
            selectTimeIntervalDialog.ShowDialog();
            if(selectTimeIntervalDialog.DialogResult.HasValue && selectTimeIntervalDialog.DialogResult.Value)
            {
                _fromTimeInverval = ((SelectTimeIntervalViewModel)selectTimeIntervalDialog.DataContext).SelectedDateTimeFrom;
                _toTimeInverval = ((SelectTimeIntervalViewModel)selectTimeIntervalDialog.DataContext).SelectedDateTimeTo;

                lock(_logsLockObj)
                {
                    Logs = new AsyncObservableCollection<LogMessage>(Logs.Where(
                        x => x.Time >= _fromTimeInverval &&
                             x.Time <= _toTimeInverval));
                    IsSearchProcess = true;
                    isTimeIntervalProcess = true;
                }
            }
        }

        private int toggledMarksCount = 0;

        private void ToggleMark(object obj)
        {
            if(obj is Node node)
            {
                var receiverColor = _allLogs.FirstOrDefault(x => x.FullPath == node.Logger)?.Color?.Clone();

                bool isSet = false;

                SolidColorBrush currentColor;
                if(node.ToggleMark.Color.ToString() != TRANSPARENT_COLOR)
                {
                    toggledMarksCount--;

                    if(Settings.Instance.ShowMessageHighlightByReceiverColor)
                    {
                        if(receiverColor != null)
                        {
                            receiverColor.Opacity = 0.3;
                            currentColor = receiverColor;
                        }
                        else
                        {
                            currentColor = new SolidColorBrush(Colors.Transparent);
                        }
                    }
                    else
                    {
                        currentColor = new SolidColorBrush(Colors.Transparent);
                    }
                }
                else
                {
                    isSet = true;
                    toggledMarksCount++;

                    Random rnd = new Random();
                    Color randomColor = Color.FromRgb((byte)rnd.Next(256), (byte)rnd.Next(256), (byte)rnd.Next(256));
                    currentColor = new SolidColorBrush(randomColor);
                    currentColor.Opacity = 0.1;
                }

                node.ToggleMark = isSet ? currentColor : new SolidColorBrush(Colors.Transparent);

                lock(_logsLockObj)
                {
                    foreach(var logMessage in Logs.Where(x => x.FullPath.Contains(node.Logger)))
                    {
                        logMessage.ToggleMark = currentColor;
                    }

                    foreach(var logMessage in _allLogs.Where(x => x.FullPath.Contains(node.Logger)))
                    {
                        logMessage.ToggleMark = currentColor;
                    }
                }
            }
        }

        private void FindLoggerInTreeByMessage()
        {
            SearchLoggerText = SelectedLog.FullPath;
            exceptParents.Clear();
            FindLastNodesAndUpdateVisibility(Loggers[0], true);
            exceptParents.ForEach(x => x.IsVisible = true);
            ShowTree = true;
        }

        #endregion

        #region TreeView

        private readonly List<string> showOnlyThisLoggers = new List<string>();

        private void CheckLoggers(Node node)
        {
            node.IsChecked = true;
            showOnlyThisLoggers.Add(node.Logger);
            if(node.Children.Any())
            {
                foreach(var nodeChild in node.Children)
                {
                    CheckLoggers(nodeChild);
                }
            }
        }

        private void UncheckAndHideLoggers(Node node)
        {
            node.IsChecked = false;
            _exceptLoggers.Add(node.Logger);
            lock(_logsLockObj)
            {
                Logs = new AsyncObservableCollection<LogMessage>(Logs.Where(l => !_exceptLoggers.Contains(l.FullPath)));
            }
        }

        private void UncheckAllLoggers(Node node, string exceptLogger = null)
        {
            if(node.Logger == exceptLogger)
            {
                return;
            }

            node.IsChecked = false;
            if(node.Children.Any())
            {
                foreach(var nodeChild in node.Children)
                {
                    UncheckAllLoggers(nodeChild, exceptLogger);
                }
            }
        }

        private readonly List<string> currentExceptLoggers = new List<string>();

        private void UpdateAllChildInExceptLoggers(Node node, bool detele = false)
        {
            var childNodes = node.Children;
            while(childNodes != null && childNodes.Any())
            {
                List<Node> currentNodes = childNodes.ToList();
                foreach(var childNode in currentNodes)
                {
                    if(detele)
                    {
                        _exceptLoggers.Remove(childNode.Logger);
                        _exceptLoggersWithBuffer.Remove(childNode.Logger);
                    }
                    else
                    {
                        currentExceptLoggers.Add(childNode.Logger);
                        _exceptLoggers.Add(childNode.Logger);
                    }

                    if(childNode.Children.Any())
                    {
                        UpdateAllChildInExceptLoggers(childNode, detele);
                    }
                }
                childNodes = null;
            }
        }

        private void ExpandChild(List<Node> nodes)
        {
            foreach(var node in nodes)
            {
                node.IsExpanded = true;
                if(node.Children.Any())
                {
                    ExpandChild(node.Children.ToList());
                }
            }
        }

        private void CollapseChild(List<Node> nodes)
        {
            foreach(var node in nodes)
            {
                node.IsExpanded = false;
                if(node.Children.Any())
                {
                    CollapseChild(node.Children.ToList());
                }
            }
        }

        private void UpdateSelectedNode()
        {
            try
            {
                if(SelectedLog == null)
                {
                    return;
                }

                var nodes = string.IsNullOrEmpty(SelectedLog.ExecutableName) ?
                    new List<string> { SelectedLog.Address } :
                    new List<string> { SelectedLog.Address, SelectedLog.ExecutableName };

                nodes.AddRange(SelectedLog.Logger.Split('.'));

                if(!nodes.Any())
                {
                    return;
                }

                var foundNode = Loggers[0].Children.FirstOrDefault(x => x.Text == nodes[0] || x.Source == nodes[0]);
                if(foundNode == null)
                {
                    return;
                }

                for(int i = 1; i < nodes.Count; i++)
                {
                    var node = foundNode.Children.FirstOrDefault(x => x.Text == nodes[i]);
                    if(node != null)
                    {
                        foundNode = node;
                    }
                }
                if(foundNode.Text != nodes.Last())
                {
                    return;
                }

                SelectedNode = foundNode;
            }
            catch(Exception e)
            {
                _logger.Warn(e, $"An error occurred while UpdateSelectedNode. With {SelectedLog.FullPath}");
            }
        }

        private void UpdateNodeSelections(Node node, bool isSelect)
        {
            node.IsSelected = isSelect;
            if(node.Parent != null)
            {
                UpdateNodeSelections(node.Parent, isSelect);
            }
        }

        private void HideSelectedNodeAndChild(Node node)
        {
            _logger.Debug($"HideSelectedNodeAndChild with {node.Text}");

            try
            {
                LastLogMessage = SelectedLog;
                IsVisibleLoader = true;

                UncheckAllLoggers(node);
                DontReceiveThisLogger(node);

                lock(_logsLockObj)
                {
                    Logs = node.Logger == "Root" ? new AsyncObservableCollection<LogMessage>() : new AsyncObservableCollection<LogMessage>(_allLogs.Where(x => !_exceptLoggers.Contains(x.FullPath)));
                }

                showOnlyThisLoggers.Clear();
            }
            catch(Exception e)
            {
                _logger.Warn(e, "An error occurred while ShowOnlyThisLogger.");
            }
            finally
            {
                IsVisibleLoader = false;
                SelectedLog = GetLastSelecterOrNearbyMessage();
            }
        }

        private Node GetParentFromFullPath(Node root, string logger, string executableName = null)
        {
            List<string> nodesStr = new List<string>();
            if(!string.IsNullOrEmpty(executableName))
            {
                nodesStr.Add(executableName);
            }

            nodesStr.AddRange(logger.Split('.'));

            var parent = root;
            foreach(string node in nodesStr)
            {
                var prevParent = parent;
                parent = parent.Children.FirstOrDefault(x => x.Text == node);
                if(parent != null)
                {
                    continue;
                }

                return prevParent;
            }

            return root;
        }

        private void BuildTreeByMessage(LogMessage log, bool addLog = true)
        {
            Node root = Loggers[0].Children.FirstOrDefault(x => x.Text == log.Address);
            if(root == null)
            {
                Node rootNode = new Node(Loggers[0], log.Address)
                {
                    IsRoot = true,
                    IsExpanded = true,
                    Logger = log.Address,
                    IsChecked = Loggers[0].IsChecked.HasValue && Loggers[0].IsChecked.Value,
                    IsVisible = !isSearchLoggersProcess || log.Address.ToUpper().Contains(SearchLoggerText.ToUpper()),
                    Source = log.Address,
                };
                Loggers[0].Children.Add(rootNode);
                root = rootNode;
            }

            if(toggledMarksCount > 0)
            {
                Node currentNode = GetNodeFromMessage(log);
                log.ToggleMark = currentNode.ToggleMark;
            }

            if(_availableLoggers.Contains(log.FullPath))
            {
                if(addLog)
                {
                    if(SelectedMinLogLevel.HasFlag(log.Level) && !_exceptLoggers.Contains(log.FullPath) && !_exceptLoggersWithBuffer.Contains(log.FullPath) && !IsSearchProcess
                        && (!isTimeIntervalProcess || (isTimeIntervalProcess && log.Time > _fromTimeInverval && log.Time < _toTimeInverval)))
                    {
                        lock(_logsLockObj)
                        {
                            Logs.Add(log);
                        }
                    }

                    if(!_exceptLoggersWithBuffer.Contains(log.FullPath))
                    {
                        lock(_logsLockObj)
                        {
                            _allLogs.Add(log);
                        }
                    }
                }

                CleanIsEnabled = _allLogs.Any();
                return;
            }

            Node currentParent = GetParentFromFullPath(root, log.Logger, log.ExecutableName);

            if((Loggers[0].IsChecked.HasValue && !Loggers[0].IsChecked.Value) ||
                (currentParent.IsChecked.HasValue && !currentParent.IsChecked.Value) || !currentParent.IsChecked.HasValue)
            {
                _exceptLoggers.Add(log.FullPath);
                if(_exceptLoggersWithBuffer.Contains(root.Parent.Logger))
                {
                    _exceptLoggersWithBuffer.Add(log.FullPath);
                }
            }

            _availableLoggers.Add(log.FullPath);

            if(addLog)
            {
                if(SelectedMinLogLevel.HasFlag(log.Level) && !_exceptLoggers.Contains(log.FullPath) && !_exceptLoggersWithBuffer.Contains(log.FullPath) && !IsSearchProcess)
                {
                    lock(_logsLockObj)
                    {
                        Logs.Add(log);
                    }
                }

                if(!_exceptLoggersWithBuffer.Contains(log.FullPath))
                {
                    lock(_logsLockObj)
                    {
                        _allLogs.Add(log);
                    }
                }
            }

            CleanIsEnabled = _allLogs.Any();

            List<string> nodes = log.Logger.Split('.').ToList();

            if(!string.IsNullOrEmpty(log.ExecutableName))
            {
                var exe = root.Children.FirstOrDefault(x => x.Text == log.ExecutableName);
                if(exe == null)
                {
                    Node exeNode = new Node(root, log.ExecutableName)
                    {
                        IsChecked = root.IsChecked,
                        IsVisible = !isSearchLoggersProcess || log.Address.ToUpper().Contains(SearchLoggerText.ToUpper())
                    };
                    root.Children.Add(exeNode);
                    root = exeNode;
                }
                else
                {
                    root = exe;
                }
            }

            Node parent = root;
            foreach(var node in nodes)
            {
                Node prevParent = parent;
                parent = parent.Children.FirstOrDefault(x => x.Text == node);
                if(parent != null)
                {
                    continue;
                }

                Node newNode = new Node(prevParent, node)
                {
                    IsChecked = prevParent.IsChecked.HasValue && prevParent.IsChecked.Value,
                    IsVisible = !isSearchLoggersProcess || log.Address.ToUpper().Contains(SearchLoggerText.ToUpper()),
                };
                prevParent.Children.Add(newNode);
                parent = newNode;
            }
        }

        #region Search in TreeView

        private bool isSearchLoggersProcess = false;
        private readonly List<Node> exceptParents = new List<Node>();

        private void UpdateLoggersVisibility(Node node, bool fullPath = false)
        {
            if(exceptParents.Contains(node))
            {
                return;
            }

            if((fullPath && node.Logger.ToUpper().Contains(SearchLoggerText.ToUpper())) || CheckChildContainsText(node.Children, fullPath) ||
                node.Text.ToUpper().Contains(SearchLoggerText.ToUpper()))
            {
                node.IsVisible = true;
                AddParentsToExcept(node);
                return;
            }

            IsEnableClearSearchLoggers = true;
            isSearchLoggersProcess = true;

            node.IsVisible = false;
            if(node.Parent?.Parent != null)
            {
                UpdateLoggersVisibility(node.Parent, fullPath);
            }
        }

        private void AddParentsToExcept(Node node)
        {
            if(node == null || exceptParents.Contains(node))
            {
                return;
            }

            exceptParents.Add(node);
            if(node.Parent != null)
            {
                AddParentsToExcept(node.Parent);
            }
        }

        private bool CheckChildContainsText(ObservableCollection<Node> nodeChildren, bool fullPath = false)
        {
            if(!nodeChildren.Any())
            {
                return false;
            }

            foreach(var nodeChild in nodeChildren)
            {
                if((fullPath && nodeChild.Logger.ToUpper().Contains(SearchLoggerText.ToUpper())) ||
                    nodeChild.Text.ToUpper().Contains(SearchLoggerText.ToUpper()))
                {
                    return true;
                }

                CheckChildContainsText(nodeChild.Children);
            }

            return false;
        }

        public void FindLastNodesAndUpdateVisibility(Node node, bool fullPath = false)
        {
            if(node.Children.Any())
            {
                foreach(var child in node.Children)
                {
                    child.IsVisible = true;
                    FindLastNodesAndUpdateVisibility(child, fullPath);
                }
            }
            else
            {
                UpdateLoggersVisibility(node, fullPath);
            }
        }

        private void SetAllLoggersVisible(Node node)
        {
            node.IsVisible = true;
            foreach(var nodeChild in node.Children)
            {
                SetAllLoggersVisible(nodeChild);
            }
        }

        #endregion

        private Node GetNodeFromMessage(LogMessage message)
        {
            Node currentNode = Loggers[0].Children.FirstOrDefault(x => x.Text == message.Address);
            Node foundNode = currentNode;

            List<string> nodesStr = new List<string>();
            if(!string.IsNullOrEmpty(message.ExecutableName))
            {
                nodesStr.Add(message.ExecutableName);
            }

            nodesStr.AddRange(message.Logger.Split('.'));

            foreach(string nodeName in nodesStr)
            {
                currentNode = currentNode.Children.FirstOrDefault(x => x.Text == nodeName);
                if(currentNode == null)
                {
                    return foundNode;
                }

                foundNode = currentNode;
            }
            return foundNode;
        }

        #endregion

        #region Private functions

        private void FileWatcherOnFileChanged(object sender, FileWatcher e)
        {
            UpdateLogsFromFile(e);
        }

        private void RemoveAllFileWatchers()
        {
            foreach(var fileWatcher in FileWatchers)
            {
                fileWatcher.StopWatch();
                fileWatcher.FileChanged -= FileWatcherOnFileChanged;
            }
            FileWatchers.Clear();
            RaisePropertyChanged(nameof(FileWatchers));
        }

        /// <summary>
        /// reading log messages from a parser
        /// </summary>
        /// <param name="parser"></param>
        private void ReadLogs(PacketsParser parser)
        {
            CancellationToken token = parser.CancellationToken.Token;
            while(true)
            {
                Thread.Sleep(1);
                try
                {
                    var log = parser.GetLog();

                    if(log == null)
                    {
                        continue;
                    }

                    if(token.IsCancellationRequested)
                    {
                        _logger.Debug($"{parser.Name} cancel requested");
                        return;
                    }

                    if(_allowMaxMessageBufferSize && (_allLogs.Count >= _maxMessageBufferSize || Logs.Count >= _maxMessageBufferSize))
                    {
                        LastLogMessage = SelectedLog;

                        IsVisibleLoader = true;
                        if(_allLogs.Count >= _maxMessageBufferSize)
                        {
                            List<LogMessage> temp = _allLogs.ToList();
                            temp.RemoveRange(0, _deletedMessagesCount);
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                _allLogs = new AsyncObservableCollection<LogMessage>(temp);
                            });
                        }

                        if(Logs.Count >= _maxMessageBufferSize)
                        {
                            List<LogMessage> temp = Logs.ToList();
                            temp.RemoveRange(0, _deletedMessagesCount);
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                Logs = new AsyncObservableCollection<LogMessage>(temp);
                            });
                        }
                        GC.Collect();
                        GC.WaitForPendingFinalizers();

                        IsVisibleLoader = false;
                        SelectedLog = GetLastSelecterOrNearbyMessage();
                    }

                    if(log != null)
                    {
                        var currentReceiver = _receivers.FirstOrDefault(x => x.Name == log.Logger);
                        if(currentReceiver != null)
                        {
                            Application.Current?.Dispatcher.Invoke(() =>
                            {
                                log.Color = currentReceiver.Color;
                                log.Logger = currentReceiver.Name;
                                if(Settings.Instance.ShowMessageHighlightByReceiverColor)
                                {
                                    var messageColor = log.Color.Clone();
                                    messageColor.Opacity = 0.1;
                                    log.ToggleMark = messageColor;
                                }
                            });
                        }

                        Application.Current?.Dispatcher.Invoke(() =>
                        {
                            if(AddLogBySearchFilters(log))
                            {
                                return;
                            }

                            BuildTreeByMessage(log);
                        });
                    }
                }
                catch(Exception e)
                {
                    _logger.Warn(e, $"An error occurred while reading from {parser.Name}");
                }
            }
        }

        private bool AddLogBySearchFilters(LogMessage log)
        {
            if(IsSearchProcess)
            {
                lock(_logsLockObj)
                {
                    _allLogs.Add(log);
                }

                if(isTimeIntervalProcess)
                {
                    if(log.Time > _fromTimeInverval && log.Time < _toTimeInverval && !_exceptLoggers.Contains(log.FullPath))
                    {
                        lock(_logsLockObj)
                        {
                            Logs.Add(log);
                        }
                    }

                    return true;
                }

                if(toggledMarksCount > 0)
                {
                    var currentNode = GetNodeFromMessage(log);
                    log.ToggleMark = currentNode.ToggleMark;
                }

                if(!IsMatchCase && IsMatchLogLevel && SelectedMinLogLevel.HasFlag(log.Level)
                    && log.Message.ToUpper().Contains(currentSearch.ToUpper()) && !_exceptLoggers.Contains(log.FullPath))
                {
                    lock(_logsLockObj)
                    {
                        Logs.Add(log);
                    }

                    return true;
                }
                if(IsMatchCase && IsMatchLogLevel && SelectedMinLogLevel.HasFlag(log.Level)
                    && log.Message.Contains(currentSearch) && !_exceptLoggers.Contains(log.FullPath))
                {
                    lock(_logsLockObj)
                    {
                        Logs.Add(log);
                    }

                    return true;
                }
                if(IsMatchCase && !IsMatchLogLevel && log.Message.Contains(currentSearch) &&
                    !_exceptLoggers.Contains(log.FullPath))
                {
                    lock(_logsLockObj)
                    {
                        Logs.Add(log);
                    }

                    return true;
                }
                if(!IsMatchCase && !IsMatchLogLevel && log.Message.ToUpper().Contains(currentSearch.ToUpper()) &&
                    !_exceptLoggers.Contains(log.FullPath))
                {
                    lock(_logsLockObj)
                    {
                        Logs.Add(log);
                    }

                    return true;
                }
            }
            return false;
        }

        private void CreateParsers()
        {
            if(!_receivers.Any(r => r.IsActive))
            {
                return;
            }

            foreach(Receiver receiver in _receivers.Where(r => r.IsActive))
            {
                if(receiver.Parser == null)
                {
                    receiver.Parser = CreateParserBasedOnType(receiver);
                }
            }
        }

        private PacketsParser CreateParserBasedOnType(Receiver receiver)
        {
            switch(receiver.ReceiverType)
            {
                case ReceiverType.UDP:
                    return new UDPPacketsParser(receiver.Name, receiver.Address, receiver.Port, receiver.IsAutoSave);
                case ReceiverType.WCF:
                    return new WCFPacketsParser(receiver.Name, receiver.Address, receiver.IsAutoSave);
                case ReceiverType.ErrorLogger:
                    return new ErrorLoggerPacketsParser(receiver.Name, receiver.Address, receiver.IsAutoSave);
                case ReceiverType.WinDebug:
                    return new DebugMonitorParser(receiver.Name, receiver.Address, receiver.IsAutoSave);
                default:
                    return null;
            }
        }

        /// <summary>
        /// parse log message based on template
        /// </summary>
        /// <param name="line"></param>
        /// <param name="template"></param>
        /// <param name="importFilePath"></param>
        /// <param name="fileName"></param>
        private void LogParse(string line, LogTemplate template, string importFilePath, string fileName)
        {
            if(string.IsNullOrEmpty(line))
            {
                return;
            }

            string[] log = line.Split(new[] { template.Separator }, StringSplitOptions.None);

            StringBuilder message = new StringBuilder();
            for(int i = template.TemplateParameters[ImportTemplateParameters.message]; i < log.Length; i++)
            {
                if(!string.IsNullOrEmpty(log[i]))
                {
                    message.Append(log[i] + "");
                }
            }

            ImportTemplateParameters dataFormat = ImportTemplateParameters.date;

            dataFormat = template.TemplateParameters.Keys.FirstOrDefault(k =>
                k == ImportTemplateParameters.longdate ||
                k == ImportTemplateParameters.shortdate ||
                k == ImportTemplateParameters.date ||
                k == ImportTemplateParameters.time ||
                k == ImportTemplateParameters.ticks ||
                k == ImportTemplateParameters.eptstime ||
                k == ImportTemplateParameters.errorloggertime);

            DateTime date = DateTime.Now;
            string dateString = log[template.TemplateParameters[dataFormat]]?.Trim();
            switch(dataFormat)
            {
                case ImportTemplateParameters.date:
                case ImportTemplateParameters.longdate:
                case ImportTemplateParameters.shortdate:
                case ImportTemplateParameters.time:
                    if(DateTime.TryParse(dateString.Replace("\0", ""), out DateTime dt))
                    {
                        date = dt;
                    }
                    break;
                case ImportTemplateParameters.ticks:
                    date = new DateTime(long.Parse(dateString.Replace("\0", "")));
                    break;
                case ImportTemplateParameters.eptstime:
                    if(DateTime.TryParseExact(dateString.Replace("\0", ""), "MM-dd-yy HH:mm:ss.ffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
                    {
                        date = dt;
                    }
                    break;
                case ImportTemplateParameters.errorloggertime:
                    if(DateTime.TryParseExact(dateString.Replace("\0", ""), "ddd MMM dd HH:mm:ss yyyy fff\\m\\s", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
                    {
                        date = dt;
                    }
                    break;
                default:
                    break;
            }

            int threadId = 0;
            if(template.TemplateParameters.ContainsKey(ImportTemplateParameters.threadid) && int.TryParse(log[template.TemplateParameters[ImportTemplateParameters.threadid]], out int threadIdParsed))
            {
                threadId = threadIdParsed;
            }

            string logLevelStr = log[template.TemplateParameters[ImportTemplateParameters.level]];
            DashboardLogLevel logLevel = FromLogLevelStringToLogLevel(logLevelStr);
            string logger = template.TemplateParameters.ContainsKey(ImportTemplateParameters.logger) ? log[template.TemplateParameters[ImportTemplateParameters.logger]] : fileName;
            int? processId = template.TemplateParameters.ContainsKey(ImportTemplateParameters.processid) ? int.Parse(log[template.TemplateParameters[ImportTemplateParameters.processid]]) : (int?)null;
            LogMessage lm = new LogMessage
            {
                Address = fileName,
                Time = date,
                Level = logLevel,
                Thread = threadId,
                Message = message.ToString(),
                Logger = logger,
                ProcessID = processId,
            };

            Application.Current.Dispatcher.Invoke(() =>
            {
                BuildTreeByMessage(lm, false);
                _importData[importFilePath].Add(lm);
            });
        }

        /// <summary>
        /// Convert from LogLevel string to Dashboard LogLevel
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static DashboardLogLevel FromLogLevelStringToLogLevel(string data)
        {
            if(string.IsNullOrEmpty(data))
            {
                return DashboardLogLevel.Trace;
            }

            if(data.ContainsAnyOfPattern(new string[] { "Trace", "TRACE", "LOG_INFO_VERBOSE" }, true))
            {
                return DashboardLogLevel.Trace;
            }
            else if(data.ContainsAnyOfPattern(new string[] { "Debug", "DEBUG", "LOG_DEBUG" }, true))
            {
                return DashboardLogLevel.Debug;
            }
            else if(data.ContainsAnyOfPattern(new string[] { "Info", "INFO", "Information", "INFORMATION", "LOG_INFORMATION" }, true))
            {
                return DashboardLogLevel.Info;
            }
            else if(data.ContainsAnyOfPattern(new string[] { "Error", "ERROR", "LOG_ERROR" }, true))
            {
                return DashboardLogLevel.Error;
            }
            else if(data.ContainsAnyOfPattern(new string[] { "Fatal", "Critical", "FATAL", "CRITICAL" }, true))
            {
                return DashboardLogLevel.Fatal;
            }
            else
            {
                return DashboardLogLevel.Trace;
            }
        }

        private void LogParseXml(string line, string importFilePath)
        {
            if(string.IsNullOrEmpty(line))
            {
                return;
            }

            Application.Current.Dispatcher.Invoke(() =>
            {
                var lm = ParseLog4JXmlLogEvent(line);
                if(lm != null)
                {
                    BuildTreeByMessage(lm, false);
                    _importData[importFilePath].Add(lm);
                }
            });
        }

        /// <summary>
        /// parse EPTS line
        /// </summary>
        /// <param name="line"></param>
        /// <param name="importFilePath"></param>
        /// <param name="logger"></param>
        /// <param name="sequenceNumber"></param>
        private void LogParseEPTS(string line, string importFilePath, string logger, ulong sequenceNumber)
        {
            if(string.IsNullOrEmpty(line))
            {
                return;
            }

            Application.Current.Dispatcher.Invoke(() =>
            {
                var lm = ParseLogEPTSLogEvent(line, logger, sequenceNumber);
                if(lm != null)
                {
                    BuildTreeByMessage(lm, false);
                    _importData[importFilePath].Add(lm);
                }
            });
        }

        /// <summary>
        /// Parse ErrorLogger format line
        /// </summary>
        /// <param name="line"></param>
        /// <param name="importFilePath"></param>
        /// <param name="logger"></param>
        /// <param name="sequenceNumber"></param>
        private void LogParseErrorLogger(string line, string importFilePath, string logger, ulong sequenceNumber)
        {
            if(string.IsNullOrEmpty(line))
            {
                return;
            }

            Application.Current.Dispatcher.Invoke(() =>
            {
                var lm = ParseLogErrorLoggerLogEvent(line, logger, sequenceNumber);
                if(lm != null)
                {
                    BuildTreeByMessage(lm, false);
                    _importData[importFilePath].Add(lm);
                }
            });
        }


        /// <summary>
        /// We can share parser context to improve performance
        /// </summary>
        static readonly XmlParserContext XmlContext = CreateContext();
        static XmlParserContext CreateContext()
        {
            NameTable nt = new NameTable();
            XmlNamespaceManager nsmanager = new XmlNamespaceManager(nt);
            nsmanager.AddNamespace("log4j", "http://jakarta.apache.org/log4j/");
            nsmanager.AddNamespace("nlog", "http://nlog-project.org");
            return new XmlParserContext(nt, nsmanager, "elem", XmlSpace.None, Encoding.UTF8);
        }

        /// <summary>
        /// Parse LOG4JXml from string
        /// </summary>
        public LogMessage ParseLog4JXmlLogEvent(string logEvent)
        {
            try
            {
                //string logMessage = logEvent.Replace(Environment.NewLine, "&#xA;");
                using(XmlTextReader reader = new XmlTextReader(logEvent, XmlNodeType.Element, XmlContext) { WhitespaceHandling = WhitespaceHandling.Significant })
                {
                    return ParseLog4JXmlLogEvent(reader);
                }
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// parsing EPTS log event message
        /// </summary>
        /// <param name="logEvent"></param>
        /// <param name="defaultLogger"></param>
        /// <returns></returns>
        public LogMessage ParseLogEPTSLogEvent(string logEvent, string defaultLogger, ulong sequenceNumber)
        {
            try
            {
                string pattern = @"^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2},\d{3})\s+(\w+)\s+\[(\d+)\] \((:\d+)\) - (.+)";
                Match match = Regex.Match(logEvent, pattern);
                if(match.Success)
                {
                    LogMessage message = new LogMessage();

                    string date = match.Groups[1].Value;
                    string time = match.Groups[2].Value;
                    string level = match.Groups[3].Value;
                    string id = match.Groups[4].Value;
                    string session = match.Groups[5].Value;
                    string msg = match.Groups[6].Value;

                    message.Time = DateTime.ParseExact($"{date} {time}", "yyyy-MM-dd HH:mm:ss,fff", null);
                    message.Level = FromLogLevelStringToLogLevel(level);
                    message.Thread = int.Parse(id);
                    message.Message = msg;
                    message.ThreadName = "EPTS";
                    message.Address = "EPTS";
                    message.Logger = defaultLogger;
                    message.SequenceNumber = sequenceNumber;
                    return message;
                }
                else
                {
                    return null;
                }
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Parses Log Event
        /// </summary>
        /// <param name="logEvent"></param>
        /// <param name="defaultLogger"></param>
        /// <param name="sequenceNumber"></param>
        /// <returns></returns>
        public LogMessage ParseLogErrorLoggerLogEvent(string logEvent, string defaultLogger, ulong sequenceNumber)
        {
            try
            {
                string pattern = @"^(\d{10}\.\d{3})\s?,\s?(\w{3}\s\w{3}\s\d{2}\s\d{2}:\d{2}:\d{2}\s\d{4}\s\d{3}ms)\s?,\s?(LOG_\w+)\s?,\s?(.+)";
                Match match = Regex.Match(logEvent, pattern);
                if(match.Success)
                {
                    LogMessage message = new LogMessage();

                    string ticks = match.Groups[1].Value;
                    string datetime = match.Groups[2].Value;

                    string level = match.Groups[3].Value;

                    string msg = match.Groups[4].Value;

                    string msgPattern = @"^\[(.+)\] (.+)";
                    Match matchMsg = Regex.Match(msg, msgPattern);

                    string logger = "ErrorLogger";
                    if(matchMsg.Success)
                    {
                        logger = matchMsg.Groups[1].Value.Replace("::", ".");
                        msg = matchMsg.Groups[2].Value;
                    }

                    message.Time = DateTime.ParseExact(datetime, "ddd MMM dd HH:mm:ss yyyy fff\\m\\s", null);
                    message.Level = FromLogLevelStringToLogLevel(level);
                    message.Thread = 0;
                    message.Message = msg;
                    message.ThreadName = defaultLogger;
                    message.Address = defaultLogger;
                    message.Logger = logger;
                    message.SequenceNumber = sequenceNumber;

                    return message;
                }
                else
                {
                    return null;
                }
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Here we expect the log event to use the log4j schema.
        /// Sample:
        ///     <log4j:event logger="Statyk7.Another.Name.DummyManager" timestamp="1184286222308" level="ERROR" thread="1">
        ///         <log4j:message>This is an Message</log4j:message>
        ///         <log4j:properties>
        ///             <log4j:data name="log4jmachinename" value="remserver" />
        ///             <log4j:data name="log4net:HostName" value="remserver" />
        ///             <log4j:data name="log4net:UserName" value="REMSERVER\Statyk7" />
        ///             <log4j:data name="log4japp" value="Test.exe" />
        ///         </log4j:properties>
        ///     </log4j:event>
        /// </summary>
        /// 
        /// Implementation inspired from: http://geekswithblogs.net/kobush/archive/2006/04/20/75717.aspx
        /// 
        private static LogMessage ParseLog4JXmlLogEvent(XmlReader reader)
        {
            LogMessage logMsg = new LogMessage();

            reader.Read();
            if((reader.MoveToContent() != XmlNodeType.Element) || (reader.Name != "log4j:event"))
            {
                throw new Exception("The Log Event is not a valid log4j Xml block.");
            }

            logMsg.Logger = reader.GetAttribute("logger");
            logMsg.Level = (DashboardLogLevel)Enum.Parse(typeof(DashboardLogLevel), reader.GetAttribute("level").ToLower().FirstCharToUpper());
            logMsg.ThreadName = reader.GetAttribute("thread");
            if(int.TryParse(logMsg.ThreadName, out int threadId))
            {
                logMsg.Thread = threadId;
            }

            if(long.TryParse(reader.GetAttribute("timestamp"), out long timeStamp))
            {
                logMsg.Time = _s1970.AddMilliseconds(timeStamp).ToLocalTime();
            }

            int eventDepth = reader.Depth;
            reader.Read();
            while(reader.Depth > eventDepth)
            {
                if(reader.MoveToContent() == XmlNodeType.Element)
                {
                    switch(reader.Name)
                    {
                        case "log4j:message":
                            logMsg.Message = reader.ReadString();
                            break;

                        case "log4j:throwable":
                            logMsg.Message += Environment.NewLine + reader.ReadString();
                            break;

                        case "log4j:locationInfo":

                            logMsg.ThreadName = reader.GetAttribute("class");
                            //logMsg.Logger = reader.GetAttribute("method");
                            //logMsg.SourceFileName = reader.GetAttribute("file");
                            //uint sourceFileLine;
                            //if (uint.TryParse(reader.GetAttribute("line"), out sourceFileLine))
                            //	logMsg.SourceFileLineNr = sourceFileLine;
                            break;
                        case "nlog:eventSequenceNumber":
                            ulong sequenceNumber;
                            if(ulong.TryParse(reader.ReadString(), out sequenceNumber))
                            {
                                logMsg.SequenceNumber = sequenceNumber;
                            }

                            break;
                        case "nlog:locationInfo":
                            break;

                        case "log4j:properties":
                            reader.Read();
                            while(reader.MoveToContent() == XmlNodeType.Element
                                   && reader.Name == "log4j:data")
                            {
                                string name = reader.GetAttribute("name");
                                string value = reader.GetAttribute("value");
                                if(name != null && name.ToLower().Equals("exceptions"))
                                {
                                    logMsg.ExceptionString = value;
                                }
                                else if(name.ToLower().Equals("log4jmachinename"))
                                {
                                    logMsg.Address = value;
                                }
                                else
                                {
                                    // TODO add properties
                                    //logMsg.Properties[name] = value;
                                }

                                reader.Read();
                            }
                            break;
                    }
                }
                reader.Read();
            }

            return logMsg;
        }

        private string CheckNullableIntExists(int? value)
        {
            return value.HasValue ? $"{value.Value};" : string.Empty;
        }

        private LogMessage GetLastSelecterOrNearbyMessage()
        {
            return Logs.Contains(LastLogMessage) ? LastLogMessage : _nearbyLastLogMessages.FirstOrDefault(nearbyLastLogMessage => Logs.Contains(nearbyLastLogMessage));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="currentIndex"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        private LogMessage FindNearMessageByLogLevel(int currentIndex, DashboardLogLevel level)
        {
            if(Logs.Count <= currentIndex + 1 || currentIndex - 1 <= 0)
            {
                return null;
            }

            for(int i = currentIndex + 1; i < Logs.Count; i++)
            {
                if(Logs[i].Level == level)
                {
                    return Logs[i];
                }
            }

            for(int i = currentIndex - 1; i >= 0; i--)
            {
                if(Logs[i].Level == level)
                {
                    return Logs[i];
                }
            }

            return null;
        }

        /// <summary>
        /// Update logs from file
        /// </summary>
        /// <param name="watcher"></param>
        private void UpdateLogsFromFile(FileWatcher watcher)
        {
            try
            {
                string fileName = new FileInfo(watcher.FilePath).Name;

                List<Task> tasks = BuildLogParsingTask(new List<ImportLogFile> { watcher.ImportLogFile }, new List<FileWatcher> { watcher }, watcher.Template.LogFileType, watcher.Position);
                tasks.ForEach(t => t.Start());

                Task.WhenAll(tasks).ContinueWith(x =>
                {
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        foreach(var logMessage in _importData[watcher.FilePath])
                        {
                            if(AddLogBySearchFilters(logMessage))
                            {
                                continue;
                            }

                            BuildTreeByMessage(logMessage);
                        }
                    });
                });
            }
            catch(Exception ex)
            {
                _logger.Warn(ex, $"An error occurred while UpdateLogsFromFile file - {watcher.FilePath}, position - {watcher.Position}");
            }
            finally
            {
                _importData[watcher.FilePath].Clear();
                CleanIsEnabled = _allLogs.Any();
                GC.Collect();
                GC.WaitForFullGCComplete();
            }
        }

        /// <summary>
        /// checks if the file exists in the import logs
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private bool CheckFileExistsInImportLogs(string filePath)
        {
            if(_importData.ContainsKey(filePath))
            {
                Messenger.Default.Send($"File {filePath} was already added", "DisplayCustomMessage");
                return true;
            }
            return false;
        }

        #endregion

    }
}
