using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using VCommon.Logging.Viewer.Utility;

namespace VCommon.Logging.Viewer
{
    //TODO: 清除时移除文件监视,仅监视最后拖入对象(目录或文件)
    //TODO: 放出JSON可视化选项,作为一行
    public partial class MainForm : Form, IMessageFilter
    {
        private bool _mouseOnLvSummary;
        private FileSizeWatcher _fileWatcherSize;
        private readonly ColumnSorter _lstColumnSorter = new ColumnSorter();
        private readonly List<DetailForm> _detailforms = new List<DetailForm>();

        public MainForm()
        {
            InitializeComponent();

            var eIcon = IconExtractor.GetMainIcon();
            if (null != eIcon) Icon = eIcon;

            Application.AddMessageFilter(this);
            WebBrowserUtility.LoadWebBrowserResource(WbJson);
            LvSummary.ListViewItemSorter = _lstColumnSorter;
        }

#pragma warning disable 4014

        public MainForm(string openDirectoryName) : this() => OpenFileTask(openDirectoryName, false);

#pragma warning restore 4014

        private async Task OpenFileTask(string fileOrdirectoryName, bool openByClick = true)
        {
            if (File.Exists(fileOrdirectoryName))
            {
                var fileInfos = new[] { new FileInfo(fileOrdirectoryName) };
                EnableWatcherChanged(fileInfos);
                await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(fileOrdirectoryName); });
            }
            else
            {
                if (!openByClick)
                {
                    await ProcessOpenDirectoryTask(fileOrdirectoryName);
                }
                else
                {
                    FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
                    if (!string.IsNullOrEmpty(fileOrdirectoryName) && Directory.Exists(fileOrdirectoryName))
                        folderBrowserDialog.SelectedPath = fileOrdirectoryName;
                    if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
                    {
                        fileOrdirectoryName = folderBrowserDialog.SelectedPath.Trim();
                        await ProcessOpenDirectoryTask(fileOrdirectoryName);
                    }
                }
            }
        }

        private async Task ProcessOpenDirectoryTask(string directoryName)
        {
            List<FileInfo> flieInfos = new List<FileInfo>();
            OperatingFileAssistant.GetAllFilesByDirectory(directoryName, flieInfos, "*.log");
            var fileInfos = flieInfos.ToArray();
            EnableWatcherChanged(fileInfos);
            await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(directoryName); });
        }

        private void btnSelectFile_Click(object sender, EventArgs e) { }

        /// <summary>
        /// 分析日志
        /// </summary>
        /// <param name="fileInfos">文件信息</param>
        /// <param name="seekBeginPosition">读取起始位置</param>
        /// <param name="clearListView">是否清除ListView</param>
        /// <param name="watchFile">监控文件委托</param>
        /// <param name="removeContent">是否是删除内容</param>
        /// <exception cref="ArgumentException">文件信息不能为空</exception>
        /// <returns></returns>
        private async Task AnalysisLogFile(FileInfo[] fileInfos, long seekBeginPosition = 0, bool clearListView = true, Action watchFile = null, bool removeContent = false)
        {
            await Task.Run(async () =>
            {
                if (fileInfos == null || fileInfos.Length == 0) throw new ArgumentException(nameof(fileInfos));
                if (clearListView)
                {
                    LvSummary.Invoke(new Action(() =>
                    {
                        LvSummary.Items.Clear();
                    }));
                }
                var errorList = new List<string>();
                Dictionary<FileInfo, Task<LogEntity[]>> readFileResultTasks = new Dictionary<FileInfo, Task<LogEntity[]>>();
                foreach (var file in fileInfos)
                {
                    var line = 0;
                    try
                    {
                        var tasksReadTextAsync = OperatingFileAssistant.ReadTextAsync(file, str =>
                        {
                            List<LogEntity> logEntities = new List<LogEntity>();
                            string[] logContent = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                            var logEnumerator = logContent.GetEnumerator();
                            while (logEnumerator.MoveNext())
                            {
                                line++;
                                // ReSharper disable once PossibleNullReferenceException
                                var currentContent = logEnumerator.Current.ToString();
                                var matchCollection = Regex.Matches(currentContent, @"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (.+? )(.+)$");
                                if (matchCollection.Count > 0)
                                {
                                    LogEntity logEntity = new LogEntity
                                    {
                                        WriteTime = $"{matchCollection[0].Groups[1].Value}",
                                        Level = matchCollection[0].Groups[2].Value,
                                        ThreadName = matchCollection[0].Groups[3].Value,
                                        FileName = file.Name,
                                        LineNumber = line
                                    };
                                    if (logEnumerator.MoveNext() == false) break;
                                    line++;
                                    logEntity.Summary = logEnumerator.Current.ToString();
                                    if (logEnumerator.MoveNext() == false) break;
                                    line++;
                                    logEntity.Detail = logEnumerator.Current.ToString();

                                    logEntities.Add(logEntity);
                                }
                            }
                            return logEntities.ToArray();
                        }, seekBeginPosition);

                        readFileResultTasks.Add(file, tasksReadTextAsync);
                    }
                    catch (Exception ex)
                    {
                        errorList.Add(string.Format("于文件{1}行{2}解析错误:{0}", ex, file.Name, line));
                    }
                }

                readFileResultTasks = readFileResultTasks.OrderByDescending(c => c.Key.LastWriteTime)
                                                                         .ToDictionary(p => p.Key, o => o.Value);
                await Task.WhenAll(readFileResultTasks.Select(c => c.Value).ToArray()).ConfigureAwait(false);
                //如果是移除,只需比较ListView数据,删除即可
                if (removeContent)
                {
                    LvSummary.Invoke(new Action(() =>
                    {
                        var listViewItems = LvSummary.Items.Cast<ListViewItem>().Where(p => p.Text == fileInfos[0].Name).ToArray();
                        var logEntities = readFileResultTasks.SelectMany(c => c.Value.Result).Select(c => c.WriteTime);
                        var viewItems = listViewItems.Where(p => !logEntities.Contains(p.SubItems[1].Text)).ToArray();
                        foreach (var item in viewItems)
                        {
                            LvSummary.Items.Remove(item);
                        }
                    }));
                }
                else
                {
                    await Task.Factory.StartNew(() =>
                    {
                        List<ListViewItem> listViewItems = new List<ListViewItem>();
                        foreach (var task in readFileResultTasks)
                        {
                            foreach (var logEntity in task.Value.Result.OrderBy(c => c.WriteTime))
                            {
                                ListViewItem listViewItem = new ListViewItem
                                {
                                    Text = task.Key.Name,
                                    Tag = logEntity
                                };

                                listViewItem.SubItems.Add(logEntity.WriteTime);
                                listViewItem.SubItems.Add(logEntity.Level);
                                listViewItem.SubItems.Add(logEntity.ThreadName);
                                listViewItem.SubItems.Add(logEntity.Summary);
                                listViewItem.SubItems.Add(logEntity.Detail);
                                listViewItems.Add(listViewItem);
                            }
                        }
                        LvSummary.Invoke(new Action<List<ListViewItem>>(viewItems =>
                        {
                            LvSummary.Items.AddRange(viewItems.ToArray());
                            LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1));
                            LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1));
                        }), listViewItems);
                    }).ConfigureAwait(false);
                }
                watchFile?.Invoke();
                if (errorList.Any())
                {
                    Invoke(new Action(() =>
                    {
                        new ErrorForm(string.Join(Environment.NewLine, errorList.ToArray())).Show();
                    }));
                }
            });
        }

        private void btnFind_Click(object sender, EventArgs e)
        {
            var filter = BuildSearchFilter();
            if (filter == null) return;
            var beginIndex = LvSummary.SelectedItems.Count == 0 ? 0 : LvSummary.SelectedItems[0].Index + 1;
            for (int i = beginIndex; i < LvSummary.Items.Count; i++)
            {
                var item = LvSummary.Items[i];
                if (!filter(item.SubItems[4].Text)) continue;
                LvSummary.Items[i].Selected = true;
                LvSummary.Items[i].EnsureVisible();
                return;
            }
            toolTip.Show("没有数据", (IWin32Window)sender, Point.Empty, 5000);
        }

        private Func<string, bool> BuildSearchFilter()
        {
            var search = txtFind.Text;
            if (!string.IsNullOrEmpty(search))
            {
                if (!chkRegex.Checked)
                    return s => s.Contains(search);

                var reg = new Regex(search, RegexOptions.Compiled);

                return s => reg.IsMatch(s);
            }

            if (LvSummary.SelectedItems.Count == 0)
            {
                balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind);
                balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind);
                return null;
            }
            search = LvSummary.SelectedItems[0].SubItems[4].Text;
            return s => s.Contains(search);
        }

        private void btnCount_Click(object sender, EventArgs e)
        {
            var filter = BuildSearchFilter();
            if (filter == null) return;

            var ret = LvSummary.Items.Cast<ListViewItem>().Count(p => filter(p.SubItems[4].Text)).ToString();

            toolTip.Show(ret, (IWin32Window)sender, Point.Empty, 5000);
        }

        private void btnHightlight_Click(object sender, EventArgs e)
        {
            var filter = BuildSearchFilter();
            if (filter == null) return;
            var items = LvSummary.Items.Cast<ListViewItem>().Where(p => filter(p.SubItems[4].Text));
            foreach (var item in items)
            {
                item.Font = new Font(item.Font, FontStyle.Bold);
            }
        }

        private void btnClearHightlight_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem item in LvSummary.Items)
            {
                item.Font = new Font(item.Font, FontStyle.Regular);
            }
        }

        private void RemoveByMatchCondition_Click(object sender, EventArgs e)
        {
            if (LvSummary.SelectedItems.Count == 0)
                return;
            var remove = sender == btnRemove;
            Func<ListViewItem, bool> filter;

            if (chkRegex.Checked)
            {
                var reg = new Regex(txtFind.Text, RegexOptions.Compiled);
                filter = p => reg.IsMatch(p.SubItems[4].Text) == !remove;
            }
            else
            {
                var sel = LvSummary.SelectedItems[0].SubItems[4].Text;
                if (remove)
                {
                    filter = p => p.SubItems[4].Text != sel;
                }
                else
                {
                    filter = p => p.SubItems[4].Text == sel;
                }
            }

            var nks = LvSummary.Items.Cast<ListViewItem>().Where(filter).ToArray();

            LvSummary.Items.Clear();
            LvSummary.Items.AddRange(nks);
        }

        private void btnClear_Click(object sender, EventArgs e) => LvSummary.Items.Clear();

        private void btnCopyAll_Click(object sender, EventArgs e)
        {
            var logArray = LvSummary.Items.Cast<ListViewItem>().Select(p => p.Tag).ToArray();
            Clipboard.SetText(string.Join(Environment.NewLine, logArray));
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            var saveFileDialog = new SaveFileDialog
            {
                Filter = "文本文件|*.log",
                FileName = "Log.log"
            };
            if (saveFileDialog.ShowDialog() != DialogResult.OK) return;
            var logArray = LvSummary.Items.Cast<ListViewItem>().Select(p => p.Tag).ToArray();
            File.WriteAllText(saveFileDialog.FileName, string.Join(Environment.NewLine, logArray));
        }

        private void LvSummary_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            var myListView = (ListView)sender;

            // Determine if clicked column is already the column that is being sorted.
            if (e.Column == _lstColumnSorter.SortColumn)
            {
                // Reverse the current sort direction for this column.
                _lstColumnSorter.Order =
                    _lstColumnSorter.Order == SortOrder.Ascending
                        ? SortOrder.Descending
                        : SortOrder.Ascending;
            }
            else
            {
                // Set the column number that is to be sorted; default to ascending.
                _lstColumnSorter.SortColumn = e.Column;
                _lstColumnSorter.Order = SortOrder.Ascending;
            }

            // Perform the sort with these new sort options.
            myListView.Sort();
            myListView.SetSortIcon(_lstColumnSorter.SortColumn, _lstColumnSorter.Order);
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            LvSummary.Items.Clear();
        }

        private async void Form1_DragDrop(object sender, DragEventArgs e)
        {
            Enabled = false;
            SuspendLayout();
            try
            {
                var fileStrings = (string[])e.Data.GetData(DataFormats.FileDrop);
                if (fileStrings.Length == 0) return;
                var rootPath = string.Empty;
                var fileInfos = fileStrings.SelectMany(file =>
                {
                    List<FileInfo> flies = new List<FileInfo>();
                    if (string.IsNullOrEmpty(Path.GetExtension(file)))
                    {
                        if (rootPath == string.Empty) rootPath = file;
                        OperatingFileAssistant.GetAllFilesByDirectory(file, flies, "*.log");
                        return flies;
                    }
                    flies.Add(new FileInfo(file));
                    if (rootPath == string.Empty) rootPath = Path.GetDirectoryName(file);
                    return flies;
                }).ToArray();
                EnableWatcherChanged(fileInfos);
                await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(rootPath); });
            }
            finally
            {
                Invoke(new Action(delegate
                {
                    ResumeLayout();
                    Enabled = true;
                }));
            }
        }

        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
                ? DragDropEffects.Copy
                : DragDropEffects.None;
        }

        private void switchWindow_Click(object sender, EventArgs e)
        {
            if (splitContainer.Orientation == Orientation.Horizontal)
            {
                splitContainer.Orientation = Orientation.Vertical;
                splitContainer.SplitterDistance = Width / 2;
            }
            else
            {
                splitContainer.Orientation = Orientation.Horizontal;
                splitContainer.SplitterDistance = _panel1Height;
            }
        }

        private int _panel1Height;

        private void Form1_Load(object sender, EventArgs e)
        {
            _panel1Height = splitContainer.Panel1.Height;
        }

        private void LvSummary_Click(object sender, EventArgs e)
        {
            var listView = (ListView)sender;
            var selectedItem = listView.SelectedItems[0];
            WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text });
        }

        #region 文件监控

        private FileSystemWatcher _watcher;

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        private void CreateFileWatcher(string path)
        {
            _watcher = new FileSystemWatcher
            {
                Path = path,
                Filter = "*.log",
                IncludeSubdirectories = true,
                NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                               | NotifyFilters.FileName | NotifyFilters.DirectoryName
                               | NotifyFilters.CreationTime
            };
            _watcher.Deleted += _watcher_Deleted;
            _watcher.Created += _watcher_Created;
            _watcher.EnableRaisingEvents = true;
        }

        private void _watcher_Deleted(object sender, FileSystemEventArgs e)
        {
            _fileWatcherSize?.RemoveFileWatch(e.FullPath);
            RemoveListItemByFileName(e.Name);
        }

        private async void _watcher_Created(object sender, FileSystemEventArgs e)
        {
            _fileWatcherSize?.AddFileWatch(e.FullPath);
            await TaskExtend.Delay(500).ContinueWith(async task =>
             {
                 await AnalysisLogFile(new[] { new FileInfo(e.FullPath) }, 0, false);
             });
        }

        public void EnableWatcherChanged(FileInfo[] fileInfos)
        {
            if (fileInfos == null || fileInfos.Length == 0)
                throw new ArgumentException(nameof(fileInfos));

            _fileWatcherSize = new FileSizeWatcher(fileInfos);
            _fileWatcherSize.Changed += FileWatcherSize_Changed;
        }

        private async void FileWatcherSize_Changed(FileSizeWatcherEventArgs e)
        {
            var fileInfo = new FileInfo(e.FullPath);
            bool isDeleted = false;
            long seekBeginPosition = 0;
            if (e.ChangeSizeType == ChangeSizeType.Increase)
            {
                seekBeginPosition = e.OriginalLength;
            }
            else
            {
                isDeleted = true;
            }
            await AnalysisLogFile(new[] { fileInfo }, seekBeginPosition, false, null, isDeleted);
        }

        private void RemoveListItemByFileName(string fileName)
        {
            Invoke(new Action(() =>
            {
                var logArray = LvSummary.Items.Cast<ListViewItem>().Where(p => ((LogEntity)p.Tag).FileName == fileName).ToArray();
                foreach (var removeItem in logArray)
                {
                    LvSummary.Items.Remove(removeItem);
                }
            }));
        }

        #endregion 文件监控

        bool IMessageFilter.PreFilterMessage(ref Message m)
        {
            if (m.Msg == 0x20A) // WM_MOUSEWHEEL
            {
                if (_mouseOnLvSummary)
                {
                    NativeMethods.SendMessage(LvSummary.Handle, 0x020A, m.WParam, m.LParam);
                }
                else
                {
                    WebBrowserUtility.SendMouseWheel(m, WbJson);
                    foreach (var form in _detailforms)
                    {
                        form.PreFilterMessage(ref m);
                    }
                }
                return true;
            }
            return false;
        }

        private void LvSummary_MouseEnter(object sender, EventArgs e) => _mouseOnLvSummary = true;

        private void LvSummary_MouseLeave(object sender, EventArgs e) => _mouseOnLvSummary = false;

        private void LvSummary_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            var listView = (ListView)sender;
            var detailForm = new DetailForm((LogEntity)listView.SelectedItems[0].Tag, form => _detailforms.Remove(form));
            _detailforms.Add(detailForm);
            if (splitContainer.Orientation == Orientation.Vertical)
            {
                detailForm.Left += Width / 2;
                detailForm.Top += Height - WbJson.Height;
            }

            detailForm.Show(this);
        }

        private void LvSummary_SelectedIndexChanged(object sender, EventArgs e)
        {
            var listView = (ListView)sender;
            if (listView.SelectedItems.Count == 0) return;

            var selectedItem = listView.SelectedItems[0];
            WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text });
        }
    }
}