use crate::{
    c_api::{self, nctree_create, NCRESULT_ERR},
    error, error_ref_mut,
    widgets::{NcTree, NcTreeItem, NcTreeOptions},
    NcError, NcInput, NcPlane, NcResult,
};

/// # `NcTree` constructors & destructors
impl NcTree {
    /// Creates an [NcTree] with the specified options.
    ///
    /// *C style function: [nctree_create()][c_api::nctree_create].*
    pub fn new<'a>(plane: &mut NcPlane, options: NcTreeOptions) -> NcResult<&'a mut Self> {
        error_ref_mut![unsafe { nctree_create(plane, &options) }, "Creating NcTree"]
    }

    /// Destroys an NcTree created with [new()][NcTree#method.new].
    ///
    /// *C style function: [nctree_destroy()][c_api::nctree_destroy].*
    pub fn destroy(&mut self) {
        unsafe { c_api::nctree_destroy(self) };
    }
}

/// # `NcTree` methods
impl NcTree {
    // NOTE: not implemented yet in C API
    //
    // /// Goes to the item specified by the array |spec|, terminated by UINT_MAX.
    // ///
    // /// If the spec is invalid, returns an error and the depth of the first
    // /// invalid spec is written to *|failspec|.
    // ///
    // /// Otherwise, the true depth is written to *|failspec|,
    // /// and the curry is returned (|failspec| is necessary because the
    // /// curry could itself be NULL).
    // ///
    // /// *C style function: [nctree_goto()][c_api::nctree_goto].*
    // pub fn goto(&mut self, spec: ... , failspec: ...) -> NcResult<&mut NcTreeItem> {
    //     let res = unsafe { c_api::nctree_goto(self) };
    //     if !res.is_null() {
    //         Ok(unsafe { &mut *(res as *mut NcTreeItem) })
    //     } else {
    //         Err(NcError::with_msg(NCRESULT_ERR, "NcTree.goto()"))
    //     }
    // }

    /// Inserts `item` at `spec`.
    ///
    /// The path up to the last element must already exist. If an item already
    /// exists at the path, it will be moved to make room for `item`.
    ///
    /// *C style function: [nctree_add()][c_api::nctree_add].*
    pub fn add(&mut self, spec: &u32, item: &NcTreeItem) -> NcResult<()> {
        error![unsafe { c_api::nctree_add(self, spec, item) }]
    }

    /// Deletes the item at `spec`, including any subitems.
    ///
    /// *C style function: [nctree_del()][c_api::nctree_del].*
    pub fn del(&mut self, spec: &u32) -> NcResult<()> {
        error![unsafe { c_api::nctree_del(self, spec) }]
    }

    /// Returns the focused item, if any items are present.
    ///
    /// This is not a copy; be careful to use it only for the duration of a
    /// critical section.
    ///
    /// *C style function: [nctree_focused()][c_api::nctree_focused].*
    pub fn focused(&mut self) -> NcResult<&mut NcTreeItem> {
        let res = unsafe { c_api::nctree_focused(self) };
        if !res.is_null() {
            Ok(unsafe { &mut *(res as *mut NcTreeItem) })
        } else {
            Err(NcError::with_msg(NCRESULT_ERR, "NcTree.focused()"))
        }
    }

    /// Changes the focus to the next item, and returns it.
    ///
    /// *C style function: [nctree_next()][c_api::nctree_next].*
    #[allow(clippy::should_implement_trait)]
    pub fn next(&mut self) -> NcResult<&mut NcTreeItem> {
        let res = unsafe { c_api::nctree_next(self) };
        if !res.is_null() {
            Ok(unsafe { &mut *(res as *mut NcTreeItem) })
        } else {
            Err(NcError::with_msg(NCRESULT_ERR, "NcTree.next()"))
        }
    }

    /// Changes the focus to the previous item, and returns it.
    ///
    /// *C style function: [nctree_prev()][c_api::nctree_prev].*
    pub fn prev(&mut self) -> NcResult<&mut NcTreeItem> {
        let res = unsafe { c_api::nctree_prev(self) };
        if !res.is_null() {
            Ok(unsafe { &mut *(res as *mut NcTreeItem) })
        } else {
            Err(NcError::with_msg(NCRESULT_ERR, "NcTree.prev()"))
        }
    }

    /// Offers the `input` to this NcTree.
    ///
    /// If it's relevant, this function returns true,
    /// and the input ought not be processed further.
    /// If it's irrelevant to the tree, false is returned.
    ///
    /// Relevant inputs include:
    ///
    /// - a mouse click on an item (focuses item)
    /// - a mouse scrollwheel event (srolls tree)
    /// - up, down, pgup, or pgdown (navigates among items)
    ///
    /// *C style function: [nctree_offer_input()][c_api::nctree_offer_input].*
    pub fn offer_input(&mut self, input: NcInput) -> bool {
        unsafe { c_api::nctree_offer_input(self, &input) }
    }

    /// Returns the [NcPlane] backing this NcTree.
    ///
    /// *C style function: [nctree_plane()][c_api::nctree_plane].*
    pub fn plane(&mut self) -> NcResult<&NcPlane> {
        error_ref_mut![unsafe { c_api::nctree_plane(self) }, "NcTree.plane()"]
    }

    /// Redraws the NcTree in its entirety.
    ///
    /// The tree will be cleared, and items will be laid out, using the focused
    /// item as a fulcrum.
    ///
    /// Item-drawing callbacks will be invoked for each visible item.
    ///
    /// *C style function: [nctree_redraw()][c_api::nctree_redraw].*
    pub fn redraw(&mut self) -> NcResult<()> {
        error![unsafe { c_api::nctree_redraw(self) }, "NcTree.redraw()"]
    }
}
