/********************************************************************++
Copyright (c) Microsoft Corporation.  All rights reserved.
--********************************************************************/

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace Microsoft.PowerShell.Internal
{
#pragma warning disable 1591

    internal static class NativeMethods
    {
        public const uint MAPVK_VK_TO_VSC   = 0x00;
        public const uint MAPVK_VSC_TO_VK   = 0x01;
        public const uint MAPVK_VK_TO_CHAR  = 0x02;

        public const byte VK_SHIFT          = 0x10;
        public const byte VK_CONTROL        = 0x11;
        public const byte VK_ALT            = 0x12;
        public const uint MENU_IS_ACTIVE    = 0x01;
        public const uint MENU_IS_INACTIVE  = 0x00; // windows key

        public const uint ENABLE_PROCESSED_INPUT = 0x0001;
        public const uint ENABLE_LINE_INPUT      = 0x0002;
        public const uint ENABLE_WINDOW_INPUT    = 0x0008;
        public const uint ENABLE_MOUSE_INPUT     = 0x0010;

        public const int FontTypeMask = 0x06;
        public const int TrueTypeFont = 0x04;

        internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);  // WinBase.h

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr GetStdHandle(uint handleId);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,
            ref SMALL_RECT lpScrollRectangle,
            IntPtr lpClipRectangle,
            COORD dwDestinationOrigin,
            ref CHAR_INFO lpFill);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool WriteConsole(IntPtr hConsoleOutput, string lpBuffer, uint nNumberOfCharsToWrite, out uint lpNumberOfCharsWritten, IntPtr lpReserved);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);

        [DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool WriteConsoleOutput(IntPtr consoleOutput, CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT writeRegion);

        [DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool ReadConsoleOutput(IntPtr consoleOutput, [Out] CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT readRegion);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint MapVirtualKey(uint uCode, uint uMapType);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern int ToUnicode(uint uVirtKey, uint uScanCode, byte[] lpKeyState,
           [MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, int charMaxCount, uint flags);

        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern short VkKeyScan(char @char);

        [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint GetConsoleOutputCP();

        [DllImport("User32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr GetConsoleWindow();

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool TranslateCharsetInfo(IntPtr src, out CHARSETINFO Cs, uint options);

        [DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC tm);

        [DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool GetCharWidth32(IntPtr hdc, uint first, uint last, out int width);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr CreateFile
        (
            string fileName,
            uint desiredAccess,
            uint ShareModes,
            IntPtr securityAttributes,
            uint creationDisposition,
            uint flagsAndAttributes,
            IntPtr templateFileWin32Handle
        );

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool GetCurrentConsoleFontEx(IntPtr consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);

        [StructLayout(LayoutKind.Sequential)]
        internal struct COLORREF
        {
            internal uint ColorDWORD;

            internal uint R
            {
                get { return ColorDWORD & 0xff; }
            }

            internal uint G
            {
                get { return (ColorDWORD >> 8) & 0xff; }
            }

            internal uint B
            {
                get { return (ColorDWORD >> 16) & 0xff; }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct CONSOLE_SCREEN_BUFFER_INFO_EX
        {
            internal int cbSize;
            internal COORD dwSize;
            internal COORD dwCursorPosition;
            internal ushort wAttributes;
            internal SMALL_RECT srWindow;
            internal COORD dwMaximumWindowSize;
            internal ushort wPopupAttributes;
            internal bool bFullscreenSupported;
            internal COLORREF Black;
            internal COLORREF DarkBlue;
            internal COLORREF DarkGreen;
            internal COLORREF DarkCyan;
            internal COLORREF DarkRed;
            internal COLORREF DarkMagenta;
            internal COLORREF DarkYellow;
            internal COLORREF Gray;
            internal COLORREF DarkGray;
            internal COLORREF Blue;
            internal COLORREF Green;
            internal COLORREF Cyan;
            internal COLORREF Red;
            internal COLORREF Magenta;
            internal COLORREF Yellow;
            internal COLORREF White;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
            ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool SetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
            ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
    }

    public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);

    public enum ConsoleBreakSignal : uint
    {
        CtrlC     = 0,
        CtrlBreak = 1,
        Close     = 2,
        Logoff    = 5,
        Shutdown  = 6,
        None      = 255,
    }

    internal enum CHAR_INFO_Attributes : ushort
    {
        COMMON_LVB_LEADING_BYTE = 0x0100,
        COMMON_LVB_TRAILING_BYTE = 0x0200
    }

    public enum StandardHandleId : uint
    {
        Error  = unchecked((uint)-12),
        Output = unchecked((uint)-11),
        Input  = unchecked((uint)-10),
    }

    [Flags]
    internal enum AccessQualifiers : uint
    {
        // From winnt.h
        GenericRead = 0x80000000,
        GenericWrite = 0x40000000
    }

    internal enum CreationDisposition : uint
    {
        // From winbase.h
        CreateNew = 1,
        CreateAlways = 2,
        OpenExisting = 3,
        OpenAlways = 4,
        TruncateExisting = 5
    }

    [Flags]
    internal enum ShareModes : uint
    {
        // From winnt.h
        ShareRead = 0x00000001,
        ShareWrite = 0x00000002
    }

    public struct SMALL_RECT
    {
        public short Left;
        public short Top;
        public short Right;
        public short Bottom;

        [ExcludeFromCodeCoverage]
        public override string ToString()
        {
            return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom);
        }
    }

    internal struct COORD
    {
        public short X;
        public short Y;

        [ExcludeFromCodeCoverage]
        public override string ToString()
        {
            return String.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct FONTSIGNATURE
    {
        //From public\sdk\inc\wingdi.h

        // fsUsb*: A 128-bit Unicode subset bitfield (USB) identifying up to 126 Unicode subranges
        internal uint fsUsb0;
        internal uint fsUsb1;
        internal uint fsUsb2;
        internal uint fsUsb3;
        // fsCsb*: A 64-bit, code-page bitfield (CPB) that identifies a specific character set or code page.
        internal uint fsCsb0;
        internal uint fsCsb1;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct CHARSETINFO
    {
        //From public\sdk\inc\wingdi.h
        internal uint ciCharset;   // Character set value.
        internal uint ciACP;       // ANSI code-page identifier.
        internal FONTSIGNATURE fs;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct TEXTMETRIC
    {
        //From public\sdk\inc\wingdi.h
        public int tmHeight;
        public int tmAscent;
        public int tmDescent;
        public int tmInternalLeading;
        public int tmExternalLeading;
        public int tmAveCharWidth;
        public int tmMaxCharWidth;
        public int tmWeight;
        public int tmOverhang;
        public int tmDigitizedAspectX;
        public int tmDigitizedAspectY;
        public char tmFirstChar;
        public char tmLastChar;
        public char tmDefaultChar;
        public char tmBreakChar;
        public byte tmItalic;
        public byte tmUnderlined;
        public byte tmStruckOut;
        public byte tmPitchAndFamily;
        public byte tmCharSet;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct CONSOLE_FONT_INFO_EX
    {
        internal int cbSize;
        internal int nFont;
        internal short FontWidth;
        internal short FontHeight;
        internal int FontFamily;
        internal int FontWeight;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        internal string FontFace;
    }

    public struct BufferChar
    {
        public char UnicodeChar;
        public ConsoleColor ForegroundColor;
        public ConsoleColor BackgroundColor;
#if !UNIX
        public bool IsLeadByte;
        public bool IsTrailByte;
#endif
        public bool Inverse;

#if !UNIX
        public CHAR_INFO ToCharInfo()
        {
            int fg = (int) ForegroundColor;
            int bg = (int) BackgroundColor;

            if (fg < 0 || fg > 0xf || bg < 0 || bg > 0xf)
                throw new InvalidOperationException();

            if (Inverse)
            {
                // TODO: check $host.UI.SupportsVirtualTerminal and maybe set Inverse instead (it does look weird)
                fg = fg ^ 7;
                bg = bg ^ 7;
            }
            ushort attrs = (ushort)(fg | (bg << 4));
            if (IsLeadByte)
                attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE;
            if (IsTrailByte)
                attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE;
            CHAR_INFO result = new CHAR_INFO
            {
                UnicodeChar = UnicodeChar,
                Attributes = attrs,
            };

            return result;
        }

        public static BufferChar FromCharInfo(CHAR_INFO charInfo)
        {
            BufferChar result = new BufferChar
            {
                UnicodeChar = (char) charInfo.UnicodeChar,
                ForegroundColor = (ConsoleColor)(charInfo.Attributes & 0xf),
                BackgroundColor = (ConsoleColor)((charInfo.Attributes & 0x00f0) >> 4),
            };
            return result;
        }
#endif
    }

    public struct CHAR_INFO
    {
        public ushort UnicodeChar;
        public ushort Attributes;
    }

    internal static class ConsoleKeyInfoExtension
    {
#if !UNIX
        private static bool _toUnicodeApiAvailable = true;

        static ConsoleKeyInfoExtension()
        {
            try
            {
                var chars = new char[2];
                NativeMethods.ToUnicode(13, 28, null, chars, chars.Length, 0);
            }
            catch (System.EntryPointNotFoundException)
            {
                _toUnicodeApiAvailable = false;  // api not available on NanoServer
            }
        }
#endif

        public static string ToGestureString(this ConsoleKeyInfo key)
        {
            var mods = key.Modifiers;

            var sb = new StringBuilder();
            if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
            {
                sb.Append("Ctrl");
            }
            if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
            {
                if (sb.Length > 0)
                    sb.Append("+");
                sb.Append("Alt");
            }

            char c = key.KeyChar;
#if !UNIX
            if (_toUnicodeApiAvailable)
            {
                // Windows cannot use KeyChar as some chords (like Ctrl+[) show up as control characters.
                c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key,
                    (mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0);
            }
#endif

            if (char.IsControl(c) || char.IsWhiteSpace(c))
            {
                if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
                {
                    if (sb.Length > 0)
                        sb.Append("+");
                    sb.Append("Shift");
                }
                if (sb.Length > 0)
                    sb.Append("+");
                sb.Append(key.Key);
            }
            else
            {
                if (sb.Length > 0)
                    sb.Append("+");
                sb.Append(c);
            }
            return sb.ToString();
        }
    }

#if !UNIX
    internal class ConhostConsole : IConsole
    {
        [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
        private IntPtr _hwnd = (IntPtr)0;
        [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
        private IntPtr _hDC = (IntPtr)0;
        private uint _codePage;
        private bool _istmInitialized = false;
        private TEXTMETRIC _tm = new TEXTMETRIC();
        private bool _trueTypeInUse = false;
        private static bool _getCurrentConsoleFontExApiAvailable = true;

        private readonly Lazy<SafeFileHandle> _outputHandle = new Lazy<SafeFileHandle>(() =>
        {
            // We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
            var handle = NativeMethods.CreateFile(
                "CONOUT$",
                (UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
                (UInt32)ShareModes.ShareWrite,
                (IntPtr)0,
                (UInt32)CreationDisposition.OpenExisting,
                0,
                (IntPtr)0);

            if (handle == NativeMethods.INVALID_HANDLE_VALUE)
            {
                int err = Marshal.GetLastWin32Error();
                Win32Exception innerException = new Win32Exception(err);
                throw new Exception("Failed to retrieve the input console handle.", innerException);
            }

            return new SafeFileHandle(handle, true);
        }
        );

        private readonly Lazy<SafeFileHandle> _inputHandle = new Lazy<SafeFileHandle>(() =>
        {
            // We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
            var handle = NativeMethods.CreateFile(
                "CONIN$",
                (UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
                (UInt32)ShareModes.ShareWrite,
                (IntPtr)0,
                (UInt32)CreationDisposition.OpenExisting,
                0,
                (IntPtr)0);

            if (handle == NativeMethods.INVALID_HANDLE_VALUE)
            {
                int err = Marshal.GetLastWin32Error();
                Win32Exception innerException = new Win32Exception(err);
                throw new Exception("Failed to retrieve the input console handle.", innerException);
            }

            return new SafeFileHandle(handle, true);
        });

        public object GetConsoleInputMode()
        {
            uint mode;
            var handle = _inputHandle.Value.DangerousGetHandle();
            NativeMethods.GetConsoleMode(handle, out mode);
            return mode;
        }

        public void SetConsoleInputMode(object modeObj)
        {
            if (modeObj is uint)
            {
                // Clear a couple flags so we can actually receive certain keys:
                //     ENABLE_PROCESSED_INPUT - enables Ctrl+C
                //     ENABLE_LINE_INPUT - enables Ctrl+S
                // Also clear a couple flags so we don't mask the input that we ignore:
                //     ENABLE_MOUSE_INPUT - mouse events
                //     ENABLE_WINDOW_INPUT - window resize events
                var mode = (uint)modeObj &
                           ~(NativeMethods.ENABLE_PROCESSED_INPUT |
                             NativeMethods.ENABLE_LINE_INPUT |
                             NativeMethods.ENABLE_WINDOW_INPUT |
                             NativeMethods.ENABLE_MOUSE_INPUT);

                var handle = _inputHandle.Value.DangerousGetHandle();
                NativeMethods.SetConsoleMode(handle, mode);
            }
        }

        public void RestoreConsoleInputMode(object modeObj)
        {
            if (modeObj is uint)
            {
                var handle = _inputHandle.Value.DangerousGetHandle();
                NativeMethods.SetConsoleMode(handle, (uint)modeObj);
            }
        }

        public ConsoleKeyInfo ReadKey()
        {
            return Console.ReadKey(true);
        }

        public bool KeyAvailable
        {
            get { return Console.KeyAvailable; }
        }

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

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

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

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

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

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

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

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

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

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

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

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

        public void Write(string value)
        {
            Console.Write(value);
        }

        public void WriteLine(string value)
        {
            Console.WriteLine(value);
        }

        public void WriteBufferLines(BufferChar[] buffer, ref int top)
        {
            WriteBufferLines(buffer, ref top, true);
        }

        public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
        {
            int bufferWidth = Console.BufferWidth;
            int bufferLineCount = buffer.Length / bufferWidth;
            if ((top + bufferLineCount) > Console.BufferHeight)
            {
                var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
                ScrollBuffer(scrollCount);
                top -= scrollCount;
            }

            var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
            var bufferSize = new COORD
            {
                X = (short) bufferWidth,
                Y = (short) bufferLineCount
            };
            var bufferCoord = new COORD {X = 0, Y = 0};
            var bottom = top + bufferLineCount - 1;
            var writeRegion = new SMALL_RECT
            {
                Top = (short) top,
                Left = 0,
                Bottom = (short) bottom,
                Right = (short) (bufferWidth - 1)
            };
            NativeMethods.WriteConsoleOutput(handle, ToCharInfoBuffer(buffer),
                                             bufferSize, bufferCoord, ref writeRegion);

            // Now make sure the bottom line is visible
            if (ensureBottomLineVisible &&
                (bottom >= (Console.WindowTop + Console.WindowHeight)))
            {
                Console.CursorTop = bottom;
            }
        }

        public void ScrollBuffer(int lines)
        {
            var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);

            var scrollRectangle = new SMALL_RECT
            {
                Top = (short) lines,
                Left = 0,
                Bottom = (short)(Console.BufferHeight - 1),
                Right = (short)Console.BufferWidth
            };
            var destinationOrigin = new COORD {X = 0, Y = 0};
            var fillChar = new CHAR_INFO
            {
                UnicodeChar = ' ',
                Attributes = (ushort)((int)Console.ForegroundColor | ((int)Console.BackgroundColor << 4))
            };
            NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
        }

        public BufferChar[] ReadBufferLines(int top, int count)
        {
            var result = new CHAR_INFO[BufferWidth * count];
            var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);

            var readBufferSize = new COORD {
                X = (short)BufferWidth,
                Y = (short)count};
            var readBufferCoord = new COORD {X = 0, Y = 0};
            var readRegion = new SMALL_RECT
            {
                Top = (short)top,
                Left = 0,
                Bottom = (short)(top + count),
                Right = (short)(BufferWidth - 1)
            };
            NativeMethods.ReadConsoleOutput(handle, result,
                readBufferSize, readBufferCoord, ref readRegion);

            return ToBufferCharBuffer(result);
        }

        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
            Justification = "Then the API we pass the handle to will return an error if it is invalid. They are not exposed.")]
        internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(SafeFileHandle consoleHandle)
        {

            CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
            fontInfo.cbSize = Marshal.SizeOf(fontInfo);
            bool result = true;
            if (_getCurrentConsoleFontExApiAvailable)
            {
                try
                {
                    result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
                }
                catch (System.EntryPointNotFoundException)
                {
                    _getCurrentConsoleFontExApiAvailable = false;  // api not available on NanoServer
                }
            }

            if (result == false)
            {
                _getCurrentConsoleFontExApiAvailable = false; // api not supported on ServerCore
            }
            return fontInfo;
        }

        public int LengthInBufferCells(char c)
        {
            if (!IsCJKOutputCodePage() || !_trueTypeInUse)
                return 1;

            return LengthInBufferCellsFE(c);
        }

        internal static bool IsAnyDBCSCharSet(uint charSet)
        {
            const uint SHIFTJIS_CHARSET = 128;
            const uint HANGUL_CHARSET = 129;
            const uint CHINESEBIG5_CHARSET = 136;
            const uint GB2312_CHARSET = 134;
            return charSet == SHIFTJIS_CHARSET || charSet == HANGUL_CHARSET ||
                   charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET;
        }

        internal uint CodePageToCharSet()
        {
            CHARSETINFO csi;
            const uint TCI_SRCCODEPAGE = 2;
            const uint OEM_CHARSET = 255;
            if (!NativeMethods.TranslateCharsetInfo((IntPtr)_codePage, out csi, TCI_SRCCODEPAGE))
            {
                csi.ciCharset = OEM_CHARSET;
            }
            return csi.ciCharset;
        }

        /// <summary>
        /// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
        /// </summary>
        /// <returns>true if it is CJK code page; otherwise, false.</returns>
        internal bool IsCJKOutputCodePage()
        {
            return _codePage == 932 || // Japanese
                   _codePage == 936 || // Simplified Chinese
                   _codePage == 949 || // Korean
                   _codePage == 950;  // Traditional Chinese
        }

        internal bool IsAvailableFarEastCodePage()
        {
            uint charSet = CodePageToCharSet();
            return IsAnyDBCSCharSet(charSet);
        }

        internal int LengthInBufferCellsFE(char c)
        {
            if (0x20 <= c && c <= 0x7e)
            {
                /* ASCII */
                return 1;
            }
            else if (0x3041 <= c && c <= 0x3094)
            {
                /* Hiragana */
                return 2;
            }
            else if (0x30a1 <= c && c <= 0x30f6)
            {
                /* Katakana */
                return 2;
            }
            else if (0x3105 <= c && c <= 0x312c)
            {
                /* Bopomofo */
                return 2;
            }
            else if (0x3131 <= c && c <= 0x318e)
            {
                /* Hangul Elements */
                return 2;
            }
            else if (0xac00 <= c && c <= 0xd7a3)
            {
                /* Korean Hangul Syllables */
                return 2;
            }
            else if (0xff01 <= c && c <= 0xff5e)
            {
                /* Fullwidth ASCII variants */
                return 2;
            }
            else if (0xff61 <= c && c <= 0xff9f)
            {
                /* Halfwidth Katakana variants */
                return 1;
            }
            else if ((0xffa0 <= c && c <= 0xffbe) ||
                     (0xffc2 <= c && c <= 0xffc7) ||
                     (0xffca <= c && c <= 0xffcf) ||
                     (0xffd2 <= c && c <= 0xffd7) ||
                     (0xffda <= c && c <= 0xffdc))
            {
                /* Halfwidth Hangul variants */
                return 1;
            }
            else if (0xffe0 <= c && c <= 0xffe6)
            {
                /* Fullwidth symbol variants */
                return 2;
            }
            else if (0x4e00 <= c && c <= 0x9fa5)
            {
                /* Han Ideographic */
                return 2;
            }
            else if (0xf900 <= c && c <= 0xfa2d)
            {
                /* Han Compatibility Ideographs */
                return 2;
            }
            else
            {
                /* Unknown character: need to use GDI*/
                if (_hDC == (IntPtr)0)
                {
                    _hwnd = NativeMethods.GetConsoleWindow();
                    if ((IntPtr)0 == _hwnd)
                    {
                        return 1;
                    }
                    _hDC = NativeMethods.GetDC(_hwnd);
                    if ((IntPtr)0 == _hDC)
                    {
                        //Don't throw exception so that output can continue
                        return 1;
                    }
                }
                bool result = true;
                if (!_istmInitialized)
                {
                    result = NativeMethods.GetTextMetrics(_hDC, out _tm);
                    if (!result)
                    {
                        return 1;
                    }
                    _istmInitialized = true;
                }
                int width;
                result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width);
                if (!result)
                {
                    return 1;
                }
                if (width >= _tm.tmMaxCharWidth)
                {
                    return 2;
                }
            }
            return 1;
        }

        public void StartRender()
        {
            _codePage = NativeMethods.GetConsoleOutputCP();
            _istmInitialized = false;
            var consoleHandle = _outputHandle.Value;
            CONSOLE_FONT_INFO_EX fontInfo = ConhostConsole.GetConsoleFontInfo(consoleHandle);
            int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
            _trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
        }

        public void EndRender()
        {
            if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0)
            {
                NativeMethods.ReleaseDC(_hwnd, _hDC);
            }
        }

        private CHAR_INFO[] cachedBuffer;
        private CHAR_INFO[] ToCharInfoBuffer(BufferChar[] buffer)
        {
            if (cachedBuffer == null || cachedBuffer.Length != buffer.Length)
            {
                cachedBuffer = new CHAR_INFO[buffer.Length];
            }

            for (int i = 0; i < buffer.Length; i++)
            {
                cachedBuffer[i] = buffer[i].ToCharInfo();
            }

            return cachedBuffer;
        }

        private BufferChar[] ToBufferCharBuffer(CHAR_INFO[] buffer)
        {
            var result = new BufferChar[buffer.Length];

            for (int i = 0; i < buffer.Length; i++)
            {
                result[i] = BufferChar.FromCharInfo(buffer[i]);
            }

            return result;
        }

    }

#else

    internal class TTYConsole : IConsole
    {
        public object GetConsoleInputMode()
        {
            return Console.TreatControlCAsInput;
        }

        public void SetConsoleInputMode(object modeObj)
        {
            Console.TreatControlCAsInput = true;
        }

        public void RestoreConsoleInputMode(object modeObj)
        {
            if (modeObj is bool)
            {
                Console.TreatControlCAsInput = (bool)modeObj;
            }
        }

        public ConsoleKeyInfo ReadKey()
        {
            return Console.ReadKey(true);
        }

        public bool KeyAvailable
        {
            get { return Console.KeyAvailable; }
        }

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

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

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

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

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

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

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

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

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

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

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

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

        public void Write(string value)
        {
            Console.Write(value);
        }

        public void WriteLine(string value)
        {
            Console.WriteLine(value);
        }

        public void WriteBufferLines(BufferChar[] buffer, ref int top)
        {
            WriteBufferLines(buffer, ref top, true);
        }

        public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
        {
            int bufferWidth = Console.BufferWidth;
            int bufferLineCount = buffer.Length / bufferWidth;
            if ((top + bufferLineCount) > Console.BufferHeight)
            {
                var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
                ScrollBuffer(scrollCount);
                top -= scrollCount;
            }

            ConsoleColor foregroundColor = Console.ForegroundColor;
            ConsoleColor backgroundColor = Console.BackgroundColor;

            Console.SetCursorPosition(0, (top>=0) ? top : 0);

            var lastForegroundColor = (ConsoleColor)(-1);
            var lastBackgroundColor = (ConsoleColor)(-1);
            for (int i = 0; i < buffer.Length; ++i)
            {
                // TODO: use escape sequences for better perf
                var nextForegroundColor = buffer[i].ForegroundColor;
                var nextBackgroundColor = buffer[i].BackgroundColor;
                if (nextForegroundColor != lastForegroundColor)
                {
                    Console.ForegroundColor = lastForegroundColor = nextForegroundColor;
                }
                if (nextBackgroundColor != lastBackgroundColor)
                {
                    Console.BackgroundColor = lastBackgroundColor = nextBackgroundColor;
                }

                Console.Write(buffer[i].UnicodeChar);
            }

            if (foregroundColor != lastForegroundColor)
            {
                Console.ForegroundColor = foregroundColor;
            }
            if (backgroundColor != lastBackgroundColor)
            {
                Console.BackgroundColor = backgroundColor;
            }
        }

        public void ScrollBuffer(int lines)
        {
            for (int i=0; i<lines; ++i)
            {
                Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
                Console.WriteLine();
            }
        }

        public BufferChar[] ReadBufferLines(int top, int count)
        {
            var result = new BufferChar[BufferWidth * count];
            for (int i=0; i<BufferWidth*count; ++i)
            {
                result[i].UnicodeChar = ' ';
                result[i].ForegroundColor = Console.ForegroundColor;
                result[i].BackgroundColor = Console.BackgroundColor;
            }
            return result;
        }

        public int LengthInBufferCells(char c)
        {
            return 1;
        }

        public void StartRender()
        {
        }

        public void EndRender()
        {
        }

        // TODO: this is not correct, PSReadline never clears the screen, it should only scroll
        public void Clear()
        {
            Console.Clear();
        }
    }
#endif

#pragma warning restore 1591
}
