namespace Archive_Tool
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Threading.Tasks;
    using System.Timers;
    using System.Windows.Forms;

    public partial class MainForm : Form
    {
        private static ArchiveFile[] archData;
        private List<FileSystemWatcher> fsWatchers;
        private ListViewColumnSorter lvColumnSorter = new ListViewColumnSorter();
        private ListViewItem[] mainList;
        private System.Timers.Timer inputTimer;
        private bool[] userFlags = new bool[4];
        private int checkedCount;

        public MainForm()
        {
            InitializeComponent();
            lvFiles.ListViewItemSorter = lvColumnSorter;
            cbShowFilter.SelectedIndex = 0;
        }

        public void AddToLog(string text, Color? color = null)
        {
            rtbLog.SelectionStart = rtbLog.TextLength;
            rtbLog.SelectionLength = 0;
            rtbLog.SelectionColor = color ?? rtbLog.ForeColor;
            rtbLog.AppendText(Environment.NewLine + text);
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            if (openFileDialogBIN.ShowDialog() == DialogResult.OK)
            {
                OpenFile(openFileDialogBIN.FileName);
            }
        }

        private void OpenFile(string fileName)
        {
            if (Path.GetExtension(fileName).ToUpper() == ".BIN")
            {
                CleanUI();
                archData = Program.ParseArchive(fileName);
                if (archData != null)
                {
                    mainList = new ListViewItem[archData.Length];
                    for (int i = 0; i < archData.Length; i++)
                    {
                        mainList[i] = new ListViewItem(new string[]
                        {
                            i.ToString(), archData[i].Name, archData[i].Flags.Substring(0, 2), archData[i].Size.ToString("N0", CultureInfo.InvariantCulture)
                        });

                        if (archData[i].SwapName != null)
                        {
                            mainList[i].BackColor = Color.PaleGreen;
                        }
                        else if (archData[i].Unused)
                        {
                            mainList[i].BackColor = Color.Lavender;
                        }
                        else if (archData[i].Unknown)
                        {
                            mainList[i].BackColor = Color.Pink;
                        }
                    }

                    ShowFiltered();
                    btnExtractSelected.Enabled = true;
                    btnChangeDir.Enabled = true;
                    tbExtractPath.Text = Path.GetDirectoryName(fileName) + @"\" + Path.GetFileNameWithoutExtension(fileName) + @"\";
                }
            }
            else
            {
                SwapSelected(fileName);
            }
        }

        private void CleanUI()
        {
            btnExtractSelected.Enabled = false;
            btnChangeDir.Enabled = false;
            tbExtractPath.Text = string.Empty;
            lvFiles.Items.Clear();
            mainList = null;
            gbSelect.Text = "Selected: 0/0";
            checkedCount = 0;
        }

        private void MainForm_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
        }

        private void MainForm_DragDrop(object sender, DragEventArgs e)
        {
            string[] fileNames = (string[])e.Data.GetData(DataFormats.FileDrop);
            Task.Factory.StartNew(() =>
            {
                this.Invoke((Action)delegate
                {
                    OpenFile(fileNames[0]);
                });
            });
        }

        private void btnUpdateDB_Click(object sender, EventArgs e)
        {
            if (openFileDialogEXE.ShowDialog() == DialogResult.OK)
            {
                CleanUI();
                Program.UpdateDB(openFileDialogEXE.FileName);
            }
        }

        private void btnClearLog_Click(object sender, EventArgs e)
        {
            rtbLog.Clear();
        }

        private void lvFiles_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            try
            {
                int targetColumn = e.Column;
                if (targetColumn == lvColumnSorter.SortColumn)
                {
                    if (lvColumnSorter.Order == SortOrder.Ascending)
                    {
                        lvColumnSorter.Order = SortOrder.Descending;
                    }
                    else
                    {
                        lvColumnSorter.Order = SortOrder.Ascending;
                    }
                }
                else
                {
                    lvColumnSorter.SortColumn = targetColumn;
                    lvColumnSorter.Order = SortOrder.Ascending;
                }

                this.lvFiles.Sort();
                ListViewExtensions.SetSortIcon(lvFiles, targetColumn, lvColumnSorter.Order);
            }
            catch (Exception ex)
            {
                AddToLog("Error: " + ex.Message, Color.Red);
            }
        }

        private void lvFiles_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                var listView = (ListView)sender;
                var lvItem = listView.GetItemAt(e.X, e.Y);
                if (lvItem != null)
                {
                    if (lvItem.BackColor == Color.PaleGreen)
                    {
                        cmReset.Visible = true;
                    }
                    else
                    {
                        cmReset.Visible = false;
                    }

                    contextMenuStrip.Show(listView, e.X + 4, e.Y + 4);
                }
            }
        }

        private void cmExtract_Click(object sender, EventArgs e)
        {
            ExtractSelected(null);
        }

        private void ExtractSelected(string savePath)
        {
            if ((lvFiles.CheckedIndices.Count > 0) && (savePath != null))
            {
                AddToLog("Info: Extracting " + lvFiles.CheckedIndices.Count + " files...");
                Directory.CreateDirectory(savePath);
                var selectedItems = new ListViewItem[lvFiles.CheckedItems.Count];
                lvFiles.CheckedItems.CopyTo(selectedItems, 0);
                var taskList = new List<Task<Tuple<string, Color>>>();
                foreach (var selectedItem in selectedItems)
                {
                    var archFile = archData[int.Parse(selectedItem.Text)];
                    var newTask = Task<Tuple<string, Color>>.Factory.StartNew(() => Program.ExtractFile(archFile, savePath + archFile.Name, userFlags));
                    taskList.Add(newTask);
                }

                pbExtract.Maximum = taskList.Count;
                pbExtract.Visible = true;
                bool errorsFound = false;
                foreach (var taskResult in taskList)
                {
                    if (taskResult.Status != TaskStatus.RanToCompletion)
                    {
                        taskResult.Wait();
                    }

                    pbExtract.Increment(1);
                    if (taskResult.Result != null)
                    {
                        AddToLog(taskResult.Result.Item1, taskResult.Result.Item2);
                        if (!errorsFound)
                        {
                            errorsFound = true;
                        }
                    }
                }

                pbExtract.Visible = false;
                pbExtract.Value = 0;
                if (!errorsFound)
                {
                    AddToLog("Info: All files extracted successfully.", Color.Green);
                }
                else
                {
                    AddToLog("Info: Files extracted.");
                }
            }
            else if (lvFiles.SelectedIndices.Count > 0)
            {
                Tuple<string, Color> extractResult = null;
                var archFile = archData[int.Parse(lvFiles.SelectedItems[0].Text)];
                if (savePath == null)
                {
                    saveFileDialog.FileName = archFile.Name;
                    if (saveFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        AddToLog("Info: Extracting file...");
                        extractResult = Program.ExtractFile(archFile, saveFileDialog.FileName, userFlags);
                    }
                    else
                    {
                        return;
                    }
                }
                else
                {
                    AddToLog("Info: Extracting file...");
                    Directory.CreateDirectory(savePath);
                    extractResult = Program.ExtractFile(archFile, savePath + archFile.Name, userFlags);
                }

                if (extractResult == null)
                {
                    AddToLog("Info: File \"" + archFile.Name + "\" extracted.", Color.Green);
                }
                else
                {
                    AddToLog(extractResult.Item1, extractResult.Item2);
                }
            }
            else
            {
                AddToLog("Error: Please select files to extract!", Color.Red);
            }
        }

        private void btnExtractSelected_Click(object sender, EventArgs e)
        {
            string savePath = tbExtractPath.Text;
            if (savePath != string.Empty)
            {
                ExtractSelected(savePath);
            }
            else
            {
                AddToLog("Error: Please select extraction path!", Color.Red);
            }
        }

        private void btnChangeDir_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                tbExtractPath.Text = folderBrowserDialog.SelectedPath + @"\";
            }
        }

        private void btnCopyLog_Click(object sender, EventArgs e)
        {
            rtbLog.SelectAll();
            rtbLog.Copy();
            rtbLog.Select(rtbLog.TextLength, 0);
        }

        private void btnSelectAll_Click(object sender, EventArgs e)
        {
            SelectItems(true);
        }

        private void btnSelectNone_Click(object sender, EventArgs e)
        {
            SelectItems(false);
        }

        private void SelectItems(bool checkItems)
        {
            if (lvFiles.Items.Count > 0)
            {
                lvFiles.ItemCheck -= lvFiles_ItemCheck;
                lvFiles.BeginUpdate();
                if (checkItems)
                {
                    foreach (ListViewItem lvItem in lvFiles.Items)
                    {
                        lvItem.Checked = true;
                    }
                }
                else
                {
                    foreach (ListViewItem lvItem in lvFiles.Items)
                    {
                        lvItem.Checked = false;
                    }
                }

                lvFiles.EndUpdate();
                checkedCount = lvFiles.CheckedIndices.Count;
                gbSelect.Text = "Selected: " + checkedCount.ToString() + "/" + lvFiles.Items.Count.ToString();
                lvFiles.ItemCheck += lvFiles_ItemCheck;
            }
        }

        private void cbForceDecryption_CheckedChanged(object sender, EventArgs e)
        {
            if (cbForceDecryption.Checked)
            {
                cbDisableDecryption.Checked = false;
                cbDisableDecryption.Enabled = false;
                userFlags[0] = true;
            }
            else
            {
                cbDisableDecryption.Enabled = true;
                userFlags[0] = false;
            }
        }

        private void cbDisableDecryption_CheckedChanged(object sender, EventArgs e)
        {
            if (cbDisableDecryption.Checked)
            {
                cbForceDecryption.Checked = false;
                cbForceDecryption.Enabled = false;
                userFlags[1] = true;
            }
            else
            {
                cbForceDecryption.Enabled = true;
                userFlags[1] = false;
            }
        }

        private void cbForceDecompression_CheckedChanged(object sender, EventArgs e)
        {
            if (cbForceDecompression.Checked)
            {
                cbDisableDecompression.Checked = false;
                cbDisableDecompression.Enabled = false;
                userFlags[2] = true;
            }
            else
            {
                cbDisableDecompression.Enabled = true;
                userFlags[2] = false;
            }
        }

        private void cbDisableDecompression_CheckedChanged(object sender, EventArgs e)
        {
            if (cbDisableDecompression.Checked)
            {
                cbForceDecompression.Checked = false;
                cbForceDecompression.Enabled = false;
                userFlags[3] = true;
            }
            else
            {
                cbForceDecompression.Enabled = true;
                userFlags[3] = false;
            }
        }

        private void tbFilter_TextChanged(object sender, EventArgs e)
        {
            if (inputTimer == null)
            {
                inputTimer = new System.Timers.Timer();
                inputTimer.SynchronizingObject = this;
                inputTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
                inputTimer.AutoReset = false;
                inputTimer.Interval = 500;
                inputTimer.Enabled = true;
            }
            else
            {
                inputTimer.Enabled = false;
                inputTimer.Enabled = true;
            }
        }

        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            ShowFiltered();
            inputTimer.Dispose();
            inputTimer = null;
        }

        private void ShowFiltered()
        {
            if (mainList != null)
            {
                lvFiles.ItemCheck -= lvFiles_ItemCheck;
                lvFiles.SelectedIndexChanged -= lvFiles_SelectedIndexChanged;
                lvFiles.BeginUpdate();
                lvFiles.Items.Clear();
                string filterPattern = tbFilter.Text.ToUpperInvariant();
                var filteredList = new List<ListViewItem>();
                switch (cbShowFilter.SelectedIndex)
                {
                    case 1:
                        foreach (var lvItem in mainList)
                        {
                            if (lvItem.Checked)
                            {
                                filteredList.Add(lvItem);
                            }
                        }

                        break;

                    case 2:
                        for (int i = 0; i < archData.Length; i++)
                        {
                            if (archData[i].SwapName != null)
                            {
                                filteredList.Add(mainList[i]);
                            }
                        }

                        break;

                    case 3:
                        for (int i = 0; i < archData.Length; i++)
                        {
                            if (archData[i].Unknown)
                            {
                                filteredList.Add(mainList[i]);
                            }
                        }

                        break;

                    case 4:
                        for (int i = 0; i < archData.Length; i++)
                        {
                            if (archData[i].Unused)
                            {
                                filteredList.Add(mainList[i]);
                            }
                        }

                        break;
                }

                if (filterPattern == string.Empty)
                {
                    if (cbShowFilter.SelectedIndex == 0)
                    {
                        lvFiles.Items.AddRange(mainList);
                    }
                    else
                    {
                        lvFiles.Items.AddRange(filteredList.ToArray());
                    }
                }
                else
                {
                    if (cbShowFilter.SelectedIndex == 0)
                    {
                        foreach (var lvItem in mainList)
                        {
                            if (lvItem.SubItems[1].Text.Contains(filterPattern))
                            {
                                filteredList.Add(lvItem);
                            }
                        }

                        lvFiles.Items.AddRange(filteredList.ToArray());
                    }
                    else
                    {
                        var filteredList2 = new List<ListViewItem>();
                        foreach (var lvItem in filteredList)
                        {
                            if (lvItem.SubItems[1].Text.Contains(filterPattern))
                            {
                                filteredList2.Add(lvItem);
                            }
                        }

                        lvFiles.Items.AddRange(filteredList2.ToArray());
                    }
                }

                checkedCount = lvFiles.CheckedIndices.Count;
                gbSelect.Text = "Selected: " + checkedCount.ToString() + "/" + lvFiles.Items.Count.ToString();
                lvFiles.EndUpdate();
                lvFiles.ItemCheck += lvFiles_ItemCheck;
                lvFiles.SelectedIndexChanged += lvFiles_SelectedIndexChanged;
            }
        }

        private void lvFiles_ItemDrag(object sender, ItemDragEventArgs e)
        {
            try
            {
                string dndStubPath = Path.GetTempPath() + "DragNDropFile";
                using (File.Create(dndStubPath))
                {
                }

                fsWatchers = new List<FileSystemWatcher>();
                foreach (string driveName in Directory.GetLogicalDrives())
                {
                    if (Directory.Exists(driveName))
                    {
                        var fsWatcher = new FileSystemWatcher();
                        fsWatcher.Filter = "DragNDropFile";
                        fsWatcher.NotifyFilter = NotifyFilters.FileName;
                        fsWatcher.Created += new FileSystemEventHandler(FWCreated);
                        fsWatcher.IncludeSubdirectories = true;
                        fsWatcher.Path = driveName;
                        fsWatcher.SynchronizingObject = this;
                        fsWatcher.EnableRaisingEvents = true;
                        fsWatchers.Add(fsWatcher);
                    }
                }

                var fileDragData = new DataObject(DataFormats.FileDrop, new string[1] { dndStubPath });
                DoDragDrop(fileDragData, DragDropEffects.Move);
            }
            catch (Exception ex)
            {
                AddToLog("Error: " + ex.Message, Color.Red);
            }
            finally
            {
                if (fsWatchers != null)
                {
                    foreach (var fsWatcher in fsWatchers)
                    {
                        fsWatcher.Dispose();
                    }
                }
            }
        }

        private void FWCreated(object sender, FileSystemEventArgs e)
        {
            string dropedFilePath = e.FullPath;
            try
            {
                File.Delete(dropedFilePath);
                SendKeys.Send("{F5}");
            }
            catch (Exception ex)
            {
                AddToLog("Error: " + ex.Message, Color.Red);
            }

            ExtractSelected(Path.GetDirectoryName(dropedFilePath) + @"\");
            SendKeys.Send("{F5}");
        }

        private void cbResizeLog_CheckedChanged(object sender, EventArgs e)
        {
            if (cbResizeLog.Checked)
            {
                gbLog.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
                gbFileList.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
            }
            else
            {
                gbLog.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
                gbFileList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
            }
        }

        private void cmSwap_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                SwapSelected(openFileDialog.FileName);
            }
        }

        private void SwapSelected(string fileName)
        {
            if (lvFiles.SelectedIndices.Count > 0)
            {
                var dialogResult = MessageBox.Show("Do you want to swap \"" + lvFiles.SelectedItems[0].SubItems[1].Text + "\" with \"" + Path.GetFileName(fileName) + "\"?", "Info",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Information);
                if (dialogResult == DialogResult.Yes)
                {
                    var selectedItem = lvFiles.SelectedItems[0];
                    var archFile = archData[int.Parse(selectedItem.Text)];
                    if (Program.SwapFile(archFile, tbSwapDir.Text, fileName))
                    {
                        lvFiles.BeginUpdate();
                        selectedItem.SubItems[2].Text = archFile.Flags.Substring(0, 2);
                        selectedItem.SubItems[3].Text = archFile.Size.ToString("N0", CultureInfo.InvariantCulture);
                        selectedItem.BackColor = Color.PaleGreen;
                        lvFiles.EndUpdate();
                    }
                }
            }
            else
            {
                AddToLog("Error: Please select file to swap!", Color.Red);
            }
        }

        private void lvFiles_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            if (e.NewValue == CheckState.Checked)
            {
                checkedCount++;
            }
            else
            {
                checkedCount--;
            }

            gbSelect.Text = "Selected: " + checkedCount.ToString() + "/" + lvFiles.Items.Count.ToString();
        }

        private void cbShowFilter_SelectionChangeCommitted(object sender, EventArgs e)
        {
            ShowFiltered();
        }

        private void lvFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvFiles.SelectedItems.Count > 0)
            {
                string swapName = archData[int.Parse(lvFiles.SelectedItems[0].Text)].SwapName;
                if (swapName != null)
                {
                    AddToLog("Info: File is swapped with \"" + swapName + "\"");
                }
            }
        }

        private void cmReset_Click(object sender, EventArgs e)
        {
            var dialogResult = MessageBox.Show("Do you want to cancel the swap for \"" + lvFiles.SelectedItems[0].SubItems[1].Text + "\"?", "Info", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
            if (dialogResult == DialogResult.Yes)
            {
                var selectedItem = lvFiles.SelectedItems[0];
                var archFile = archData[int.Parse(selectedItem.Text)];
                if (Program.ResetSwap(archFile))
                {
                    lvFiles.BeginUpdate();
                    selectedItem.SubItems[2].Text = archFile.Flags.Substring(0, 2);
                    selectedItem.SubItems[3].Text = archFile.Size.ToString("N0", CultureInfo.InvariantCulture);
                    selectedItem.BackColor = SystemColors.Window;
                    lvFiles.EndUpdate();
                }
            }
        }
    }

    internal static class ListViewExtensions
    {
        private const int HDIFormat = 0x0004;
        private const int HDFLeft = 0x0000;
        private const int HDFBitmapOnRight = 0x1000;
        private const int HDFSortUp = 0x0400;
        private const int HDFSortDown = 0x0200;
        private const int LVMGetHeader = 4127;
        private const int HDMGetItem = 4619;
        private const int HDMSetItem = 4620;

        public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order)
        {
            IntPtr columnHeader = SendMessage(listView.Handle, LVMGetHeader, IntPtr.Zero, IntPtr.Zero);

            for (int columnNumber = 0; columnNumber < listView.Columns.Count; columnNumber++)
            {
                IntPtr columnPtr = new IntPtr(columnNumber);
                LVCOLUMN lvColumn = new LVCOLUMN();
                lvColumn.Mask = HDIFormat;

                SendMessageLVCOLUMN(columnHeader, HDMGetItem, columnPtr, ref lvColumn);

                if (!(order == SortOrder.None) && columnNumber == columnIndex)
                {
                    switch (order)
                    {
                        case SortOrder.Ascending:
                            lvColumn.Fmt &= ~HDFSortDown;
                            lvColumn.Fmt |= HDFSortUp;
                            break;

                        case SortOrder.Descending:
                            lvColumn.Fmt &= ~HDFSortUp;
                            lvColumn.Fmt |= HDFSortDown;
                            break;
                    }

                    lvColumn.Fmt |= HDFLeft | HDFBitmapOnRight;
                }
                else
                {
                    lvColumn.Fmt &= ~HDFSortDown & ~HDFSortUp & ~HDFBitmapOnRight;
                }

                SendMessageLVCOLUMN(columnHeader, HDMSetItem, columnPtr, ref lvColumn);
            }
        }

        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage")]
        private static extern IntPtr SendMessageLVCOLUMN(IntPtr hWnd, int msg, IntPtr wParam, ref LVCOLUMN lPLVCOLUMN);

        [StructLayout(LayoutKind.Sequential)]
        private struct LVCOLUMN
        {
            public int Mask;
            public int Cx;

            [MarshalAs(UnmanagedType.LPTStr)]
            public string PszText;

            public IntPtr Hbm;
            public int CchTextMax;
            public int Fmt;
            public int ISubItem;
            public int IImage;
            public int IOrder;
        }
    }

    internal class ListViewColumnSorter : IComparer
    {
        private int columnToSort;

        private SortOrder orderOfSort;

        private CaseInsensitiveComparer objectCompare;

        public ListViewColumnSorter()
        {
            orderOfSort = SortOrder.None;
            objectCompare = new CaseInsensitiveComparer();
        }

        public int SortColumn
        {
            get
            {
                return columnToSort;
            }

            set
            {
                columnToSort = value;
            }
        }

        public SortOrder Order
        {
            get
            {
                return orderOfSort;
            }

            set
            {
                orderOfSort = value;
            }
        }

        public int Compare(object x, object y)
        {
            int compareResult;
            ListViewItem listviewX, listviewY;
            listviewX = (ListViewItem)x;
            listviewY = (ListViewItem)y;
            if ((SortColumn == 1) || (SortColumn == 2))
            {
                compareResult = objectCompare.Compare(listviewX.SubItems[columnToSort].Text, listviewY.SubItems[columnToSort].Text);
            }
            else
            {
                compareResult = objectCompare.Compare(int.Parse(listviewX.SubItems[columnToSort].Text, NumberStyles.Number, CultureInfo.InvariantCulture),
                    int.Parse(listviewY.SubItems[columnToSort].Text, NumberStyles.Number, CultureInfo.InvariantCulture));
            }

            if (orderOfSort == SortOrder.Ascending)
            {
                return compareResult;
            }
            else if (orderOfSort == SortOrder.Descending)
            {
                return -compareResult;
            }
            else
            {
                return 0;
            }
        }
    }
}