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

// 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;

namespace System
{
	public
#if NET_2_0
	static
#else
	sealed
#endif
	class Console
	{
		private class WindowsConsole
		{
			[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 ();

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

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

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

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

			if (Environment.IsRunningOnWindows) {
				//
				// On Windows, follow the Windows tradition
				//
				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.GetEncoding (28591);
				}
			} else {
				//
				// On Unix systems (128), do not output the
				// UTF-8 ZWNBSP (zero-width non-breaking space).
				//
				int code_page = 0;
				Encoding.InternalCodePage (ref code_page);

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

			stderr = new UnexceptionalStreamWriter (OpenStandardError (0), outputEncoding); 
			((StreamWriter)stderr).AutoFlush = true;
			stderr = TextWriter.Synchronized (stderr, true);

#if NET_2_0
			if (!Environment.IsRunningOnWindows && ConsoleDriver.IsConsole) {
				StreamWriter w = new CStreamWriter (OpenStandardOutput (0), outputEncoding);
				w.AutoFlush = true;
				stdout = w;
				stdin = new CStreamReader (OpenStandardInput (0), inputEncoding);
			} else {
#endif
				stdout = new UnexceptionalStreamWriter (OpenStandardOutput (0), outputEncoding);
				((StreamWriter)stdout).AutoFlush = true;
				stdout = TextWriter.Synchronized (stdout, true);
				stdin = new UnexceptionalStreamReader (OpenStandardInput (0), inputEncoding);
				stdin = TextReader.Synchronized (stdin);
#if NET_2_0
			}
#endif

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

#if !NET_2_0
		private Console ()
		{
		}
#endif

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

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

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

		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)
		{
			try {
				return new FileStream (MonoIO.ConsoleError, FileAccess.Write, false, bufferSize, false, bufferSize == 0);
			} catch (IOException) {
				return new NullStream ();
			}
		}

		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)
		{
			try {
				return new FileStream (MonoIO.ConsoleInput, FileAccess.Read, false, bufferSize, false, bufferSize == 0);
			} catch (IOException) {
				return new NullStream ();
			}
		}

		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)
		{
			try {
				return new FileStream (MonoIO.ConsoleOutput, FileAccess.Write, false, bufferSize, false, bufferSize == 0);
			} catch (IOException) {
				return new NullStream ();
			}
		}

		[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[] value)
		{
			stdout.Write (value);
		}

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

#if ! BOOTSTRAP_WITH_OLDLIB
		[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));
		}
#endif

		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[] value)
		{
			stdout.WriteLine (value);
		}

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

#if ! BOOTSTRAP_WITH_OLDLIB
		[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));
		}
#endif

#if NET_2_0
		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_0
		// FIXME: Console should use these encodings when changed
		static Encoding inputEncoding;
		static Encoding outputEncoding;

		public static Encoding InputEncoding {
			get { return inputEncoding; }
			set { inputEncoding = value; }
		}

		public static Encoding OutputEncoding {
			get { return outputEncoding; }
			set { outputEncoding = value; }
		}

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

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

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

		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 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 string Title {
			get { return ConsoleDriver.Title; }
			set { ConsoleDriver.Title = value; }
		}

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

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

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

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

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

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

		[MonoTODO ("Not implemented")]
		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
						int targetLeft, int targetTop)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO("Not implemented")]
		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
						int targetLeft, int targetTop, Char sourceChar,
						ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
		{
			throw new NotImplementedException ();
		}

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

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

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

		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;
			}
			remove {
				if (ConsoleDriver.Initialized == false)
					ConsoleDriver.Init ();

				cancel_event -= value;
			}
		}

		delegate void InternalCancelHandler ();
		static InternalCancelHandler cancel_handler = new InternalCancelHandler (DoConsoleCancelEvent);

		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
	}
}

