/*! Managing the state of text input in the application.
 * 
 * This is a library module.
 * 
 * It needs to combine text-input and virtual-keyboard protocols
 * to achieve a consistent view of the text-input state,
 * and to submit exactly what the user wanted.
 * 
 * It must also not get tripped up by sudden disappearances of interfaces.
 * 
 * The virtual-keyboard interface is always present.
 * 
 * The text-input interface may not be presented,
 * and, for simplicity, no further attempt to claim it is made.
 * 
 * The text-input interface may be enabled and disabled at arbitrary times,
 * and those events SHOULD NOT cause any lost events.
 * */

use std::collections::HashSet;
use std::ffi::CString;

use crate::vkeyboard::c::ZwpVirtualKeyboardV1;
use crate::action::Modifier;
use crate::imservice;
use crate::imservice::IMService;
use crate::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
use crate::layout;
use crate::util::vec_remove;
use crate::vkeyboard;
use crate::vkeyboard::VirtualKeyboard;

// traits
use std::iter::FromIterator;

/// Gathers stuff defined in C or called by C
pub mod c {
    use super::*;

    use crate::util::c::Wrapped;

    pub type Submission = Wrapped<super::Submission>;
    

    #[no_mangle]
    pub extern "C"
    fn submission_use_layout(
        submission: Submission,
        layout: *const layout::Layout,
        time: u32,
    ) {
        let submission = submission.clone_ref();
        let mut submission = submission.borrow_mut();
        let layout = unsafe { &*layout };
        submission.use_layout(&layout.shape, Timestamp(time));
    }

    #[no_mangle]
    pub extern "C"
    fn submission_hint_available(submission: Submission) -> u8 {
        let submission = submission.clone_ref();
        let submission = submission.borrow();
        let active = submission.imservice.as_ref()
            .map(|imservice| imservice.is_active());
        (Some(true) == active) as u8
    }
}

#[derive(Clone, Copy)]
pub struct Timestamp(pub u32);

#[derive(Clone)]
enum SubmittedAction {
    /// A collection of keycodes that were pressed
    VirtualKeyboard(Vec<KeyCode>),
    IMService,
}

pub struct Submission {
    imservice: Option<Box<IMService>>,
    virtual_keyboard: VirtualKeyboard,
    modifiers_active: Vec<(KeyStateId, Modifier)>,
    pressed: Vec<(KeyStateId, SubmittedAction)>,
    keymap_fds: Vec<vkeyboard::c::KeyMap>,
    keymap_idx: Option<usize>,
}

pub enum SubmitData<'a> {
    Text(&'a CString),
    Erase,
    Keycodes,
}

impl Submission {
    pub fn new(vk: ZwpVirtualKeyboardV1, imservice: Option<Box<IMService>>) -> Self {
        Submission {
            imservice,
            modifiers_active: Vec::new(),
            virtual_keyboard: VirtualKeyboard(vk),
            pressed: Vec::new(),
            keymap_fds: Vec::new(),
            keymap_idx: None,
        }
    }

    /// Sends a submit text event if possible;
    /// otherwise sends key press and makes a note of it
    pub fn handle_press(
        &mut self,
        key_id: KeyStateId,
        data: SubmitData,
        keycodes: &Vec<KeyCode>,
        time: Timestamp,
    ) {
        let mods_are_on = !self.modifiers_active.is_empty();

        let was_committed_as_text = match (&mut self.imservice, mods_are_on) {
            (Some(imservice), false) => {
                enum Outcome {
                    Submitted(Result<(), imservice::SubmitError>),
                    NotSubmitted,
                }

                let submit_outcome = match data {
                    SubmitData::Text(text) => {
                        Outcome::Submitted(imservice.commit_string(text))
                    },
                    SubmitData::Erase => {
                        /* Delete_surrounding_text takes byte offsets,
                         * so cannot work without get_surrounding_text.
                         * This is a bug in the protocol.
                         */
                        // imservice.delete_surrounding_text(1, 0),
                        Outcome::NotSubmitted
                    },
                    SubmitData::Keycodes => Outcome::NotSubmitted,
                };

                match submit_outcome {
                    Outcome::Submitted(result) => {
                        match result.and_then(|()| imservice.commit()) {
                            Ok(()) => true,
                            Err(imservice::SubmitError::NotActive) => false,
                        }
                    },
                    Outcome::NotSubmitted => false,
                }
            },
            (_, _) => false,
        };

        let submit_action = match was_committed_as_text {
            true => SubmittedAction::IMService,
            false => {
                let keycodes_count = keycodes.len();
                for keycode in keycodes.iter() {
                    self.select_keymap(keycode.keymap_idx, time);
                    let keycode = keycode.code;
                    match keycodes_count {
                        // Pressing a key made out of a single keycode is simple:
                        // press on press, release on release.
                        1 => self.virtual_keyboard.switch(
                            keycode,
                            PressType::Pressed,
                            time,
                        ),
                        // A key made of multiple keycodes
                        // has to submit them one after the other.
                        _ => {
                            self.virtual_keyboard.switch(
                                keycode.clone(),
                                PressType::Pressed,
                                time,
                            );
                            self.virtual_keyboard.switch(
                                keycode.clone(),
                                PressType::Released,
                                time,
                            );
                        },
                    };
                }
                SubmittedAction::VirtualKeyboard(keycodes.clone())
            },
        };
        
        self.pressed.push((key_id, submit_action));
    }
    
