// --------------------------------------------------
// UnityInjector - ConsoleWindow.cs
// Copyright (c) Usagirei 2015 - 2015
// --------------------------------------------------

using System;
using System.Runtime.InteropServices;
using BepInEx;

namespace UnityInjector.ConsoleUtil
{
	internal class ConsoleWindow
	{
		public static bool IsAttached { get; private set; }
		public static IntPtr ConsoleOutHandle;
		public static IntPtr OriginalStdoutHandle;

		public static void Attach()
		{
			if (IsAttached)
				return;

			if (OriginalStdoutHandle == IntPtr.Zero)
				OriginalStdoutHandle = GetStdHandle(-11);

			// Store Current Window
			IntPtr currWnd = GetForegroundWindow();

			//Check for existing console before allocating
			if (GetConsoleWindow() == IntPtr.Zero)
				if (!AllocConsole())
					throw new Exception("AllocConsole() failed");

			// Restore Foreground
			SetForegroundWindow(currWnd);

			ConsoleOutHandle = CreateFile("CONOUT$", 0x80000000 | 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
			BepInEx.ConsoleUtil.Kon.conOut = ConsoleOutHandle;

			if (!SetStdHandle(-11, ConsoleOutHandle))
				throw new Exception("SetStdHandle() failed");

			if (OriginalStdoutHandle != IntPtr.Zero && ConsoleManager.ConfigConsoleOutRedirectType.Value == ConsoleManager.ConsoleOutRedirectType.ConsoleOut)
				CloseHandle(OriginalStdoutHandle);

			IsAttached = true;
		}

		public static string Title
		{
			set
			{
				if (!IsAttached)
					return;

				if (value == null)
				{
					throw new ArgumentNullException(nameof(value));
				}

				if (value.Length > 24500)
				{
					throw new InvalidOperationException("Console title too long");
				}

				if (!SetConsoleTitle(value))
				{
					throw new InvalidOperationException("Console title invalid");
				}
			}
		}

		public static void Detach()
		{
			if (!IsAttached)
				return;

			if (!CloseHandle(ConsoleOutHandle))
				throw new Exception("CloseHandle() failed");

			ConsoleOutHandle = IntPtr.Zero;

			if (!FreeConsole())
				throw new Exception("FreeConsole() failed");

			if (!SetStdHandle(-11, OriginalStdoutHandle))
				throw new Exception("SetStdHandle() failed");

			IsAttached = false;
		}

		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool SetForegroundWindow(IntPtr hWnd);

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool AllocConsole();

		[DllImport("kernel32.dll")]
		private static extern IntPtr GetConsoleWindow();

		[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
		private static extern bool CloseHandle(IntPtr handle);

		[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		private static extern IntPtr CreateFile(
			string fileName,
			uint desiredAccess,
			int shareMode,
			IntPtr securityAttributes,
			int creationDisposition,
			int flagsAndAttributes,
			IntPtr templateFile);

		[DllImport("kernel32.dll", SetLastError = false)]
		private static extern bool FreeConsole();

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern IntPtr GetStdHandle(int nStdHandle);

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput);

		[DllImport("kernel32.dll", BestFitMapping = true, CharSet = CharSet.Auto, SetLastError = true)]
		private static extern bool SetConsoleTitle(string title);
	}
}