//! Actions tests
use crate::common::sample_page_url;
use fantoccini::actions::{
    Actions, InputSource, KeyAction, KeyActions, MouseActions, NullActions, PointerAction,
    MOUSE_BUTTON_LEFT,
};
use fantoccini::key::Key;
use fantoccini::{error, Client, Locator};
use serial_test::serial;
use std::time::{Duration, Instant};

mod common;

async fn actions_null(c: Client, port: u16) -> Result<(), error::CmdError> {
    let sample_url = sample_page_url(port);
    c.goto(&sample_url).await?;
    let null_actions = NullActions::new("null".to_string()).pause(Duration::from_secs(1));
    let now = Instant::now();
    c.perform_actions(null_actions).await?;
    assert!(now.elapsed().as_secs_f64() >= 1.0);
    Ok(())
}

async fn actions_key(c: Client, port: u16) -> Result<(), error::CmdError> {
    let sample_url = sample_page_url(port);
    c.goto(&sample_url).await?;

    // Test pause.
    let key_pause = KeyActions::new("key".to_string()).pause(Duration::from_secs(1));
    let now = Instant::now();
    c.perform_actions(key_pause).await?;
    assert!(now.elapsed().as_secs_f64() >= 1.0);

    // Test key down/up.
    let elem = c.find(Locator::Id("text-input")).await?;
    elem.send_keys("a").await?;
    assert_eq!(elem.prop("value").await?.unwrap(), "a");

    let key_actions = KeyActions::new("key".to_string())
        .then(KeyAction::Down {
            value: Key::Backspace.into(),
        })
        .then(KeyAction::Up {
            value: Key::Backspace.into(),
        });
    elem.click().await?;
    c.perform_actions(key_actions).await?;
    let elem = c.find(Locator::Id("text-input")).await?;
    assert_eq!(elem.prop("value").await?.unwrap(), "");
    Ok(())
}

async fn actions_mouse(c: Client, port: u16) -> Result<(), error::CmdError> {
    let sample_url = sample_page_url(port);
    c.goto(&sample_url).await?;

    // Test pause.
    let mouse_pause = MouseActions::new("mouse".to_string()).pause(Duration::from_secs(1));
    let now = Instant::now();
    c.perform_actions(mouse_pause).await?;
    assert!(now.elapsed().as_secs_f64() >= 1.0);

    let elem = c.find(Locator::Id("button-alert")).await?;

    // Test mouse down/up.
    let mouse_actions = MouseActions::new("mouse".to_string())
        .then(PointerAction::MoveToElement {
            element: elem,
            duration: None,
            x: 0.,
            y: 0.,
        })
        .then(PointerAction::Down {
            button: MOUSE_BUTTON_LEFT,
        })
        .then(PointerAction::Up {
            button: MOUSE_BUTTON_LEFT,
        });

    c.perform_actions(mouse_actions).await?;
    assert_eq!(c.get_alert_text().await?, "This is an alert");
    c.dismiss_alert().await?;
    Ok(())
}

async fn actions_mouse_move(c: Client, port: u16) -> Result<(), error::CmdError> {
    // Set window size to avoid moving the cursor out-of-bounds during actions.
    c.set_window_rect(0, 0, 800, 800).await?;

    let sample_url = sample_page_url(port);
    c.goto(&sample_url).await?;

    let elem = c.find(Locator::Id("button-alert")).await?;
    let rect = elem.rectangle().await?;
    let elem_center_x = rect.0 + (rect.2 / 2.0);
    let elem_center_y = rect.1 + (rect.3 / 2.0);

    // Test mouse MoveBy.
    let mouse_actions = MouseActions::new("mouse".to_string())
        // Move to a position at a known offset from the button.
        .then(PointerAction::MoveTo {
            duration: None,
            x: 0.,
            y: elem_center_y - 100.,
        })
        // Now move by relative offset so that the cursor is now over the button.
        .then(PointerAction::MoveBy {
            duration: None,
            x: elem_center_x,
            y: 100.,
        })
        // Press left mouse button down.
        .then(PointerAction::Down {
            button: MOUSE_BUTTON_LEFT,
        })
        // Release left mouse button.
        .then(PointerAction::Up {
            button: MOUSE_BUTTON_LEFT,
        });

    // Sanity check - ensure no alerts are displayed prior to actions.
    assert!(matches!(
        c.get_alert_text().await,
        Err(e) if e.is_no_such_alert()
    ));

    let actions = Actions::from(mouse_actions);
    c.perform_actions(actions).await?;
    assert_eq!(c.get_alert_text().await?, "This is an alert");
    c.accept_alert().await?;

    Ok(())
}

async fn actions_release(c: Client, port: u16) -> Result<(), error::CmdError> {
    let sample_url = sample_page_url(port);
    c.goto(&sample_url).await?;

    // Focus the input element.
    let elem = c.find(Locator::Id("text-input")).await?;
    elem.click().await?;

    // Add initial text.
    let elem = c.find(Locator::Id("text-input")).await?;
    assert_eq!(elem.prop("value").await?.unwrap(), "");

    // Press CONTROL key down and hold it.
    let key_actions = KeyActions::new("key".to_string()).then(KeyAction::Down {
        value: Key::Control.into(),
    });
    c.perform_actions(key_actions).await?;

    // Now release all actions. This should release the control key.
    c.release_actions().await?;

    // Now press the 'a' key again.
    //
    // If the Control key was not released, this would do `Ctrl+a` (i.e. select all)
    // but there is no text so it would do nothing.
    //
    // However if the Control key was released (as expected)
    // then this will type 'a' into the text element.
    let key_actions = KeyActions::new("key".to_string()).then(KeyAction::Down { value: 'a' });
    c.perform_actions(key_actions).await?;
    assert_eq!(elem.prop("value").await?.unwrap(), "a");
    Ok(())
}

mod firefox {
    use super::*;

    #[test]
    #[serial]
    fn actions_null_test() {
        local_tester!(actions_null, "firefox");
    }

    #[test]
    #[serial]
    fn actions_key_test() {
        local_tester!(actions_key, "firefox");
    }

    #[test]
    #[serial]
    fn actions_mouse_test() {
        local_tester!(actions_mouse, "firefox");
    }

    #[test]
    #[serial]
    fn actions_mouse_move_test() {
        local_tester!(actions_mouse_move, "firefox");
    }

    #[test]
    #[serial]
    fn actions_release_test() {
        local_tester!(actions_release, "firefox");
    }
}

mod chrome {
    use super::*;

    #[test]
    #[serial]
    fn actions_null_test() {
        local_tester!(actions_null, "chrome");
    }

    #[test]
    #[serial]
    fn actions_key_test() {
        local_tester!(actions_key, "chrome");
    }

    #[test]
    #[serial]
    fn actions_mouse_test() {
        local_tester!(actions_mouse, "chrome");
    }

    #[test]
    #[serial]
    fn actions_mouse_move_test() {
        local_tester!(actions_mouse_move, "chrome");
    }

    #[test]
    #[serial]
    fn actions_release_test() {
        local_tester!(actions_release, "chrome");
    }
}
