//! Displays a dialog to select a file

use crate::natives_and_messaging::*;
use std::ffi::OsStr;
use std::iter::once;
use std::mem::size_of;
use std::ptr::null_mut;
use std::result;

#[cfg(windows)]
const SZW_SIZE: usize = 512;
#[cfg(windows)]
type Result<T> = result::Result<T, String>;
#[cfg(windows)]
type TSzwBuf = [u16; SZW_SIZE];

#[cfg(windows)]
pub struct OpenFileDialog<'a> {
    /// Dialog Title
    pub title: &'a str,
    pub style: winapi::shared::minwindef::DWORD,
}

#[cfg(windows)]
impl<'a> OpenFileDialog<'a> {
    pub fn new(title: &'a str, style: winapi::shared::minwindef::DWORD) -> Self {
        Self { title, style }
    }

    #[allow(non_snake_case)]
    pub fn show(&self) -> String {
        use winapi::um::commdlg::GetOpenFileNameW;
        use winapi::um::commdlg::*;

        const SZW_SIZE: usize = 512;
        type TSzwBuf = [u16; SZW_SIZE];

        let szwFilter = str_to_szw(" ");
        let szwTitle = str_to_szw(self.title);
        let mut szw_buf: TSzwBuf = [0; SZW_SIZE];
        let mut dlgOpen = OPENFILENAMEW {
            lStructSize: size_of::<OPENFILENAMEW>() as u32, //DWORD,
            hwndOwner: null_mut(),                          // HWND,
            hInstance: null_mut(),                          // HINSTANCE,
            lpstrFilter: szwFilter.as_ptr(),                // LPCWSTR,
            lpstrCustomFilter: null_mut(),                  // LPWSTR,
            nMaxCustFilter: 0,                              // DWORD,
            nFilterIndex: 0,                                // DWORD,
            lpstrFile: szw_buf.as_mut_ptr(),                // LPWSTR,
            nMaxFile: szw_buf.len() as u32,                 // DWORD,
            lpstrFileTitle: null_mut(),                     // LPWSTR,
            nMaxFileTitle: 0,                               // DWORD,
            lpstrInitialDir: null_mut(),                    // LPCWSTR,
            lpstrTitle: szwTitle.as_ptr(),                  // LPCWSTR,
            Flags: self.style,                              // DWORD,
            nFileOffset: 0,                                 // WORD,
            nFileExtension: 0,                              // WORD,
            lpstrDefExt: null_mut(),                        // LPCWSTR,
            lCustData: 0,                                   // LPARAM,
            lpfnHook: None,                                 // LPOFNHOOKPROC,
            lpTemplateName: null_mut(),                     // LPCWSTR,
            pvReserved: null_mut(),                         // *mut c_void,
            dwReserved: 0,                                  // DWORD,
            FlagsEx: 0,                                     // DWORD,
        };

        match unsafe { GetOpenFileNameW(&mut dlgOpen) } {
            0 => "Nothing is selected !".to_string(),
            _ => szw_to_string(&szw_buf).unwrap(),
        }
    }
}

#[cfg(windows)]
fn szw_to_string(szwbuf: &TSzwBuf) -> Result<String> {
    szwbuf
        .iter()
        .position(|wch| wch == &0)
        .ok_or("String : Can't find zero terminator !".to_owned())
        .and_then(|ix| String::from_utf16(&szwbuf[..ix]).map_err(|e| e.to_string()))
}

#[cfg(windows)]
fn str_to_szw(str_body: &str) -> Vec<u16> {
    use std::os::windows::ffi::OsStrExt;
    return OsStr::new(str_body)
        .encode_wide()
        .chain(once(0))
        .collect::<Vec<u16>>();
}

#[cfg(unix)]
pub struct OpenFileDialog<'a> {
    pub title: &'a str,
    style: FileChooserStyle,
}

#[cfg(unix)]
impl<'a> OpenFileDialog<'a> {
    pub fn new(title: &'a str, style: FileChooserStyle) -> Self {
        Self {
            /// Dialog Title
            title,
            style,
        }
    }

