use std::mem;
use std::ops::DerefMut;

use crate::backend::DialogFutureType;
use crate::message_dialog::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel};

use super::{
    modal_future::ModalFuture,
    utils::{run_on_main, FocusManager, PolicyManager},
    AsModal,
};

use super::utils::{INSApplication, INSWindow, NSApplication, NSWindow};
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc_foundation::{INSString, NSString};

use objc_id::Id;

#[repr(i64)]
#[derive(Debug, PartialEq)]
enum NSAlertStyle {
    Warning = 0,
    Informational = 1,
    Critical = 2,
}

#[repr(i64)]
#[derive(Debug, PartialEq)]
enum NSAlertReturn {
    FirstButton = 1000,
    SecondButton = 1001,
    ThirdButton = 1002,
}

pub struct NSAlert {
    buttons: MessageButtons,
    alert: Id<Object>,
    parent: Option<Id<NSWindow>>,
    _focus_manager: FocusManager,
    _policy_manager: PolicyManager,
}

impl NSAlert {
    pub fn new(opt: MessageDialog) -> Self {
        let _policy_manager = PolicyManager::new();

        let alert: *mut Object = unsafe { msg_send![class!(NSAlert), new] };

        let level = match opt.level {
            MessageLevel::Info => NSAlertStyle::Informational,
            MessageLevel::Warning => NSAlertStyle::Warning,
            MessageLevel::Error => NSAlertStyle::Critical,
        };

        unsafe {
            let _: () = msg_send![alert, setAlertStyle: level as i64];
        }

        let buttons = match &opt.buttons {
            MessageButtons::Ok => vec!["OK".to_owned()],
            MessageButtons::OkCancel => vec!["OK".to_owned(), "Cancel".to_owned()],
            MessageButtons::YesNo => vec!["Yes".to_owned(), "No".to_owned()],
            MessageButtons::YesNoCancel => {
                vec!["Yes".to_owned(), "No".to_owned(), "Cancel".to_owned()]
            }
            MessageButtons::OkCustom(ok_text) => vec![ok_text.to_owned()],
            MessageButtons::OkCancelCustom(ok_text, cancel_text) => {
                vec![ok_text.to_owned(), cancel_text.to_owned()]
            }
            MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => {
                vec![
                    yes_text.to_owned(),
                    no_text.to_owned(),
                    cancel_text.to_owned(),
                ]
            }
        };

        for button in buttons {
            unsafe {
                let label = NSString::from_str(&button);
                let _: () = msg_send![alert, addButtonWithTitle: label];
            }
        }

        unsafe {
            let text = NSString::from_str(&opt.title);
            let _: () = msg_send![alert, setMessageText: text];
            let text = NSString::from_str(&opt.description);
            let _: () = msg_send![alert, setInformativeText: text];
        }

        let _focus_manager = FocusManager::new();

        Self {
            alert: unsafe { Id::from_retained_ptr(alert) },
            parent: opt.parent.map(|x| NSWindow::from_raw_window_handle(&x)),
            buttons: opt.buttons,
            _focus_manager,
            _policy_manager,
        }
    }

    pub fn run(mut self) -> MessageDialogResult {
        if let Some(parent) = self.parent.take() {
            let completion = {
                block::ConcreteBlock::new(|result: isize| {
                    let _: () = unsafe {
                        msg_send![NSApplication::shared_application(), stopModalWithCode: result]
                    };
                })
            };

            unsafe {
                msg_send![self.alert, beginSheetModalForWindow: parent completionHandler: &completion]
            }

            mem::forget(completion);
        }

        let ret: i64 = unsafe { msg_send![self.alert, runModal] };
        dialog_result(&self.buttons, ret)
    }
}

fn dialog_result(buttons: &MessageButtons, ret: i64) -> MessageDialogResult {
    match buttons {
        MessageButtons::Ok if ret == NSAlertReturn::FirstButton as i64 => MessageDialogResult::Ok,
        MessageButtons::OkCancel if ret == NSAlertReturn::FirstButton as i64 => {
            MessageDialogResult::Ok
        }
        MessageButtons::OkCancel if ret == NSAlertReturn::SecondButton as i64 => {
            MessageDialogResult::Cancel
        }
        MessageButtons::YesNo if ret == NSAlertReturn::FirstButton as i64 => {
            MessageDialogResult::Yes
        }
        MessageButtons::YesNo if ret == NSAlertReturn::SecondButton as i64 => {
            MessageDialogResult::No
        }
        MessageButtons::YesNoCancel if ret == NSAlertReturn::FirstButton as i64 => {
            MessageDialogResult::Yes
        }
        MessageButtons::YesNoCancel if ret == NSAlertReturn::SecondButton as i64 => {
            MessageDialogResult::No
        }
        MessageButtons::YesNoCancel if ret == NSAlertReturn::ThirdButton as i64 => {
            MessageDialogResult::Cancel
        }
        MessageButtons::OkCustom(custom) if ret == NSAlertReturn::FirstButton as i64 => {
            MessageDialogResult::Custom(custom.to_owned())
        }
        MessageButtons::OkCancelCustom(custom, _) if ret == NSAlertReturn::FirstButton as i64 => {
            MessageDialogResult::Custom(custom.to_owned())
        }
        MessageButtons::OkCancelCustom(_, custom) if ret == NSAlertReturn::SecondButton as i64 => {
            MessageDialogResult::Custom(custom.to_owned())
        }
        MessageButtons::YesNoCancelCustom(custom, _, _)
            if ret == NSAlertReturn::FirstButton as i64 =>
        {
            MessageDialogResult::Custom(custom.to_owned())
        }
        MessageButtons::YesNoCancelCustom(_, custom, _)
            if ret == NSAlertReturn::SecondButton as i64 =>
        {
            MessageDialogResult::Custom(custom.to_owned())
        }
        MessageButtons::YesNoCancelCustom(_, _, custom)
            if ret == NSAlertReturn::ThirdButton as i64 =>
        {
            MessageDialogResult::Custom(custom.to_owned())
        }
        _ => MessageDialogResult::Cancel,
    }
}

impl AsModal for NSAlert {
    fn modal_ptr(&mut self) -> *mut Object {
        self.alert.deref_mut()
    }
}

use crate::backend::MessageDialogImpl;
impl MessageDialogImpl for MessageDialog {
    fn show(self) -> MessageDialogResult {
        objc::rc::autoreleasepool(move || run_on_main(move || NSAlert::new(self).run()))
    }
}

use crate::backend::AsyncMessageDialogImpl;

impl AsyncMessageDialogImpl for MessageDialog {
    fn show_async(self) -> DialogFutureType<MessageDialogResult> {
        let win = self.parent.as_ref().map(NSWindow::from_raw_window_handle);

        let future = ModalFuture::new(
            win,
            move || NSAlert::new(self),
            |dialog, ret| dialog_result(&dialog.buttons, ret),
        );
        Box::pin(future)
    }
}
