using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PatchDownloader
{
	public partial class fPatchDownloader : Form
	{
		private string _patchListURL = "http://patch.cdn.gamigo.com/fous/gdp/PatchHive.txt";

		public fPatchDownloader()
		{
			InitializeComponent();
		}

		private void bDownload_Click(object sender, EventArgs e)
		{
			BackgroundWorker backgroundWorker = new BackgroundWorker();

			backgroundWorker.WorkerReportsProgress = true;

			backgroundWorker.DoWork += DownloadPatches;
			backgroundWorker.ProgressChanged += (doWorkSender, doWorkEventArgs) =>
			{
				if (doWorkEventArgs.UserState != null)
				{
					if (doWorkEventArgs.UserState is string)
					{
						lStatus.Text = (string)doWorkEventArgs.UserState;
					}

					pbDownloading.Value = doWorkEventArgs.ProgressPercentage;
				}
			};

			backgroundWorker.RunWorkerAsync();
		}

		private void tbDownloadDirectory_Click(object sender, EventArgs e)
		{
			ChooseDownloadDirectory();
		}

		private void bChooseDownloadDirectory_Click(object sender, EventArgs e)
		{
			ChooseDownloadDirectory();
		}
		
		private void bExtractAll_Click(object sender, EventArgs e)
		{
			BackgroundWorker backgroundWorker = new BackgroundWorker();

			backgroundWorker.WorkerReportsProgress = true;

			backgroundWorker.DoWork += ExtractAllPatches;
			backgroundWorker.ProgressChanged += (doWorkSender, doWorkEventArgs) =>
			{
				if (doWorkEventArgs.UserState != null)
				{
					if (doWorkEventArgs.UserState is string)
					{
						lStatus.Text = (string)doWorkEventArgs.UserState;
					}

					pbDownloading.Value = doWorkEventArgs.ProgressPercentage;
				}
			};

			backgroundWorker.RunWorkerAsync();
		}

		private void bCopyBins_Click(object sender, EventArgs e)
		{
			CopyBins();
		}

		private void bCheckPDB_Click(object sender, EventArgs e)
		{
			CheckPDB();
		}

		private void DownloadPatches(object sender, DoWorkEventArgs args)
		{
			BackgroundWorker backgroundWorker = (BackgroundWorker)sender;

			using (WebClient webClient = new WebClient())
			{
				Version minVersion = new Version();

				if (tbMinVersion.Text.Length > 0)
				{
					minVersion = new Version(tbMinVersion.Text);
				}

				string rootURL = null;
				List<string> patches = new List<string>();

				string patchList = webClient.DownloadString(_patchListURL);

				string[] patchListLines = patchList.Split(new[] { '\r', '\n' });

				foreach (string patchListLine in patchListLines)
				{
					if (patchListLine.StartsWith("#ROOT"))
					{
						string[] patchListLineParts = patchListLine.Split('\t');

						if (patchListLineParts.Length > 1)
						{
							rootURL = patchListLineParts[1];
						}
					}
					else if (patchListLine.StartsWith("#PATCH"))
					{
						string[] patchListLineParts = patchListLine.Split('\t');

						if (patchListLineParts.Length > 2)
						{
							Version version = new Version(patchListLineParts[1]);

							if (version.CompareTo(minVersion) > 0)
							{
								patches.Add(patchListLineParts[2]);
							}
						}
					}
				}

				if (rootURL == null)
				{
					backgroundWorker.ReportProgress(0, "Failed to download patches");
				}
				else if (patches.Count == 0)
				{
					backgroundWorker.ReportProgress(100, "No patches to download");
				}
				else
				{
					backgroundWorker.ReportProgress(0, "Starting downloads...");

					for (Int32 i = 0; i < patches.Count; i++)
					{
						try
						{
							webClient.DownloadFile($"{rootURL}//{patches[i]}", $"{tbDownloadDirectory.Text}\\{patches[i]}");
						}
						catch
						{

						}

						backgroundWorker.ReportProgress((Int32)(((double)i / (double)patches.Count) * 100), $"Downloading Patch {i}/{patches.Count}");
					}

					backgroundWorker.ReportProgress(100, "Finished");
				}
			}
		}

		private void ChooseDownloadDirectory()
		{
			FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();

			if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
			{
				tbDownloadDirectory.Text = folderBrowserDialog.SelectedPath;

				bDownload.Enabled = true;
			}
		}

		private void ExtractAllPatches(object sender, DoWorkEventArgs args)
		{
			BackgroundWorker backgroundWorker = (BackgroundWorker)sender;

			string[] filePaths = Directory.GetFiles(tbDownloadDirectory.Text, "*.gdp", SearchOption.TopDirectoryOnly);

			for (Int32 i = 0; i < filePaths.Length; i++)
			{
				backgroundWorker.ReportProgress((Int32)(((double)i / (double)filePaths.Length) * 100), filePaths[i]);

				string to = $"{filePaths[i]}";
				to = to.TrimEnd(".gdp".ToArray());

				using (FileStream fileStream = new FileStream(filePaths[i], FileMode.Open))
				using (BinaryReader binaryReader = new BinaryReader(fileStream))
				{
					byte[] hType = binaryReader.ReadBytes(3);

					if (Encoding.ASCII.GetString(hType) == "GDP")
					{
						byte[] hUnk = binaryReader.ReadBytes(5);
						byte[] hHead = binaryReader.ReadBytes(260);
						string sHead = Encoding.ASCII.GetString(hHead.Where(x => x > 0x00).ToArray());

						uint GDPUnk = binaryReader.ReadUInt32();
						uint GDPSpaceCount = binaryReader.ReadUInt32();
						byte[] GDPUnk2 = binaryReader.ReadBytes(40);

						Dictionary<int, GDPFile> GDPFiles = new Dictionary<int, GDPFile>();

						for (int x = 0; x < GDPSpaceCount; x++)
						{
							long type = binaryReader.ReadInt64();

							if (type != 2)
								continue;

							GDPFiles.Add(x, new GDPFile()
							{
								Type = type,
								ApplyLocation = Encoding.ASCII.GetString(binaryReader.ReadBytes(264).Where(y => y > 0x00).ToArray()),
								Position = binaryReader.ReadInt64(),
								DataLengthA = binaryReader.ReadInt64(),
								DataLengthB = binaryReader.ReadInt64(),
								Unk1 = binaryReader.ReadBytes(20),
							});

							long CPos = binaryReader.BaseStream.Position;
							binaryReader.BaseStream.Position = GDPFiles[x].Position;

							string extension = Path.GetExtension(GDPFiles[x].ApplyLocation);
							
							if (extension != String.Empty)
							{
								string dir = Path.GetDirectoryName(GDPFiles[x].ApplyLocation);

								if (!Directory.Exists(to + dir))
									Directory.CreateDirectory(to + dir);

								using (BinaryWriter binaryWriter = new BinaryWriter(File.Create(to + GDPFiles[x].ApplyLocation)))
								{
									long c = GDPFiles[x].DataLengthA;

									while (c > 0)
									{
										if (c > 2147483647)
										{
											binaryWriter.Write(binaryReader.ReadBytes(2147483647));
											c -= 2147483647;
										}
										else
										{
											binaryWriter.Write(binaryReader.ReadBytes((int)c));
											c = 0;
										}
									}
								}
							}

							binaryReader.BaseStream.Position = CPos;
						}
					}
					else
					{
						MessageBox.Show($"The file {filePaths[i]} isn't a GDP file");
					}
				}
			}

			MessageBox.Show($"Done extracting all GDPs");
		}

		private void CopyBins()
		{
			string[] patchDirectories = Directory.GetDirectories(tbDownloadDirectory.Text, "*", SearchOption.TopDirectoryOnly);

			if (!Directory.Exists($"{tbDownloadDirectory.Text}\\Bins"))
			{
				Directory.CreateDirectory($"{tbDownloadDirectory.Text}\\Bins");
			}

			if (patchDirectories.Length > 0)
			{
				foreach (string patchDirectory in patchDirectories)
				{
					string directoryName = Path.GetFileName(patchDirectory);

					if (File.Exists($"{patchDirectory}\\Fiesta.bin"))
					{
						File.Copy($"{patchDirectory}\\Fiesta.bin", $"{tbDownloadDirectory.Text}\\Bins\\Fiesta.{directoryName}.bin");
					}
				}
			}
		}

		private void CheckPDB()
		{
			string binsDirectory = $"{tbDownloadDirectory.Text}\\Bins";

			if (Directory.Exists(binsDirectory) && File.Exists($"{binsDirectory}\\ChkMatch.exe"))
			{
				string[] binFiles = Directory.GetFiles(binsDirectory, "*.bin", SearchOption.TopDirectoryOnly);

				if (binFiles.Length > 0)
				{
					using (StreamWriter streamWriter = File.AppendText($"{binsDirectory}\\Log.txt"))
					{
						foreach (string binFile in binFiles)
						{
							string binFileName = Path.GetFileName(binFile);

							Process process = new Process();
							process.StartInfo.WorkingDirectory = binsDirectory;
							process.StartInfo.FileName = $"{binsDirectory}\\ChkMatch.exe";
							process.StartInfo.Arguments = $"-c {binFileName} Fiesta.pdb";
							process.StartInfo.UseShellExecute = false;
							process.StartInfo.RedirectStandardOutput = true;
							process.Start();

							string output = process.StandardOutput.ReadToEnd();

							process.WaitForExit();

							streamWriter.WriteLine($"Output for {binFileName}");
							streamWriter.WriteLine(output);
							streamWriter.WriteLine();
						}
					}
				}
			}
		}

		private class GDPFile
		{
			public Int64 Type;
			public string ApplyLocation;
			public Int64 Position;
			public Int64 DataLengthA;
			public Int64 DataLengthB;
			public byte[] Unk1;
		}

		private void cbLocale_SelectedValueChanged(object sender, EventArgs e)
		{
			switch (cbLocale.Text)
			{
				case "US":
					_patchListURL = "http://patch.cdn.gamigo.com/fous/gdp/PatchHive.txt";
					break;

				case "DE":
				case "EN":
				case "FR":
				case "ES":
					_patchListURL = $"http://patch.cdn.gamigo.com/fo/{cbLocale.Text.ToLower()}/PatchHive.txt?";
					break;
			}
		}
	}
}