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

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

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

		public static TextWriter OriginalOut { get; set; }

		public static TextWriter StandardOut { get; private set; }

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

			if (_oOut == IntPtr.Zero)
				_oOut = 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);

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

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


#warning Fix OriginalOut

			//var originalOutStream = new FileStream(new SafeFileHandle(_oOut, false), FileAccess.Write);
			//OriginalOut = new StreamWriter(originalOutStream, new UTF8Encoding(false));
			OriginalOut = TextWriter.Null;

			Init();

			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(_cOut))
				throw new Exception("CloseHandle() failed");
			_cOut = IntPtr.Zero;
			if (!FreeConsole())
				throw new Exception("FreeConsole() failed");
			if (!SetStdHandle(-11, _oOut))
				throw new Exception("SetStdHandle() failed");
			Init();

			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);

		private static void Init()
		{
			var stdOut = Console.OpenStandardOutput();
			StandardOut = new StreamWriter(stdOut, Encoding.Default)
			{
				AutoFlush = true
			};

			Console.SetOut(StandardOut);
			Console.SetError(StandardOut);
		}

		[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);
	}
}