#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST
using System;
using Microsoft.Xna.Framework.Input;
using NUnit.Framework;
using NMock;
using Nuclex.UserInterface.Input;
using Nuclex.Input;
using Is = NUnit.Framework.Is;
namespace Nuclex.UserInterface {
  /// Unit Test for the Screen class
  [TestFixture]
  internal class ScreenTest {
    #region class DelegatingControl
    /// Control that delegates input to another input receiver
    public class DelegatingControl : Controls.Control {
      /// Initializes a new input delegating control
      /// Receiver to which the input is delegated
      public DelegatingControl(IInputReceiver receiver) {
        this.receiver = receiver;
      }
      /// Called when a command was sent to the control
      /// Command the control should perform
      /// Whether the command has been processed by the control
      protected override bool OnCommand(Command command) {
        this.receiver.InjectCommand(command);
        return true;
      }
      /// Called when the mouse position is updated
      /// X coordinate of the mouse cursor on the GUI
      /// Y coordinate of the mouse cursor on the GUI
      protected override void OnMouseMoved(float x, float y) {
        this.receiver.InjectMouseMove(x, y);
      }
      /// Called when a mouse button has been pressed down
      /// Index of the button that has been pressed
      protected override void OnMousePressed(MouseButtons button) {
        this.receiver.InjectMousePress(button);
      }
      /// Called when a mouse button has been released again
      /// Index of the button that has been released
      protected override void OnMouseReleased(MouseButtons button) {
        this.receiver.InjectMouseRelease(button);
      }
      /// Called when the mouse wheel has been rotated
      /// Number of ticks that the mouse wheel has been rotated
      protected override void OnMouseWheel(float ticks) {
        this.receiver.InjectMouseWheel(ticks);
      }
      /// Called when a key on the keyboard has been pressed down
      /// Code of the key that was pressed
      protected override bool OnKeyPressed(Keys keyCode) {
        this.receiver.InjectKeyPress(keyCode);
        return true;
      }
      /// Called when a key on the keyboard has been released again
      /// Code of the key that was released
      protected override void OnKeyReleased(Keys keyCode) {
        this.receiver.InjectKeyRelease(keyCode);
      }
      /// Input receiver all received input is delegated to
      private IInputReceiver receiver;
    }
    #endregion // class DelegatingControl
    #region class GamePadTestControl
    /// Control used to test game pad notifications
    private class GamePadTestControl : Controls.Control {
      /// Called when a button on the game pad has been pressed
      /// Button that has been pressed
      /// 
      ///   True if the button press was handled by the control, otherwise false.
      /// 
      protected override bool OnButtonPressed(Buttons button) {
        ++this.HeldButtonCount;
        base.OnButtonPressed(button);
        return true;
      }
      /// Called when a button on the game pad has been released
      /// Button that has been released
      protected override void OnButtonReleased(Buttons button) {
        --this.HeldButtonCount;
        base.OnButtonReleased(button);
      }
      /// Number of game pad buttons being held down
      public int HeldButtonCount;
    }
    #endregion // class GamePadTestControl
    #region class KeyboardTestControl
    /// Control used to test keyboard notifications
    private class KeyboardTestControl : Controls.Control {
      /// Called when a key on the keyboard has been pressed down
      /// Code of the key that was pressed
      /// 
      ///   True if the key press was handled by the control, otherwise false.
      /// 
      protected override bool OnKeyPressed(Keys keyCode) {
        ++this.HeldKeyCount;
        base.OnKeyPressed(keyCode);
        return true;
      }
      /// Called when a key on the keyboard has been released again
      /// Code of the key that was released
      protected override void OnKeyReleased(Keys keyCode) {
        --this.HeldKeyCount;
        base.OnKeyReleased(keyCode);
      }
      /// Number of keys being held down
      public int HeldKeyCount;
    }
    #endregion // class KeyboardTestControl
    #region class MouseTestControl
    /// Control used to test mouse notifications
    private class MouseTestControl : Controls.Control {
      /// Called when a key on the keyboard has been pressed down
      /// Code of the key that was pressed
      /// 
      ///   True if the key press was handled by the control, otherwise false.
      /// 
      protected override bool OnKeyPressed(Keys keyCode) { return true; }
      /// Called when a mouse button has been pressed down
      /// Index of the button that has been pressed
      /// Whether the control has processed the mouse press
      protected override void OnMousePressed(MouseButtons button) {
        this.HeldMouseButtons |= button;
        base.OnMousePressed(button);
      }
      /// Called when a mouse button has been released again
      /// Index of the button that has been released
      protected override void OnMouseReleased(MouseButtons button) {
        this.HeldMouseButtons &= ~button;
        base.OnMouseReleased(button);
      }
      /// Called when the mouse wheel has been rotated
      /// Number of ticks that the mouse wheel has been rotated
      protected override void OnMouseWheel(float ticks) {
        this.MouseWheelTicks += ticks;
        base.OnMouseWheel(ticks);
      }
      /// Mouse buttons being held down
      public MouseButtons HeldMouseButtons;
      /// Ticks the mouse wheel has been rotated by
      public float MouseWheelTicks;
    }
    #endregion // class GamePadTestControl
    #region class CommandTestControl
    /// Control for testing command routing
    private class CommandTestControl : Controls.Control {
      /// Called when an input command was sent to the control
      /// Input command that has been triggered
      /// Whether the command has been processed by the control
      protected override bool OnCommand(Command command) {
        this.LastCommand = command;
        return true;
      }
      public Command LastCommand;
    }
    #endregion // class CommandTestControl
    #region interface IFocusChangeSubscriber
    /// 
    ///   Interface for a subscriber to a Control's FocusChanged event
    /// 
    public interface IFocusChangeSubscriber {
      /// Called when the focused control has changed
      /// Screen that is reporting the focus change
      /// Contains the control that is now focused
      void FocusChanged(object sender, Controls.ControlEventArgs arguments);
    }
    #endregion // interface IFocusChangeSubscriber
    /// Initialization routine executed before each test is run
    [SetUp]
    public void Setup() {
      this.mockery = new MockFactory();
    }
    /// Called after each test has run
    [TearDown]
    public void Teardown() {
      if(this.mockery != null) {
        this.mockery.Dispose();
        this.mockery = null;
      }
    }
    /// Tests the default constructor of the screen class
    [Test]
    public void TestDefaultConstructor() {
      Screen myScreen = new Screen();
      Assert.That(myScreen.Width, Is.EqualTo(0.0f));
      Assert.That(myScreen.Height, Is.EqualTo(0.0f));
    }
    /// Tests the full featured constructor of the screen class
    [Test]
    public void TestFullConstructor() {
      Screen myScreen = new Screen(12.34f, 56.78f);
      Assert.That(myScreen.Width, Is.EqualTo(12.34f));
      Assert.That(myScreen.Height, Is.EqualTo(56.78f));
    }
    /// 
    ///   Verifies that an action input is processed by the screen
    /// 
    [Test]
    public void TestCommandProcessing() {
      Screen testScreen = new Screen();
      testScreen.InjectCommand(Command.Accept);
      testScreen.InjectCommand(Command.Cancel);
      // No exception means success
    }
    /// 
    ///   Tests whether the Gui reference is properly propagated to all controls
    ///   and their children in a control tree.
    /// 
    [Test]
    public void TestScreenPropagationOnInsertion() {
      Screen myScreen = new Screen();
      Controls.Control child = new Controls.Control();
      myScreen.Desktop.Children.Add(child);
      Assert.AreSame(myScreen, child.Screen);
    }
    /// 
    ///   Verifies that button presses are propagated down the control tree
    /// 
    [Test]
    public void TestInjectButtonPress() {
      Screen screen = new Screen();
      GamePadTestControl control = new GamePadTestControl();
      screen.Desktop.Children.Add(control);
      screen.InjectButtonPress(Buttons.A);
      Assert.AreEqual(1, control.HeldButtonCount);
    }
    /// 
    ///   Verifies that button press notifications are routed to the activated control
    ///   instead of searching for a control to handle the press
    /// 
    [Test]
    public void TestButtonPressWithActivatedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      GamePadTestControl child1 = new GamePadTestControl();
      GamePadTestControl child2 = new GamePadTestControl();
      child2.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f);
      screen.Desktop.Children.Add(child1);
      screen.Desktop.Children.Add(child2);
      // Click on child 2
      screen.InjectMouseMove(50.0f, 50.0f);
      screen.InjectMousePress(MouseButtons.Left);
      // Now child 2 should be receiving the input instead of child 1
      screen.InjectButtonPress(Buttons.A);
      Assert.AreEqual(0, child1.HeldButtonCount);
      Assert.AreEqual(1, child2.HeldButtonCount);
      screen.InjectButtonRelease(Buttons.A);
      Assert.AreEqual(0, child1.HeldButtonCount);
      Assert.AreEqual(0, child2.HeldButtonCount);
    }
    /// 
    ///   Verifies that button press notifications are sent to the focused control
    ///   first when looking the a control that handles the notification
    /// 
    [Test]
    public void TestButtonPressWithFocusedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      GamePadTestControl child1 = new GamePadTestControl();
      GamePadTestControl child2 = new GamePadTestControl();
      screen.Desktop.Children.Add(child1);
      screen.Desktop.Children.Add(child2);
      screen.FocusedControl = child2;
      screen.InjectButtonPress(Buttons.A);
      Assert.AreEqual(0, child1.HeldButtonCount);
      Assert.AreEqual(1, child2.HeldButtonCount);
      screen.InjectButtonRelease(Buttons.A);
      Assert.AreEqual(0, child1.HeldButtonCount);
      Assert.AreEqual(0, child2.HeldButtonCount);
    }
    /// 
    ///   Verifies that key presses are propagated down the control tree
    /// 
    [Test]
    public void TestInjectKeyPress() {
      Screen screen = new Screen();
      KeyboardTestControl control = new KeyboardTestControl();
      screen.Desktop.Children.Add(control);
      screen.InjectKeyPress(Keys.A);
      Assert.AreEqual(1, control.HeldKeyCount);
    }
    /// 
    ///   Verifies that key press notifications are routed to the activated control
    ///   instead of searching for a control to handle the press
    /// 
    [Test]
    public void TestKeyPressWithActivatedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      KeyboardTestControl child1 = new KeyboardTestControl();
      KeyboardTestControl child2 = new KeyboardTestControl();
      child2.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f);
      screen.Desktop.Children.Add(child1);
      screen.Desktop.Children.Add(child2);
      // Click on child 2
      screen.InjectMouseMove(50.0f, 50.0f);
      screen.InjectMousePress(MouseButtons.Left);
      // Now child 2 should be receiving the input instead of child 1
      screen.InjectKeyPress(Keys.A);
      Assert.AreEqual(0, child1.HeldKeyCount);
      Assert.AreEqual(1, child2.HeldKeyCount);
      screen.InjectKeyRelease(Keys.A);
      Assert.AreEqual(0, child1.HeldKeyCount);
      Assert.AreEqual(0, child2.HeldKeyCount);
    }
    /// 
    ///   Verifies that key press notifications are sent to the focused control
    ///   first when looking the a control that handles the notification
    /// 
    [Test]
    public void TestKeyPressWithFocusedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      KeyboardTestControl child1 = new KeyboardTestControl();
      KeyboardTestControl child2 = new KeyboardTestControl();
      screen.Desktop.Children.Add(child1);
      screen.Desktop.Children.Add(child2);
      screen.FocusedControl = child2;
      screen.InjectKeyPress(Keys.A);
      Assert.AreEqual(0, child1.HeldKeyCount);
      Assert.AreEqual(1, child2.HeldKeyCount);
      screen.InjectKeyRelease(Keys.A);
      Assert.AreEqual(0, child1.HeldKeyCount);
      Assert.AreEqual(0, child2.HeldKeyCount);
    }
    /// 
    ///   Verifies that mouse pressed are routed to the activated control
    /// 
    [Test]
    public void TestMousePressWithActivatedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      MouseTestControl child = new MouseTestControl();
      child.Bounds = new UniRectangle(55.0f, 10.0f, 35.0f, 80.0f);
      screen.Desktop.Children.Add(child);
      screen.InjectKeyPress(Keys.A);
      screen.InjectMousePress(MouseButtons.Left);
      Assert.AreEqual(MouseButtons.Left, child.HeldMouseButtons);
    }
    /// 
    ///   Verifies that the FocusChanged event is triggered when the control
    ///   in focus changes
    /// 
    [Test]
    public void TestFocusChangeEvent() {
      Screen screen = new Screen(100.0f, 100.0f);
      Mock mockedSubscriber = mockSubscriber(screen);
      Controls.Control child1 = new Controls.Control();
      Controls.Control child2 = new Controls.Control();
      screen.Desktop.Children.Add(child1);
      screen.Desktop.Children.Add(child2);
      mockedSubscriber.Expects.One.Method(m => m.FocusChanged(null, null)).WithAnyArguments();
      screen.FocusedControl = child1;
      mockedSubscriber.Expects.One.Method(m => m.FocusChanged(null, null)).WithAnyArguments();
      screen.FocusedControl = child2;
      this.mockery.VerifyAllExpectationsHaveBeenMet();
    }
    /// 
    ///   Verifies that mouse wheel rotations are propagated down the control tree
    /// 
    [Test]
    public void TestMouseWheel() {
      Screen screen = new Screen();
      MouseTestControl control = new MouseTestControl();
      control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f);
      screen.Desktop.Children.Add(control);
      screen.InjectMouseMove(50.0f, 50.0f);
      screen.InjectMouseWheel(12.34f);
      Assert.AreEqual(12.34f, control.MouseWheelTicks);
    }
    /// 
    ///   Verifies that mouse wheel rotations are sent to the activated control first
    /// 
    [Test]
    public void TestMouseWheelWithActivatedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      MouseTestControl control = new MouseTestControl();
      control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f);
      screen.Desktop.Children.Add(control);
      screen.InjectKeyPress(Keys.A);
      screen.InjectMouseWheel(12.34f);
      Assert.AreEqual(12.34f, control.MouseWheelTicks);
    }
    /// 
    ///   Verifies that entered characters are sent to the focused control
    /// 
    [Test]
    public void TestInjectCharacter() {
      Screen screen = new Screen(100.0f, 100.0f);
      Controls.Desktop.InputControl control = new Controls.Desktop.InputControl();
      screen.Desktop.Children.Add(control);
      screen.InjectCharacter('a');
      Assert.AreEqual(string.Empty, control.Text);
      screen.FocusedControl = control;
      screen.InjectCharacter('a');
      Assert.AreEqual("a", control.Text);
    }
    /// Tests whether focus can be changed using the keyboard
    [Test]
    public void TestFocusSwitching() {
      Screen screen = new Screen(100.0f, 100.0f);
      Controls.Desktop.ButtonControl center = new Controls.Desktop.ButtonControl();
      center.Bounds = new UniRectangle(40, 40, 20, 20);
      Controls.Desktop.ButtonControl left = new Controls.Desktop.ButtonControl();
      left.Bounds = new UniRectangle(10, 51, 20, 20);
      Controls.Desktop.ButtonControl right = new Controls.Desktop.ButtonControl();
      right.Bounds = new UniRectangle(70, 29, 20, 20);
      Controls.Desktop.ButtonControl up = new Controls.Desktop.ButtonControl();
      up.Bounds = new UniRectangle(29, 10, 20, 20);
      Controls.Desktop.ButtonControl down = new Controls.Desktop.ButtonControl();
      down.Bounds = new UniRectangle(51, 70, 20, 20);
      screen.Desktop.Children.Add(center);
      screen.Desktop.Children.Add(left);
      screen.Desktop.Children.Add(right);
      screen.Desktop.Children.Add(up);
      screen.Desktop.Children.Add(down);
      screen.FocusedControl = center;
      screen.InjectKeyPress(Keys.Down);
      Assert.AreSame(down, screen.FocusedControl);
      screen.FocusedControl = center;
      screen.InjectKeyPress(Keys.Up);
      Assert.AreSame(up, screen.FocusedControl);
      screen.FocusedControl = center;
      screen.InjectKeyPress(Keys.Left);
      Assert.AreSame(left, screen.FocusedControl);
      screen.FocusedControl = center;
      screen.InjectKeyPress(Keys.Right);
      Assert.AreSame(right, screen.FocusedControl);
    }
    /// 
    ///   Verifies that the screen skips an unfocusable control and jumps to the
    ///   next focusable control.
    /// 
    [Test]
    public void TestFocusSwitchingWithUnfocusableControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      Controls.Desktop.ButtonControl one = new Controls.Desktop.ButtonControl();
      one.Bounds = new UniRectangle(40, 10, 20, 20);
      Controls.Control two = new Controls.Control();
      two.Bounds = new UniRectangle(40, 40, 20, 20);
      Controls.Desktop.ButtonControl three = new Controls.Desktop.ButtonControl();
      three.Bounds = new UniRectangle(40, 70, 20, 20);
      screen.Desktop.Children.Add(one);
      screen.Desktop.Children.Add(two);
      screen.Desktop.Children.Add(three);
      screen.FocusedControl = one;
      screen.InjectKeyPress(Keys.Down);
      screen.InjectKeyRelease(Keys.Down);
      Assert.AreSame(three, screen.FocusedControl);
    }
    /// 
    ///   Verifies that if the focused control handles a directional command, no
    ///   focus switching will occur
    /// 
    [Test]
    public void TestNoFocusChangeOnHandledDirectionalCommand() {
      Screen screen = new Screen(100.0f, 100.0f);
      Mock mockedReceiver = mockReceiver(screen);
      DelegatingControl one = new DelegatingControl(mockedReceiver.MockObject);
      one.Bounds = new UniRectangle(40, 10, 20, 20);
      Controls.Desktop.ButtonControl two = new Controls.Desktop.ButtonControl();
      two.Bounds = new UniRectangle(40, 40, 20, 20);
      screen.Desktop.Children.Add(one);
      screen.Desktop.Children.Add(two);
      mockedReceiver.Expects.One.MethodWith(m => m.InjectCommand(Command.Down));
      screen.FocusedControl = one;
      screen.InjectCommand(Command.Down);
      Assert.AreSame(one, screen.FocusedControl);
      this.mockery.VerifyAllExpectationsHaveBeenMet();
    }
    /// 
    ///   Verifies that the screen can handle a focus switch request without any
    ///   focused control
    /// 
    [Test]
    public void TestFocusSwitchingWithoutFocusedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      screen.InjectCommand(Command.Down);
      // No exception means success
    }
    /// 
    ///   Verifies that the screen can handle a focus switch when the control
    ///   currently assigned as the focused control has been disconnected from the tree
    /// 
    [Test]
    public void TestFocusSwitchingWithDisconnectedControl() {
      Screen screen = new Screen(100.0f, 100.0f);
      screen.FocusedControl = new Controls.Control();
      screen.InjectCommand(Command.Down);
      // No exception means success
    }
    /// 
    ///   Verifies that classic focus switching is still supported
    /// 
    [Test]
    public void TestClassicFocusSwitching() {
      Screen screen = new Screen(100.0f, 100.0f);
      screen.InjectCommand(Command.SelectPrevious);
      screen.InjectCommand(Command.SelectNext);
      // TODO: Implement classic focus switching and do some real testing here
    }
    /// 
    ///   Verifies that classic focus switching is still supported
    /// 
    [Test]
    public void TestAcceptAndCancelFromKeyboard() {
      Screen screen = new Screen(100.0f, 100.0f);
      CommandTestControl test = new CommandTestControl();
      screen.Desktop.Children.Add(test);
      screen.FocusedControl = test;
      screen.InjectKeyPress(Keys.Escape);
      Assert.AreEqual(Command.Cancel, test.LastCommand);
      screen.InjectKeyPress(Keys.Enter);
      Assert.AreEqual(Command.Accept, test.LastCommand);
    }
    /// Mocks a receiver for the input processing of a control
    /// Screen to mock an input receiver on
    /// The mocked input receiver
    private Mock mockReceiver(Screen screen) {
      Mock mockedReceiver = this.mockery.CreateMock();
      DelegatingControl delegatingControl = new DelegatingControl(mockedReceiver.MockObject);
      screen.Desktop.Children.Add(delegatingControl);
      screen.FocusedControl = delegatingControl;
      return mockedReceiver;
    }
    /// Mocks a subscriber to the screen's events
    /// Screen to mock an event subcriber to
    /// The mocked event subscriber
    private Mock mockSubscriber(Screen screen) {
      Mock mockedSubscriber =
        this.mockery.CreateMock();
      screen.FocusChanged += new EventHandler(
        mockedSubscriber.MockObject.FocusChanged
      );
      return mockedSubscriber;
    }
    /// Mock object factory
    private MockFactory mockery;
  }
} // namespace Nuclex.UserInterface
#endif // UNITTEST