    pub fn show(&self) -> String {
        use gtk::prelude::{DialogExt, WidgetExt, WidgetExtManual};
        let dialog = gtk::FileChooserDialog::new::<gtk::Window>(
            Some(self.title),
            None,
            gtk::FileChooserAction::Open,
        );

        dialog.add_button("Cancel", gtk::ResponseType::Cancel.into());
        dialog.add_button("Open", gtk::ResponseType::Accept.into());

        match self.style {
            FC_ENABLE_HELP => {
                dialog.add_button("Help", gtk::ResponseType::Help);
            }
            _ => {}
        }

        let mut file = "".to_string();

        if dialog.run() == gtk::ResponseType::Accept.into() {
            if let path = dialog.path() {
                file = dialog.path().to_string()
            } else {
                file = "".to_string()
            }
        } else {
            file = "".to_string()
        }

        unsafe {
            dialog.destroy();
        }

        file
    }
}

#[cfg(windows)]
pub struct SaveFileDialog<'a> {
    /// Dialog Title
    pub title: &'a str,
    pub style: winapi::shared::minwindef::DWORD,
}

#[cfg(windows)]
impl<'a> SaveFileDialog<'a> {
    pub fn new(title: &'a str, style: winapi::shared::minwindef::DWORD) -> Self {
        Self { title, style }
    }

    #[allow(non_snake_case)]
    pub fn show(&self) -> String {
        use winapi::um::commdlg::*;

        const SZW_SIZE: usize = 512;
        type TSzwBuf = [u16; SZW_SIZE];

        let szwFilter = str_to_szw(" ");
        let szwTitle = str_to_szw(self.title);
        let mut szw_buf: TSzwBuf = [0; SZW_SIZE];
        let mut dlgOpen = OPENFILENAMEW {
            lStructSize: size_of::<OPENFILENAMEW>() as u32, //DWORD,
            hwndOwner: null_mut(),                          // HWND,
            hInstance: null_mut(),                          // HINSTANCE,
            lpstrFilter: szwFilter.as_ptr(),                // LPCWSTR,
            lpstrCustomFilter: null_mut(),                  // LPWSTR,
            nMaxCustFilter: 0,                              // DWORD,
            nFilterIndex: 0,                                // DWORD,
            lpstrFile: szw_buf.as_mut_ptr(),                // LPWSTR,
            nMaxFile: szw_buf.len() as u32,                 // DWORD,
            lpstrFileTitle: null_mut(),                     // LPWSTR,
            nMaxFileTitle: 0,                               // DWORD,
            lpstrInitialDir: null_mut(),                    // LPCWSTR,
            lpstrTitle: szwTitle.as_ptr(),                  // LPCWSTR,
            Flags: self.style,                              // DWORD,
            nFileOffset: 0,                                 // WORD,
            nFileExtension: 0,                              // WORD,
            lpstrDefExt: null_mut(),                        // LPCWSTR,
            lCustData: 0,                                   // LPARAM,
            lpfnHook: None,                                 // LPOFNHOOKPROC,
            lpTemplateName: null_mut(),                     // LPCWSTR,
            pvReserved: null_mut(),                         // *mut c_void,
            dwReserved: 0,                                  // DWORD,
            FlagsEx: 0,                                     // DWORD,
        };

        match unsafe { GetSaveFileNameW(&mut dlgOpen) } {
            0 => "Nothing is selected !".to_string(),
            _ => szw_to_string(&szw_buf).unwrap(),
        }
    }
}

#[cfg(unix)]
pub struct SaveFileDialog<'a> {
    pub title: &'a str,
    style: FileChooserStyle,
}

#[cfg(unix)]
impl<'a> SaveFileDialog<'a> {
    pub fn new(title: &'a str, style: FileChooserStyle) -> Self {
        Self {
            /// Dialog Title
            title,
            style,
        }
    }

    pub fn show(&self) -> String {
        use gtk::prelude::{DialogExt, WidgetExt, WidgetExtManual};
        let dialog = gtk::FileChooserDialog::new::<gtk::Window>(
            Some(self.title),
            None,
            gtk::FileChooserAction::Save,
        );

        dialog.add_button("Cancel", gtk::ResponseType::Cancel.into());
        dialog.add_button("Save", gtk::ResponseType::Accept.into());

        match self.style {
            FC_ENABLE_HELP => {
                dialog.add_button("Help", gtk::ResponseType::Help);
            }
            _ => {}
        }

        let mut file = "".to_string();

        if dialog.run() == gtk::ResponseType::Accept.into() {
            if let path = dialog.path() {
                file = dialog.path().to_string()
            }
        } else {
            file = "".to_string()
        }

        unsafe {
            dialog.destroy();
        }

        file
    }
}
