//! A progress indicator widget.
//!
//! This control wraps `NSProgressIndicator` on macOS, and 
//! `UIProgressView+UIActivityIndicatorView` on iOS and tvOS. It operates in two modes: determinate
//! (where you have a fixed start and end) and indeterminate (infinite; it will go and go until you
//! tell it to stop).
//!
//! ```rust,no_run
//! let indicator = ProgressIndicator::new();
//! indicator.set_indeterminate(true);
//! my_view.add_subview(&indicator);
//! ```

use core_graphics::base::CGFloat;

use objc_id::ShareId;
use objc::runtime::{Class, Object};
use objc::{class, msg_send, sel, sel_impl};

use crate::foundation::{id, nil, YES, NO, NSUInteger};
use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};

#[cfg(target_os = "ios")]
mod ios;

#[cfg(target_os = "ios")]
use ios::register_progress_indicator_class;

mod enums;
pub use enums::ProgressIndicatorStyle;

/// A control used for reporting progress to a user visually.
#[derive(Debug)]
pub struct ProgressIndicator {
    /// A pointer to the Objective-C Object.
    pub objc: ShareId<Object>,
    
    /// A pointer to the Objective-C runtime top layout constraint.
    pub top: LayoutAnchorY,

    /// A pointer to the Objective-C runtime leading layout constraint.
    pub leading: LayoutAnchorX,

    /// A pointer to the Objective-C runtime left layout constraint.
    pub left: LayoutAnchorX,

    /// A pointer to the Objective-C runtime trailing layout constraint.
    pub trailing: LayoutAnchorX,

    /// A pointer to the Objective-C runtime right layout constraint.
    pub right: LayoutAnchorX,

    /// A pointer to the Objective-C runtime bottom layout constraint.
    pub bottom: LayoutAnchorY,

    /// A pointer to the Objective-C runtime width layout constraint.
    pub width: LayoutAnchorDimension,

    /// A pointer to the Objective-C runtime height layout constraint.
    pub height: LayoutAnchorDimension,

    /// A pointer to the Objective-C runtime center X layout constraint.
    pub center_x: LayoutAnchorX,

    /// A pointer to the Objective-C runtime center Y layout constraint.
    pub center_y: LayoutAnchorY
}

impl Default for ProgressIndicator {
    fn default() -> Self {
        ProgressIndicator::new()
    }
}

impl ProgressIndicator {
    /// Returns a default `ProgressIndicator`. You should retain this yourself for as long as you
    /// need it to stay around.
    pub fn new() -> Self {
        let view = unsafe {
            #[cfg(feature = "macos")]
            let view: id = msg_send![class!(NSProgressIndicator), new];
            let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];

            #[cfg(feature = "macos")]
            let _: () = msg_send![view, setWantsLayer:YES];

            view
        };

        ProgressIndicator {
            top: LayoutAnchorY::top(view),
            left: LayoutAnchorX::left(view),
            leading: LayoutAnchorX::leading(view),
            right: LayoutAnchorX::right(view),
            trailing: LayoutAnchorX::trailing(view),
            bottom: LayoutAnchorY::bottom(view),
            width: LayoutAnchorDimension::width(view),
            height: LayoutAnchorDimension::height(view),
            center_x: LayoutAnchorX::center(view),
            center_y: LayoutAnchorY::center(view),
            objc: unsafe { ShareId::from_ptr(view) },
        }
    }
}

impl ProgressIndicator {
    /// Starts the animation for an indeterminate indicator.
    pub fn start_animation(&self) {
        unsafe {
            let _: () = msg_send![&*self.objc, startAnimation:nil];
        }
    }

    /// Stops any animations that are currently happening on this indicator (e.g, if it's an
    /// indeterminate looping animation).
    pub fn stop_animation(&self) {
        unsafe {
            let _: () = msg_send![&*self.objc, stopAnimation:nil];
        }
    }

    /// Increment the progress indicator by the amount specified.
    pub fn increment(&self, amount: f64) {
        unsafe {
            let _: () = msg_send![&*self.objc, incrementBy:amount];
        }
    }

    /// Set the style for the progress indicator.
    pub fn set_style(&self, style: ProgressIndicatorStyle) {
        unsafe {
            let style = style as NSUInteger;
            let _: () = msg_send![&*self.objc, setStyle:style];
        }
    }

    /// Set whether this is an indeterminate indicator or not. Indeterminate indicators are
    /// "infinite" and their appearance is that of a circular spinner.
    ///
    /// Invert this to go back to a bar appearance.
    pub fn set_indeterminate(&self, is_indeterminate: bool) {
        unsafe {
            let _: () = msg_send![&*self.objc, setIndeterminate:match is_indeterminate {
                true => YES,
                false => NO
            }];
        }
    }

    /// Sets the value of this progress indicator.
    ///
    /// If this progress indicator is indeterminate, this will have no effect.
    pub fn set_value(&self, value: f64) {
        let value = value as CGFloat;

        unsafe {
            let _: () = msg_send![&*self.objc, setDoubleValue:value];
        }
    }

    /// Set whether this control is hidden or not.
    pub fn set_hidden(&self, hidden: bool) {
        unsafe {
            let _: () = msg_send![&*self.objc, setHidden:match hidden {
                true => YES,
                false => NO
            }];
        }
    }
}

impl Layout for ProgressIndicator {
    fn get_backing_node(&self) -> ShareId<Object> {
        self.objc.clone()
    }

    fn add_subview<V: Layout>(&self, view: &V) {
        let backing_node = view.get_backing_node();

        unsafe {
            let _: () = msg_send![&*self.objc, addSubview:backing_node];
        }
    }
}

impl Drop for ProgressIndicator {
    /// A bit of extra cleanup for delegate callback pointers. 
    /// If the originating `ProgressIndicator` is being
    /// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
    /// this has a superview (i.e, it's in the heirarchy) on the Objective-C side. If it does, we go
    /// ahead and remove it - this is intended to match the semantics of how Rust handles things).
    ///
    /// There are, thankfully, no delegates we need to break here.
    fn drop(&mut self) {
        unsafe {
            let superview: id = msg_send![&*self.objc, superview];
            if superview != nil {
                let _: () = msg_send![&*self.objc, removeFromSuperview];
            }
        }
    }
}