    pub fn handle_release(&mut self, key_id: KeyStateId, time: Timestamp) {
        let index = self.pressed.iter().position(|(id, _)| *id == key_id);
        if let Some(index) = index {
            let (_id, action) = self.pressed.remove(index);
            match action {
                // string already sent, nothing to do
                SubmittedAction::IMService => {},
                // no matter if the imservice got activated,
                // keys must be released
                SubmittedAction::VirtualKeyboard(keycodes) => {
                    let keycodes_count = keycodes.len();
                    match keycodes_count {
                        1 => {
                            let keycode = &keycodes[0];
                            self.select_keymap(keycode.keymap_idx, time);
                            self.virtual_keyboard.switch(
                                keycode.code,
                                PressType::Released,
                                time,
                            );
                        },
                        // Design choice here: submit multiple all at press time
                        // and do nothing at release time.
                        _ => {},
                    };
                },
            }
        };
    }
    
    pub fn handle_add_modifier(
        &mut self,
        key_id: KeyStateId,
        modifier: Modifier, _time: Timestamp,
    ) {
        self.modifiers_active.push((key_id, modifier));
        self.update_modifiers();
    }

    pub fn handle_drop_modifier(
        &mut self,
        key_id: KeyStateId,
        _time: Timestamp,
    ) {
        vec_remove(&mut self.modifiers_active, |(id, _)| *id == key_id);
        self.update_modifiers();
    }

    fn update_modifiers(&mut self) {
        let raw_modifiers = self.modifiers_active.iter()
            .map(|(_id, m)| match m {
                Modifier::Control => Modifiers::CONTROL,
                Modifier::Alt => Modifiers::MOD1,
                Modifier::Mod4 => Modifiers::MOD4,
            })
            .fold(Modifiers::empty(), |m, n| m | n);
        self.virtual_keyboard.set_modifiers_state(raw_modifiers);
    }

    pub fn is_modifier_active(&self, modifier: Modifier) -> bool {
        self.modifiers_active.iter()
            .position(|(_id, m)| *m == modifier)
            .is_some()
    }

    pub fn get_active_modifiers(&self) -> HashSet<Modifier> {
        HashSet::from_iter(
            self.modifiers_active.iter().map(|(_id, m)| m.clone())
        )
    }

    fn clear_all_modifiers(&mut self) {
        // Looks like an optimization,
        // but preemptive cleaning is needed before setting a new keymap,
        // so removing this check would break keymap setting.
        if self.modifiers_active.is_empty() {
            return;
        }
        self.modifiers_active = Vec::new();
        self.virtual_keyboard.set_modifiers_state(Modifiers::empty())
    }

    fn release_all_virtual_keys(&mut self, time: Timestamp) {
        let virtual_pressed = self.pressed
            .clone().into_iter()
            .filter_map(|(id, action)| {
                match action {
                    SubmittedAction::VirtualKeyboard(_) => Some(id),
                    _ => None,
                }
            });
        for id in virtual_pressed {
            self.handle_release(id, time);
        }
    }


    /// Changes keymap and clears pressed keys and modifiers.
    ///
    /// It's not obvious if clearing is the right thing to do, 
    /// but keymap update may (or may not) do that,
    /// possibly putting self.modifiers_active and self.pressed out of sync,
    /// so a consistent stance is adopted to avoid that.
    /// Alternatively, modifiers could be restored on the new keymap.
    /// That approach might be difficult
    /// due to modifiers meaning different things in different keymaps.
    fn select_keymap(&mut self, idx: usize, time: Timestamp) {
        if self.keymap_idx != Some(idx) {
            self.keymap_idx = Some(idx);
            self.clear_all_modifiers();
            self.release_all_virtual_keys(time);
            let keymap = &self.keymap_fds[idx];
            self.virtual_keyboard.update_keymap(keymap);
        }
    }
    
    pub fn use_layout(&mut self, layout: &layout::LayoutData, time: Timestamp) {
        self.keymap_fds = layout.keymaps.iter()
            .map(|keymap_str| vkeyboard::c::KeyMap::from_cstr(
                keymap_str.as_c_str()
            ))
            .collect();
        self.keymap_idx = None;

        // This can probably be eliminated,
        // because key presses can trigger an update anyway.
        // However, self.keymap_idx needs to become Option<>
        // in order to force update on new layouts.
        self.select_keymap(0, time);
    }
}
