use webcore::value::Reference;
use webcore::try_from::TryInto;
use webcore::reference_type::ReferenceType;
use webapi::event_target::EventTarget;
use webapi::window::Window;

/// The `IEvent` interface represents any event which takes place in the DOM; some
/// are user-generated (such as mouse or keyboard events), while others are
/// generated by APIs (such as events that indicate an animation has finished
/// running, a video has been paused, and so forth). There are many types of event,
/// some of which use other interfaces based on the main `IEvent` interface. `IEvent`
/// itself contains the properties and methods which are common to all events.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event)
// https://dom.spec.whatwg.org/#event
pub trait IEvent: ReferenceType {
    /// Indicates whether this event bubbles upward through the DOM.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-bubbles%E2%91%A0
    #[inline]
    fn bubbles( &self ) -> bool {
        js!(
            return @{self.as_ref()}.bubbles;
        ).try_into().unwrap()
    }

    /// A historical alias to `Event.stopPropagation()`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelBubble)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-cancelbubble
    #[inline]
    fn cancel_bubble( &self ) -> bool {
        js!(
            return @{self.as_ref()}.cancelBubble;
        ).try_into().unwrap()
    }

    /// A historical alias to `Event.stopPropagation()`.
    /// Setting this to `true` before returning from an event handler will stop propagation
    /// of the event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelBubble)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-cancelbubble
    #[inline]
    fn set_cancel_bubble( &self, value: bool ) {
        js! { @(no_return)
            @{self.as_ref()}.cancelBubble = @{value};
        }
    }

    /// Indicates whether the event is cancelable.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelable)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-cancelable
    #[inline]
    fn cancelable( &self ) -> bool {
        js!(
            return @{self.as_ref()}.cancelable;
        ).try_into().unwrap()
    }

    /// A reference to the currently registered target of this event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-currenttarget%E2%91%A0
    #[inline]
    fn current_target( &self ) -> Option< EventTarget > {
        js!(
            return @{self.as_ref()}.currentTarget;
        ).try_into().ok()
    }

    /// Indicates whether `preventDefault` has been called on this event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/defaultPrevented)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-defaultprevented
    #[inline]
    fn default_prevented( &self ) -> bool {
        js!(
            return @{self.as_ref()}.defaultPrevented;
        ).try_into().unwrap()
    }

    /// Indicates which phase of event flow is currently being evaluated.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-eventphase%E2%91%A1
    fn event_phase( &self ) -> EventPhase {
        match js!(
            return @{self.as_ref()}.eventPhase;
        ).try_into().unwrap() {
            0 => EventPhase::None,
            1 => EventPhase::Capturing,
            2 => EventPhase::AtTarget,
            3 => EventPhase::Bubbling,
            _ => unreachable!("Unexpected EventPhase type"),
        }
    }

    /// Prevents any further listeners from being called for this event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation)
    #[inline]
    fn stop_immediate_propagation( &self ) {
        js! { @(no_return)
            @{self.as_ref()}.stopImmediatePropagation();
        }
    }

    /// Stops the propagation of this event to descendants in the DOM.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-stopimmediatepropagation
    #[inline]
    fn stop_propagation( &self ) {
        js! { @(no_return)
            @{self.as_ref()}.stopPropagation();
        }
    }


    /// Returns a reference to the target to which this event was originally registered.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/target)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-target%E2%91%A1
    #[inline]
    fn target( &self ) -> Option< EventTarget > {
        js!(
            return @{self.as_ref()}.target;
        ).try_into().ok()
    }

    /// Returns the time in milliseconds at which this event was created.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-timestamp
    #[inline]
    fn time_stamp( &self ) -> Option< f64 > {
        js!(
            return @{self.as_ref()}.timeStamp;
        ).try_into().ok()
    }

    /// Indicates whether the event was generated by a user action.
    // https://dom.spec.whatwg.org/#ref-for-dom-event-istrusted
    #[inline]
    fn is_trusted( &self ) -> bool {
        js!(
            return @{self.as_ref()}.isTrusted;
        ).try_into().unwrap()
    }

    /// Returns a string containing the type of event. It is set when
    /// the event is constructed and is the name commonly used to refer
    /// to the specific event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/type)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-type%E2%91%A1
    #[inline]
    fn event_type( &self ) -> String {
        js!(
            return @{self.as_ref()}.type;
        ).try_into().unwrap()
    }

