use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget};
use heapless::{String, Vec};

use crate::{
    contact::{ContactGroup, MAX_CONTACTS, MAX_CONTACT_NAME_LENGTH},
    gui::{chat::Chat, contact_view::ContactView, selector::Selector, status::StatusBar, Element},
    keyboard::KeyCode,
    voltage::VoltMeter,
};

pub enum View {
    MainMenu(Selector<&'static str, [&'static str; 3]>),
    Contacts(
        Selector<
            String<MAX_CONTACT_NAME_LENGTH>,
            Vec<String<MAX_CONTACT_NAME_LENGTH>, MAX_CONTACTS>,
        >,
    ),
    ContactOptions(Selector<&'static str, [&'static str; 3]>, usize),
    #[allow(clippy::enum_variant_names)]
    ContactView(ContactView, usize),
    Chat(Chat),
}

impl View {
    pub fn new_main_menu(id: usize) -> Self {
        let menu = Selector::new(["Contacts", "Messages", "Settings"], id);
        Self::MainMenu(menu)
    }

    pub fn new_contacts(id: usize, contacts: &ContactGroup) -> Self {
        let mut contact_names: Vec<_, MAX_CONTACTS> =
            contacts.contacts.iter().map(|c| c.name.clone()).collect();

        // If we run out of space, we automatically hide the "Add" button
        let _ = contact_names.push(String::try_from("Add New").unwrap());

        Self::Contacts(Selector::new(contact_names, id))
    }

    pub fn new_contact_options(contact_id: usize) -> Self {
        let opts = Selector::new(["View", "Edit", "Message"], 0);
        Self::ContactOptions(opts, contact_id)
    }

    pub fn from_main_menu_id(id: usize, contacts: &ContactGroup) -> Option<Self> {
        match id {
            0 => Some(Self::new_contacts(0, contacts)),
            1 => Some(Self::Chat(Chat::new())),
            _ => None,
        }
    }

    pub fn to_main_menu_id(&self) -> usize {
        match self {
            View::Contacts(_) => 0,
            View::Chat(_) => 1,
            _ => unreachable!(),
        }
    }

    pub fn from_contact_options_id(
        id: usize,
        contact_id: usize,
        contacts: &ContactGroup,
    ) -> Option<Self> {
        match id {
            0 => {
                let contact = contacts.contacts[contact_id].clone();
                Some(View::ContactView(
                    ContactView::new(contact, false),
                    contact_id,
                ))
            }

            1 => {
                let contact = contacts.contacts[contact_id].clone();
                Some(View::ContactView(
                    ContactView::new(contact, true),
                    contact_id,
                ))
            }

            _ => None,
        }
    }
}

impl Default for View {
    fn default() -> Self {
        Self::new_main_menu(0)
    }
}

impl Element for View {
    fn render<E, DT: DrawTarget<Color = Rgb565, Error = E>>(
        &mut self,
        dt: &mut DT,
    ) -> Result<(), E> {
        match self {
            View::MainMenu(m) => m.render(dt),
            View::Contacts(c) => c.render(dt),
            View::ContactOptions(c, _) => c.render(dt),
            View::ContactView(c, _) => c.render(dt),
            View::Chat(c) => c.render(dt),
        }
    }

    fn key_push(&mut self, k: KeyCode) {
        match self {
            View::MainMenu(m) => m.key_push(k),
            View::Contacts(c) => c.key_push(k),
            View::ContactOptions(c, _) => c.key_push(k),
            View::ContactView(c, _) => c.key_push(k),
            View::Chat(c) => c.key_push(k),
        }
    }

    fn touchpad_scroll(&mut self, x: i8, y: i8) {
        match self {
            View::MainMenu(m) => m.touchpad_scroll(x, y),
            View::Contacts(c) => c.touchpad_scroll(x, y),
            View::ContactOptions(c, _) => c.touchpad_scroll(x, y),
            View::ContactView(c, _) => c.touchpad_scroll(x, y),
            View::Chat(c) => c.touchpad_scroll(x, y),
        }
    }
}

pub struct Controller {
    status: StatusBar,
    cur_view: View,

    // May belong in a DataStore struct
    contacts: ContactGroup,
}

impl Controller {
    pub fn new(volt_meter: VoltMeter) -> Self {
        Self {
            status: StatusBar::new(volt_meter),
            cur_view: View::default(),

            contacts: ContactGroup::default(),
        }
    }
}

impl Element for Controller {
    fn render<E, DT: DrawTarget<Color = Rgb565, Error = E>>(
        &mut self,
        dt: &mut DT,
    ) -> Result<(), E> {
        dt.clear(Rgb565::new(0, 32, 16))?;

        self.status.render(dt)?;

        self.cur_view.render(dt)?;

        Ok(())
    }

    fn key_push(&mut self, k: KeyCode) {
        match (&self.cur_view, k) {
            (View::MainMenu(m), KeyCode::Touchpad) => {
                if let Some(new_view) = View::from_main_menu_id(m.selected(), &self.contacts) {
                    self.cur_view = new_view;
                }
            }

            (View::Contacts(c), KeyCode::Touchpad) => {
                // TODO need to handle "Add New" button
                self.cur_view = View::new_contact_options(c.selected());
            }

            (View::ContactOptions(c, contact_id), KeyCode::Touchpad) => {
                if let Some(new_view) =
                    View::from_contact_options_id(c.selected(), *contact_id, &self.contacts)
                {
                    self.cur_view = new_view;
                }
            }

            (View::MainMenu(_), KeyCode::Back) => {}

            (View::ContactOptions(_, id), KeyCode::Back) => {
                self.cur_view = View::new_contacts(*id, &self.contacts);
            }

            (View::ContactView(cv, contact_id), KeyCode::Back) => {
                self.contacts.contacts[*contact_id] = cv.contact();
                self.cur_view = View::new_contact_options(*contact_id)
            }

            (_, KeyCode::Back) => {
                let id = self.cur_view.to_main_menu_id();
                self.cur_view = View::new_main_menu(id);
            }

            (_, k) => self.cur_view.key_push(k),
        }
    }

    fn touchpad_scroll(&mut self, x: i8, y: i8) {
        self.cur_view.touchpad_scroll(x, y);
    }
}