//----------------------------------------------------------------------------------------------------------------------
// <copyright file="ViewportArea.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>Wrapper and helper for instantiating and interacting with the main text region (viewport area) of the console.</summary>
//----------------------------------------------------------------------------------------------------------------------
namespace Conhost.UIA.Tests.Elements
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;

    using OpenQA.Selenium.Appium;

    using WEX.Logging.Interop;
    using WEX.TestExecution;

    using Conhost.UIA.Tests.Common;
    using Conhost.UIA.Tests.Common.NativeMethods;
    using OpenQA.Selenium;

    public class ViewportArea : IDisposable
    {
        private CmdApp app;
        private Point clientTopLeft;
        private Size sizeFont;

        private ViewportStates state;

        public enum ViewportStates
        {
            Normal,
            Mark, // keyboard selection
            Select, // mouse selection
            Scroll
        }

        public ViewportArea(CmdApp app)
        {
            this.app = app;

            this.state = ViewportStates.Normal;

            this.InitializeFont();
            this.InitializeWindow();
        }

        ~ViewportArea()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Don't need to dispose of anything now, but this helps maintain the pattern used by other controls.
        }

        private void InitializeFont()
        {
            AutoHelpers.LogInvariant("Initializing font data for viewport area...");
            this.sizeFont = new Size();

            IntPtr hCon = app.GetStdOutHandle();
            Verify.IsNotNull(hCon, "Check that we obtained the console output handle.");

            WinCon.CONSOLE_FONT_INFO cfi = new WinCon.CONSOLE_FONT_INFO();
            NativeMethods.Win32BoolHelper(WinCon.GetCurrentConsoleFont(hCon, false, out cfi), "Attempt to get current console font.");

            this.sizeFont.Width = cfi.dwFontSize.X;
            this.sizeFont.Height = cfi.dwFontSize.Y;
            AutoHelpers.LogInvariant("Font size is X:{0} Y:{1}", this.sizeFont.Width, this.sizeFont.Height);
        }

        private void InitializeWindow()
        {
            AutoHelpers.LogInvariant("Initializing window data for viewport area...");

            IntPtr hWnd = app.GetWindowHandle();

            User32.RECT lpRect;
            User32.GetClientRect(hWnd, out lpRect);

            int style = User32.GetWindowLong(hWnd, User32.GWL_STYLE);
            int exStyle = User32.GetWindowLong(hWnd, User32.GWL_EXSTYLE);

            Verify.IsTrue(User32.AdjustWindowRectEx(ref lpRect, style, false, exStyle));

            this.clientTopLeft = new Point();
            this.clientTopLeft.X = Math.Abs(lpRect.left);
            this.clientTopLeft.Y = Math.Abs(lpRect.top);
            AutoHelpers.LogInvariant("Top left corner of client area is at X:{0} Y:{1}", this.clientTopLeft.X, this.clientTopLeft.Y);
        }

        public void ExitModes()
        {
            app.UIRoot.SendKeys(Keys.Escape);
            this.state = ViewportStates.Normal;
        }

        public void EnterMode(ViewportStates state)
        {
            if (state == ViewportStates.Normal)
            {
                ExitModes();
                return;
            }

            var titleBar = app.UIRoot.FindElementByAccessibilityId("TitleBar");
            app.Session.Mouse.ContextClick(titleBar.Coordinates);

            Globals.WaitForTimeout();
            var contextMenu = app.Session.FindElementByClassName(Globals.PopupMenuClassId);

            var editButton = contextMenu.FindElementByName("Edit");

            editButton.Click();
            Globals.WaitForTimeout();

            Globals.WaitForTimeout();

            AppiumWebElement subMenuButton;
            switch (state)
            {
                case ViewportStates.Mark:
                    subMenuButton = app.Session.FindElementByName("Mark");
                    break;
                default:
                    throw new NotImplementedException(AutoHelpers.FormatInvariant("Set Mode doesn't yet support type of '{0}'", state.ToString()));
            }

            subMenuButton.Click();
            Globals.WaitForTimeout();

            this.state = state;
        }


        // Accepts Point in characters. Will convert to pixels and move to the right location relative to this viewport.
        public void MouseMove(Point pt)
        {
            Log.Comment($"Character position {pt.X}, {pt.Y}");

            Point modPoint = pt;
            ConvertCharacterOffsetToPixelPosition(ref modPoint);

            Log.Comment($"Pixel position {modPoint.X}, {modPoint.Y}");

            app.Session.Mouse.MouseMove(app.UIRoot.Coordinates, modPoint.X, modPoint.Y);
        }

        public void MouseDown()
        {
            app.Session.Mouse.MouseDown(null);
        }

        public void MouseUp()
        {
            app.Session.Mouse.MouseUp(null);
        }

        private void ConvertCharacterOffsetToPixelPosition(ref Point pt)
        {
            // Scale by pixel count per character
            pt.X *= this.sizeFont.Width;
            pt.Y *= this.sizeFont.Height;

            // Move it to center of character
            pt.X += this.sizeFont.Width / 2;
            pt.Y += this.sizeFont.Height / 2;

            // Adjust to the top left corner of the client rectangle.
            pt.X += this.clientTopLeft.X;
            pt.Y += this.clientTopLeft.Y;
        }

        public WinCon.CHAR_INFO GetCharInfoAt(IntPtr handle, Point pt)
        {
            Size size = new Size(1, 1);
            Rectangle rect = new Rectangle(pt, size);

            WinCon.CHAR_INFO[,] data = GetCharInfoInRectangle(handle, rect);

            return data[0, 0];
        }

        public WinCon.CHAR_INFO[,] GetCharInfoInRectangle(IntPtr handle, Rectangle rect)
        {
            WinCon.SMALL_RECT readRectangle = new WinCon.SMALL_RECT();
            readRectangle.Top = (short)rect.Top;
            readRectangle.Bottom = (short)(rect.Bottom - 1);
            readRectangle.Left = (short)rect.Left;
            readRectangle.Right = (short)(rect.Right - 1);

            WinCon.COORD dataBufferSize = new WinCon.COORD();
            dataBufferSize.X = (short)rect.Width;
            dataBufferSize.Y = (short)rect.Height;

            WinCon.COORD dataBufferPos = new WinCon.COORD();
            dataBufferPos.X = 0;
            dataBufferPos.Y = 0;

            WinCon.CHAR_INFO[,] data = new WinCon.CHAR_INFO[dataBufferSize.Y, dataBufferSize.X];

            NativeMethods.Win32BoolHelper(WinCon.ReadConsoleOutput(handle, data, dataBufferSize, dataBufferPos, ref readRectangle), string.Format("Attempting to read rectangle (L: {0}, T: {1}, R: {2}, B: {3}) from output buffer.", readRectangle.Left, readRectangle.Top, readRectangle.Right, readRectangle.Bottom));

            return data;
        }

        public IEnumerable<string> GetLinesInRectangle(IntPtr handle, Rectangle rect)
        {
            WinCon.CHAR_INFO[,] data = GetCharInfoInRectangle(handle, rect);
            List<string> lines = new List<string>();

            for (int row = 0; row < data.GetLength(0); row++)
            {
                StringBuilder builder = new StringBuilder();

                for (int col = 0; col < data.GetLength(1); col++)
                {
                    char z = data[row, col].UnicodeChar;
                    builder.Append(z);
                }

                lines.Add(builder.ToString());
            }

            return lines;
        }
    }
}