//
// System.Console.cs
//
// Author:
// 	Dietmar Maurer (dietmar@ximian.com)
//	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) Ximian, Inc.  http://www.ximian.com
// (C) 2004,2005 Novell, Inc. (http://www.novell.com)
// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;

namespace System
{
	public static partial class Console
	{
#if !NET_2_1
		private class WindowsConsole
		{
			public static bool ctrlHandlerAdded = false;
			private delegate bool WindowsCancelHandler (int keyCode);
			private static WindowsCancelHandler cancelHandler = new WindowsCancelHandler (DoWindowsConsoleCancelEvent);

			[DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
			private static extern int GetConsoleCP ();
			[DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
			private static extern int GetConsoleOutputCP ();

			[DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
			private static extern bool SetConsoleCtrlHandler (WindowsCancelHandler handler, bool addHandler);

			// Only call the event handler if Control-C was pressed (code == 0), nothing else
			private static bool DoWindowsConsoleCancelEvent (int keyCode)
			{
				if (keyCode == 0)
					DoConsoleCancelEvent ();
				return keyCode == 0;
			}

			[MethodImpl (MethodImplOptions.NoInlining)]
			public static int GetInputCodePage ()
			{
				return GetConsoleCP ();
			}

			[MethodImpl (MethodImplOptions.NoInlining)]
			public static int GetOutputCodePage ()
			{
				return GetConsoleOutputCP ();
			}

			public static void AddCtrlHandler ()
			{
				SetConsoleCtrlHandler (cancelHandler, true);
				ctrlHandlerAdded = true;
			}
			
			public static void RemoveCtrlHandler ()
			{
				SetConsoleCtrlHandler (cancelHandler, false);
				ctrlHandlerAdded = false;
			}
		}
#endif

		internal static TextWriter stdout;
		private static TextWriter stderr;
		private static TextReader stdin;

		static Console ()
		{
#if NET_2_1
			Encoding inputEncoding;
			Encoding outputEncoding;
#endif

			if (Environment.IsRunningOnWindows) {
				//
				// On Windows, follow the Windows tradition
				//
#if NET_2_1
				// should never happen since Moonlight does not run on windows
				inputEncoding = outputEncoding = Encoding.Default;
#else			
				try {
					inputEncoding = Encoding.GetEncoding (WindowsConsole.GetInputCodePage ());
					outputEncoding = Encoding.GetEncoding (WindowsConsole.GetOutputCodePage ());
					// ArgumentException and NotSupportedException can be thrown as well
				} catch {
					// FIXME: I18N assemblies are not available when compiling mcs
					// Use Latin 1 as it is fast and UTF-8 is never used as console code page
					inputEncoding = outputEncoding = Encoding.Default;
				}
#endif
			} else {
				//
				// On Unix systems (128), do not output the
				// UTF-8 ZWNBSP (zero-width non-breaking space).
				//
				int code_page = 0;
				EncodingHelper.InternalCodePage (ref code_page);

				if (code_page != -1 && ((code_page & 0x0fffffff) == 3 // UTF8Encoding.UTF8_CODE_PAGE
					|| ((code_page & 0x10000000) != 0)))
					inputEncoding = outputEncoding = EncodingHelper.UTF8Unmarked;
				else
					inputEncoding = outputEncoding = Encoding.Default;
			}

			SetupStreams (inputEncoding, outputEncoding);
		}

		static void SetupStreams (Encoding inputEncoding, Encoding outputEncoding)
		{
#if !NET_2_1
			if (!Environment.IsRunningOnWindows && ConsoleDriver.IsConsole) {
				StreamWriter w = new CStreamWriter (OpenStandardOutput (0), outputEncoding, true);
				w.AutoFlush = true;
				stdout = TextWriter.Synchronized (w);

				w = new CStreamWriter (OpenStandardOutput (0), outputEncoding, true);
				w.AutoFlush = true;
				stderr = TextWriter.Synchronized (w);
				
				stdin = new CStreamReader (OpenStandardInput (0), inputEncoding);
			} else {
#endif
#if MONOTOUCH
				stdout = new NSLogWriter ();
#else
				stdout = new UnexceptionalStreamWriter (OpenStandardOutput (0), outputEncoding);
				((StreamWriter)stdout).AutoFlush = true;
#endif
				stdout = TextWriter.Synchronized (stdout);

#if MONOTOUCH
				stderr = new NSLogWriter ();
#else
				stderr = new UnexceptionalStreamWriter (OpenStandardError (0), outputEncoding); 
				((StreamWriter)stderr).AutoFlush = true;
#endif
				stderr = TextWriter.Synchronized (stderr);

				stdin = new UnexceptionalStreamReader (OpenStandardInput (0), inputEncoding);
				stdin = TextReader.Synchronized (stdin);
#if !NET_2_1
			}
#endif

#if MONODROID
			if (LogcatTextWriter.IsRunningOnAndroid ()) {
				stdout = TextWriter.Synchronized (new LogcatTextWriter ("mono-stdout", stdout));
				stderr = TextWriter.Synchronized (new LogcatTextWriter ("mono-stderr", stderr));
			}
#endif  // MONODROID

			GC.SuppressFinalize (stdout);
			GC.SuppressFinalize (stderr);
			GC.SuppressFinalize (stdin);
		}

		public static TextWriter Error {
			get {
				return stderr;
			}
		}

		public static TextWriter Out {
			get {
				return stdout;
			}
		}

		public static TextReader In {
			get {
				return stdin;
			}
		}

		private static Stream Open (IntPtr handle, FileAccess access, int bufferSize)
		{
			try {
				return new FileStream (handle, access, false, bufferSize, false, bufferSize == 0);
			} catch (IOException) {
				return Stream.Null;
			}
		}

		public static Stream OpenStandardError ()
		{
			return OpenStandardError (0);
		}

		// calling any FileStream constructor with a handle normally
		// requires permissions UnmanagedCode permissions. In this 
		// case we assert this permission so the console can be used
		// in partial trust (i.e. without having UnmanagedCode).
		[SecurityPermission (SecurityAction.Assert, UnmanagedCode = true)]
		public static Stream OpenStandardError (int bufferSize)
		{
			return Open (MonoIO.ConsoleError, FileAccess.Write, bufferSize);
		}

		public static Stream OpenStandardInput ()
		{
			return OpenStandardInput (0);
		}

		// calling any FileStream constructor with a handle normally
		// requires permissions UnmanagedCode permissions. In this 
		// case we assert this permission so the console can be used
		// in partial trust (i.e. without having UnmanagedCode).
		[SecurityPermission (SecurityAction.Assert, UnmanagedCode = true)]
		public static Stream OpenStandardInput (int bufferSize)
		{
			return Open (MonoIO.ConsoleInput, FileAccess.Read, bufferSize);
		}

		public static Stream OpenStandardOutput ()
		{
			return OpenStandardOutput (0);
		}

		// calling any FileStream constructor with a handle normally
		// requires permissions UnmanagedCode permissions. In this 
		// case we assert this permission so the console can be used
		// in partial trust (i.e. without having UnmanagedCode).
		[SecurityPermission (SecurityAction.Assert, UnmanagedCode = true)]
		public static Stream OpenStandardOutput (int bufferSize)
		{
			return Open (MonoIO.ConsoleOutput, FileAccess.Write, bufferSize);
		}

		[SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
		public static void SetError (TextWriter newError)
		{
			if (newError == null)
				throw new ArgumentNullException ("newError");

			stderr = newError;
		}

		[SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
		public static void SetIn (TextReader newIn)
		{
			if (newIn == null)
				throw new ArgumentNullException ("newIn");

			stdin = newIn;
		}

		[SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
		public static void SetOut (TextWriter newOut)
		{
			if (newOut == null)
				throw new ArgumentNullException ("newOut");

			stdout = newOut;
		}

		public static void Write (bool value)
		{
			stdout.Write (value);
		}

		public static void Write (char value)
		{
			stdout.Write (value);
		}

		public static void Write (char[] buffer)
		{
			stdout.Write (buffer);
		}

		public static void Write (decimal value)
		{
			stdout.Write (value);
		}
		
		public static void Write (double value)
		{
			stdout.Write (value);
		}

		public static void Write (int value)
		{
			stdout.Write (value);
		}

		public static void Write (long value)
		{
			stdout.Write (value);
		}

		public static void Write (object value)
		{
			stdout.Write (value);
		}

		public static void Write (float value)
		{
			stdout.Write (value);
		}

		public static void Write (string value)
		{
			stdout.Write (value);
		}

		[CLSCompliant (false)]
		public static void Write (uint value)
		{
			stdout.Write (value);
		}

		[CLSCompliant (false)]
		public static void Write (ulong value)
		{
			stdout.Write (value);
		}

		public static void Write (string format, object arg0)
		{
			stdout.Write (format, arg0);
		}

		public static void Write (string format, params object[] arg)
		{
			if (arg == null)
				stdout.Write (format);
			else
				stdout.Write (format, arg);
		}

		public static void Write (char[] buffer, int index, int count)
		{
			stdout.Write (buffer, index, count);
		}

		public static void Write (string format, object arg0, object arg1)
		{
			stdout.Write (format, arg0, arg1);
		}

		public static void Write (string format, object arg0, object arg1, object arg2 )
		{
			stdout.Write (format, arg0, arg1, arg2);
		}

		[CLSCompliant (false)]
		public static void Write (string format, object arg0, object arg1, object arg2, object arg3, __arglist)
		{
			ArgIterator iter = new ArgIterator (__arglist);
			int argCount = iter.GetRemainingCount();

			object[] args = new object [argCount + 4];
			args [0] = arg0;
			args [1] = arg1;
			args [2] = arg2;
			args [3] = arg3;
			for (int i = 0; i < argCount; i++) {
				TypedReference typedRef = iter.GetNextArg ();
				args [i + 4] = TypedReference.ToObject (typedRef);
			}

			stdout.Write (String.Format (format, args));
		}

		public static void WriteLine ()
		{
			stdout.WriteLine ();
		}

		public static void WriteLine (bool value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (char value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (char[] buffer)
		{
			stdout.WriteLine (buffer);
		}

		public static void WriteLine (decimal value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (double value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (int value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (long value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (object value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (float value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (string value)
		{
			stdout.WriteLine (value);
		}

		[CLSCompliant (false)]
		public static void WriteLine (uint value)
		{
			stdout.WriteLine (value);
		}

		[CLSCompliant (false)]
		public static void WriteLine (ulong value)
		{
			stdout.WriteLine (value);
		}

		public static void WriteLine (string format, object arg0)
		{
			stdout.WriteLine (format, arg0);
		}

		public static void WriteLine (string format, params object[] arg)
		{
			if (arg == null)
				stdout.WriteLine (format);
			else
				stdout.WriteLine (format, arg);
		}

		public static void WriteLine (char[] buffer, int index, int count)
		{
			stdout.WriteLine (buffer, index, count);
		}

		public static void WriteLine (string format, object arg0, object arg1)
		{
			stdout.WriteLine (format, arg0, arg1);
		}

		public static void WriteLine (string format, object arg0, object arg1, object arg2)
		{
			stdout.WriteLine (format, arg0, arg1, arg2);
		}

		[CLSCompliant (false)]
		public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3, __arglist)
		{
			ArgIterator iter = new ArgIterator (__arglist);
			int argCount = iter.GetRemainingCount();

			object[] args = new object [argCount + 4];
			args [0] = arg0;
			args [1] = arg1;
			args [2] = arg2;
			args [3] = arg3;
			for (int i = 0; i < argCount; i++) {
				TypedReference typedRef = iter.GetNextArg ();
				args [i + 4] = TypedReference.ToObject (typedRef);
			}

			stdout.WriteLine (String.Format (format, args));
		}

#if !NET_2_1
		public static int Read ()
		{
			if ((stdin is CStreamReader) && ConsoleDriver.IsConsole) {
				return ConsoleDriver.Read ();
			} else {
				return stdin.Read ();
			}
		}

		public static string ReadLine ()
		{
			if ((stdin is CStreamReader) && ConsoleDriver.IsConsole) {
				return ConsoleDriver.ReadLine ();
			} else {
				return stdin.ReadLine ();
			}
		}
#else
		public static int Read ()
		{
			return stdin.Read ();
		}

		public static string ReadLine ()
		{
			return stdin.ReadLine ();
		}

#endif

#if !NET_2_1
		// FIXME: Console should use these encodings when changed
		static Encoding inputEncoding;
		static Encoding outputEncoding;

		public static Encoding InputEncoding {
			get { return inputEncoding; }
			set {
				inputEncoding = value;
				SetupStreams (inputEncoding, outputEncoding);
			}
		}

		public static Encoding OutputEncoding {
			get { return outputEncoding; }
			set {
				outputEncoding = value;
				SetupStreams (inputEncoding, outputEncoding);
			}
		}

		public static ConsoleColor BackgroundColor {
			get { return ConsoleDriver.BackgroundColor; }
			set { ConsoleDriver.BackgroundColor = value; }
		}

		public static int BufferHeight {
			get { return ConsoleDriver.BufferHeight; }
			[MonoLimitation ("Implemented only on Windows")]
			set { ConsoleDriver.BufferHeight = value; }
		}

		public static int BufferWidth {
			get { return ConsoleDriver.BufferWidth; }
			[MonoLimitation ("Implemented only on Windows")]
			set { ConsoleDriver.BufferWidth = value; }
		}

		[MonoLimitation ("Implemented only on Windows")]
		public static bool CapsLock {
			get { return ConsoleDriver.CapsLock; }
		}

		public static int CursorLeft {
			get { return ConsoleDriver.CursorLeft; }
			set { ConsoleDriver.CursorLeft = value; }
		}

		public static int CursorTop {
			get { return ConsoleDriver.CursorTop; }
			set { ConsoleDriver.CursorTop = value; }
		}

		public static int CursorSize {
			get { return ConsoleDriver.CursorSize; }
			set { ConsoleDriver.CursorSize = value; }
		}

		public static bool CursorVisible {
			get { return ConsoleDriver.CursorVisible; }
			set { ConsoleDriver.CursorVisible = value; }
		}

		public static ConsoleColor ForegroundColor {
			get { return ConsoleDriver.ForegroundColor; }
			set { ConsoleDriver.ForegroundColor = value; }
		}

		public static bool KeyAvailable {
			get { return ConsoleDriver.KeyAvailable; }
		}

		public static int LargestWindowHeight {
			get { return ConsoleDriver.LargestWindowHeight; }
		}

		public static int LargestWindowWidth {
			get { return ConsoleDriver.LargestWindowWidth; }
		}

		[MonoLimitation ("Only works on windows")]
		public static bool NumberLock {
			get { return ConsoleDriver.NumberLock; }
		}

		public static string Title {
			get { return ConsoleDriver.Title; }
			set { ConsoleDriver.Title = value; }
		}

		public static bool TreatControlCAsInput {
			get { return ConsoleDriver.TreatControlCAsInput; }
			set { ConsoleDriver.TreatControlCAsInput = value; }
		}

		[MonoLimitation ("Only works on windows")]
		public static int WindowHeight {
			get { return ConsoleDriver.WindowHeight; }
			set { ConsoleDriver.WindowHeight = value; }
		}

		[MonoLimitation ("Only works on windows")]
		public static int WindowLeft {
			get { return ConsoleDriver.WindowLeft; }
			set { ConsoleDriver.WindowLeft = value; }
		}

		[MonoLimitation ("Only works on windows")]
		public static int WindowTop {
			get { return ConsoleDriver.WindowTop; }
			set { ConsoleDriver.WindowTop = value; }
		}

		[MonoLimitation ("Only works on windows")]
		public static int WindowWidth {
			get { return ConsoleDriver.WindowWidth; }
			set { ConsoleDriver.WindowWidth = value; }
		}

		public static bool IsErrorRedirected {
			get {
				return ConsoleDriver.IsErrorRedirected;
			}
		}

		public static bool IsOutputRedirected {
			get {
				return ConsoleDriver.IsOutputRedirected;
			}
		}

		public static bool IsInputRedirected {
			get {
				return ConsoleDriver.IsInputRedirected;
			}
		}

		public static void Beep ()
		{
			Beep (1000, 500);
		}

		public static void Beep (int frequency, int duration)
		{
			if (frequency < 37 || frequency > 32767)
				throw new ArgumentOutOfRangeException ("frequency");

			if (duration <= 0)
				throw new ArgumentOutOfRangeException ("duration");

			ConsoleDriver.Beep (frequency, duration);
		}

		public static void Clear ()
		{
			ConsoleDriver.Clear ();
		}

		[MonoLimitation ("Implemented only on Windows")]
		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
						int targetLeft, int targetTop)
		{
			ConsoleDriver.MoveBufferArea (sourceLeft, sourceTop, sourceWidth, sourceHeight, targetLeft, targetTop);
		}

		[MonoLimitation ("Implemented only on Windows")]
		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
						int targetLeft, int targetTop, Char sourceChar,
						ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
		{
			ConsoleDriver.MoveBufferArea (sourceLeft, sourceTop, sourceWidth, sourceHeight, targetLeft, targetTop,
							sourceChar, sourceForeColor, sourceBackColor);
		}

		public static ConsoleKeyInfo ReadKey ()
		{
			return ReadKey (false);
		}

		public static ConsoleKeyInfo ReadKey (bool intercept)
		{
			return ConsoleDriver.ReadKey (intercept);
		}

		public static void ResetColor ()
		{
			ConsoleDriver.ResetColor ();
		}

		[MonoLimitation ("Only works on windows")]
		public static void SetBufferSize (int width, int height)
		{
			ConsoleDriver.SetBufferSize (width, height);
		}

		public static void SetCursorPosition (int left, int top)
		{
			ConsoleDriver.SetCursorPosition (left, top);
		}

		public static void SetWindowPosition (int left, int top)
		{
			ConsoleDriver.SetWindowPosition (left, top);
		}

		public static void SetWindowSize (int width, int height)
		{
			ConsoleDriver.SetWindowSize (width, height);
		}

		static ConsoleCancelEventHandler cancel_event;
		public static event ConsoleCancelEventHandler CancelKeyPress {
			add {
				if (ConsoleDriver.Initialized == false)
					ConsoleDriver.Init ();

				cancel_event += value;

				if (Environment.IsRunningOnWindows && !WindowsConsole.ctrlHandlerAdded)
					WindowsConsole.AddCtrlHandler();
			}
			remove {
				if (ConsoleDriver.Initialized == false)
					ConsoleDriver.Init ();

				cancel_event -= value;

				if (cancel_event == null && Environment.IsRunningOnWindows)
				{
					// Need to remove our hook if there's nothing left in the event
					if (WindowsConsole.ctrlHandlerAdded)
						WindowsConsole.RemoveCtrlHandler();
				}
			}
		}

		delegate void InternalCancelHandler ();
		
#pragma warning disable 414
		//
		// Used by console-io.c
		//
		static readonly InternalCancelHandler cancel_handler = new InternalCancelHandler (DoConsoleCancelEvent);
#pragma warning restore 414		

		internal static void DoConsoleCancelEvent ()
		{
			bool exit = true;
			if (cancel_event != null) {
				ConsoleCancelEventArgs args = new ConsoleCancelEventArgs (ConsoleSpecialKey.ControlC);
				Delegate [] delegates = cancel_event.GetInvocationList ();
				foreach (ConsoleCancelEventHandler d in delegates){
					try {
						// Sender is always null here.
						d (null, args);
					} catch {} // Ignore any exception.
				}
				exit = !args.Cancel;
			}

			if (exit)
				Environment.Exit (58);
		}
#endif
	}
}