    /// Cancels the event if it is cancelable, without
    /// stopping further propagation of the event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
    // https://dom.spec.whatwg.org/#ref-for-dom-event-preventdefault%E2%91%A0
    #[inline]
    fn prevent_default( &self ) {
        js! { @(no_return)
            @{self.as_ref()}.preventDefault();
        }
    }
}

/// Indicates the phase of event flow during event proessing.
// https://dom.spec.whatwg.org/#dom-event-eventphase
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EventPhase {
    /// No event is currently being processed.
    None,
    /// The event is being propagated down through the target's ancestors.
    Capturing,
    /// The target is currently processing the event.
    AtTarget,
    /// The event is propagating back up through the target's ancestors.
    Bubbling,
}

/// A trait representing a concrete event type.
pub trait ConcreteEvent: IEvent {
    /// A string representing the event type.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event/type)
    const EVENT_TYPE: &'static str;
}

/// A reference to a JavaScript object which implements the [IEvent](trait.IEvent.html)
/// interface.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Event)
// https://dom.spec.whatwg.org/#event
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "Event")]
pub struct Event( Reference );

impl IEvent for Event {}

/// The `BeforeUnload` event fires when the window is about to be unloaded (to close the page or
/// follow a link). If the event propagation is cancelled, the browser will present the user with
/// a confirmation dialog allowing them to stay on the page or continue.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event)
// https://html.spec.whatwg.org/multipage/indices.html#event-beforeunload
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "BeforeUnloadEvent")]
#[reference(event = "beforeunload")]
pub struct BeforeUnloadEvent( Reference );

impl IEvent for BeforeUnloadEvent {}


/// The `Unload` event fires when the window has been unloaded and is no longer visible. This event
/// can't be cancelled.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event)
// https://html.spec.whatwg.org/multipage/indices.html#event-unload
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "UnloadEvent")]
#[reference(event = "unload")]
pub struct UnloadEvent( Reference );

impl IEvent for UnloadEvent {}

/// The 'FullscreenChange' event fires when an element enters or exits fullscreen
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event)
// https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenchange
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "Event")]
#[reference(event = "fullscreenchange")]
pub struct FullscreenChangeEvent( Reference );

impl IEvent for FullscreenChangeEvent {}

/// The `IUiEvent` interface represents simple user interface events.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent)
// https://w3c.github.io/uievents/#uievent
pub trait IUiEvent: IEvent {
    /// Provides the current click count for this event, if applicable.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail)
    // https://w3c.github.io/uievents/#dom-uievent-detail
    #[inline]
    fn detail( &self ) -> i32 {
        js!(
            return @{self.as_ref()}.detail;
        ).try_into().unwrap()
    }

    /// Returns the `WindowProxy` that generated the event.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view)
    // https://w3c.github.io/uievents/#dom-uievent-view
    #[inline]
    fn view( &self ) -> Option< Window > {
        js!(
            return @{self.as_ref()}.view;
        ).try_into().ok()
    }
}

/// A reference to a JavaScript object which implements the [IUiEvent](trait.IUiEvent.html)
/// interface.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent)
// https://w3c.github.io/uievents/#uievent
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "UIEvent")]
#[reference(subclass_of(Event))]
pub struct UiEvent( Reference );

impl IEvent for UiEvent {}
impl IUiEvent for UiEvent {}

#[cfg(all(test, feature = "web_test"))]
mod tests {
    use super::*;

    #[test]
    fn test_event() {
        let event: Event = js!(
            return new Event("dummy")
        ).try_into().unwrap();

        assert_eq!( event.event_type(), "dummy" );
        assert_eq!( event.bubbles(), false );
        assert!( !event.cancel_bubble() );
        assert!( !event.cancelable(), false );
        assert!( event.current_target().is_none() );
        assert!( !event.default_prevented() );
        assert_eq!( event.event_phase(), EventPhase::None );
        assert!( event.target().is_none() );
        assert!( event.time_stamp().is_some() );
        assert!( !event.is_trusted() );

        event.stop_immediate_propagation();
        event.stop_propagation();
    }

    #[test]
    fn test_ui_event() {
        use webapi::events::mouse::ClickEvent;

        let event: UiEvent = js!(
            return new UIEvent(
                @{ClickEvent::EVENT_TYPE},
                {
                    detail: 1,
                }
            )
        ).try_into().unwrap();
        assert_eq!( event.event_type(), ClickEvent::EVENT_TYPE );
        assert_eq!( event.detail(), 1 );
        assert!( event.view().is_none() );
    }
}
