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

    public partial class MainForm : Form
    {
        private static readonly List<string> SuppFiles = new List<string> { ".TMC", ".--H", ".--P", ".SPR", ".SCN", ".TNF" };
        private static List<Texture> texBuffer;
        private static string sourcePath;
        private static int fileType;
        private static FormWindowState previousState;
        private List<FileSystemWatcher> fsWatchers;
        private ListViewColumnSorter lvColumnSorter = new ListViewColumnSorter();

        public MainForm()
        {
            InitializeComponent();
            lvTextures.ListViewItemSorter = lvColumnSorter;
        }

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

        private void OpenFile(string fileName)
        {
            string fileExt = Path.GetExtension(fileName).ToUpper();
            if (fileExt == ".DDS")
            {
                if (lvTextures.SelectedIndices.Count > 0)
                {
                    SwapFile(fileName);
                }
                else
                {
                    throw new Exception("Please select texture to swap!");
                }
            }
            else
            {
                fileType = SuppFiles.IndexOf(fileExt);
                if (fileType != -1)
                {
                    lvTextures.Items.Clear();
                    pbPrv.Image = null;
                    btnSave.Enabled = false;
                    btnExtractAll.Enabled = false;
                    btnAddMove.Enabled = false;
                    btnChangeDir.Enabled = false;
                    cmMoveTextures.Enabled = false;
                    btnAddMove.Enabled = false;
                    cmSeparator1.Visible = false;
                    cmAdd.Visible = false;
                    tbExtPath.Text = Path.GetDirectoryName(fileName) + @"\" + Path.GetFileNameWithoutExtension(fileName) + @"\";
                    Tuple<List<Texture>, int> openResult = Program.OpenContainer(fileType, fileName);
                    if ((openResult != null) && (openResult.Item1.Count > 0))
                    {
                        texBuffer = openResult.Item1;
                        ShowTextures();
                        btnExtractAll.Enabled = true;
                        btnChangeDir.Enabled = true;
                        if (fileType < 3)
                        {
                            sourcePath = fileName;
                            btnSave.Enabled = true;
                            if (fileType < 2)
                            {
                                btnAddMove.Enabled = true;
                                cmSeparator1.Visible = true;
                                cmAdd.Visible = true;
                            }

                            if (openResult.Item2 > 0)
                            {
                                cmMoveTextures.Enabled = true;
                            }
                        }
                    }
                }
            }
        }

        private void SwapFile(string fileName)
        {
            var selectedItem = lvTextures.SelectedItems[0];
            int texIndex = int.Parse(selectedItem.Text);
            if (Program.SwapTexture(texBuffer, texIndex, fileName))
            {
                lvTextures.BeginUpdate();
                selectedItem.SubItems[2].Text = texBuffer[texIndex].Type;
                selectedItem.SubItems[3].Text = texBuffer[texIndex].Height.ToString();
                selectedItem.SubItems[4].Text = texBuffer[texIndex].Width.ToString();
                selectedItem.SubItems[5].Text = texBuffer[texIndex].Size.ToString("N0", CultureInfo.InvariantCulture);
                selectedItem.BackColor = System.Drawing.Color.PaleGreen;
                lvTextures.EndUpdate();
                if (!chkDisPreview.Checked)
                {
                    ShowPreview(texBuffer[texIndex]);
                }
            }
        }

        private void ShowTextures()
        {
            var texList = new ListViewItem[texBuffer.Count];
            for (int i = 0; i < texBuffer.Count; i++)
            {
                texList[i] = new ListViewItem(new string[] {
                    texBuffer[i].AbsoluteID.ToString(), texBuffer[i].RelativeID, texBuffer[i].Type, texBuffer[i].Height.ToString(), texBuffer[i].Width.ToString(),
                    texBuffer[i].Size.ToString("N0", CultureInfo.InvariantCulture)
                });
            }

            lvTextures.BeginUpdate();
            lvTextures.Items.AddRange(texList);
            lvTextures.EndUpdate();
        }

        private void ShowPreview(Texture tex)
        {
            try
            {
                var prvBmp = Program.PreviewDDS(tex.Buffer.GetBuffer(), tex.Width, tex.Height);
                if (chkResizePrv.Checked && ((pbPrv.Width < tex.Width) || (pbPrv.Height < tex.Height)))
                {
                    float scaleWidth = (float)pbPrv.Width / (float)tex.Width;
                    float scaleHeight = (float)pbPrv.Height / (float)tex.Height;
                    float scaleFactor = Math.Min(scaleHeight, scaleWidth);
                    pbPrv.Image = Program.ResizePreview(prvBmp, (int)(tex.Width * scaleFactor), (int)(tex.Height * scaleFactor));
                }
                else
                {
                    pbPrv.Image = prvBmp;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        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 btnExtractAll_Click(object sender, EventArgs e)
        {
            string extPath = tbExtPath.Text;
            if (extPath != string.Empty)
            {
                Directory.CreateDirectory(extPath);
                bool extractResult = false;
                foreach (var tex in texBuffer)
                {
                    extractResult = Program.ExtractTexture(tex, extPath + tex.RelativeID + ".dds");
                    if (!extractResult)
                    {
                        break;
                    }
                }

                if (extractResult)
                {
                    MessageBox.Show("Textures extracted.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            else
            {
                MessageBox.Show("Please select extraction path!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

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

        private void lvTextures_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((lvTextures.SelectedIndices.Count > 0) && !chkDisPreview.Checked)
            {
                ShowPreview(texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)]);
            }
            else
            {
                pbPrv.Image = null;
            }
        }

        private void chkDisPreview_CheckedChanged(object sender, EventArgs e)
        {
            if (chkDisPreview.Checked)
            {
                pbPrv.Image = null;
                chkResizePrv.Enabled = false;
            }
            else
            {
                if (lvTextures.SelectedIndices.Count > 0)
                {
                    ShowPreview(texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)]);
                }

                chkResizePrv.Enabled = true;
            }
        }

        private void chkResizePrv_CheckedChanged(object sender, EventArgs e)
        {
            if (lvTextures.SelectedIndices.Count > 0)
            {
                ShowPreview(texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)]);
            }
        }

        private void lvTextures_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.Descending;
                }

                this.lvTextures.Sort();
                ListViewExtensions.SetSortIcon(lvTextures, targetColumn, lvColumnSorter.Order);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void lvTextures_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)
                {
                    contextMenuStripTexs.Show(listView, e.X + 4, e.Y + 4);
                }
            }
        }

        private void MainForm_Resize(object sender, EventArgs e)
        {
            if ((WindowState != FormWindowState.Minimized) && (previousState != FormWindowState.Minimized) && (lvTextures.SelectedIndices.Count > 0)
                && !chkDisPreview.Checked && chkResizePrv.Checked)
            {
                ShowPreview(texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)]);
            }

            previousState = WindowState;
        }

        private void cmExtract_Click(object sender, EventArgs e)
        {
            var tex = texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)];
            saveFileDialog.FileName = tex.RelativeID;
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                if (Program.ExtractTexture(tex, saveFileDialog.FileName))
                {
                    MessageBox.Show("Texture extracted.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        private void cmSwap_Click(object sender, EventArgs e)
        {
            openFileDialogDDS.Multiselect = false;
            if (openFileDialogDDS.ShowDialog() == DialogResult.OK)
            {
                SwapFile(openFileDialogDDS.FileName);
            }
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            bool saveResult = false;
            switch (fileType)
            {
                case 0:
                    saveResult = Program.SaveTMC(texBuffer, sourcePath);
                    break;

                case 1:
                    saveResult = Program.SaveH(texBuffer, sourcePath);
                    break;

                case 2:
                    saveResult = Program.SaveP(texBuffer, sourcePath);
                    break;
            }

            if (saveResult)
            {
                MessageBox.Show("File saved.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void pbPrv_MouseClick(object sender, MouseEventArgs e)
        {
            if ((e.Button == MouseButtons.Right) && (pbPrv.Image != null))
            {
                contextMenuStripTexs.Show(pbPrv, e.X + 4, e.Y + 4);
            }
        }

        private void lvTextures_ItemDrag(object sender, ItemDragEventArgs e)
        {
            try
            {
                string dndStubPath = Path.GetTempPath() + "DragNDropFile";
                File.Create(dndStubPath).Dispose();
                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)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            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)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            var tex = texBuffer[int.Parse(lvTextures.SelectedItems[0].Text)];
            Program.ExtractTexture(tex, Path.GetDirectoryName(dropedFilePath) + @"\" + tex.RelativeID + ".dds");
        }

        private void btnAddDelete_Click(object sender, EventArgs e)
        {
            contextMenuStripAddMove.Show(Cursor.Position.X, Cursor.Position.Y);
        }

        private void cmMoveTextures_Click(object sender, EventArgs e)
        {
            Program.ConvertTMC(texBuffer);
            lvTextures.BeginUpdate();
            foreach (ListViewItem lvTex in lvTextures.Items)
            {
                lvTex.SubItems[1].Text = texBuffer[int.Parse(lvTex.Text)].RelativeID;
            }

            lvTextures.EndUpdate();
            cmMoveTextures.Enabled = false;
        }

        private void cmAdd_Click(object sender, EventArgs e)
        {
            AddTextures(null);
        }

        private void cmAddTextures_Click(object sender, EventArgs e)
        {
            openFileDialogDDS.Multiselect = true;
            if (openFileDialogDDS.ShowDialog() == DialogResult.OK)
            {
                AddTextures(openFileDialogDDS.FileNames);
            }
        }

        private void AddTextures(string[] fileNames)
        {
            Texture[] newTexs;
            if (fileNames == null)
            {
                newTexs = new Texture[1];
                newTexs[0] = Program.AddTexture(null);
                texBuffer.Add(newTexs[0]);
            }
            else
            {
                newTexs = new Texture[fileNames.Length];
                for (int i = 0; i < fileNames.Length; i++)
                {
                    newTexs[i] = Program.AddTexture(fileNames[i]);
                    texBuffer.Add(newTexs[i]);
                }
            }

            var newItems = new ListViewItem[newTexs.Length];
            for (int i = 0; i < newTexs.Length; i++)
            {
                newItems[i] = new ListViewItem(new string[] {
                        newTexs[i].AbsoluteID.ToString(), newTexs[i].RelativeID, newTexs[i].Type, newTexs[i].Height.ToString(), newTexs[i].Width.ToString(),
                        newTexs[i].Size.ToString("N0", CultureInfo.InvariantCulture)
                    });
                newItems[i].BackColor = System.Drawing.Color.PaleGreen;
            }

            if (lvColumnSorter.Order != SortOrder.None)
            {
                lvColumnSorter.Order = SortOrder.None;
                ListViewExtensions.SetSortIcon(lvTextures, lvColumnSorter.SortColumn, SortOrder.None);
            }

            lvTextures.BeginUpdate();
            lvTextures.Items.AddRange(newItems);
            lvTextures.Items[lvTextures.Items.Count - 1].Selected = true;
            lvTextures.EndUpdate();
            try
            {
                lvTextures.TopItem = lvTextures.Items[lvTextures.Items.Count - 1];
            }
            catch (Exception)
            {
            }

            lvTextures.Select();
        }
    }

    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 = StrCmpLogicalW(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;
            }
        }

        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        private static extern int StrCmpLogicalW(string x, string y);
    }
}