// SPDX-License-Identifier: GPL-3.0-or-later
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate clap_derive;
use std::{error::Error, collections::HashMap};
use cli::Cli;
use iced::{
    Application, Element, Theme, Command, Subscription, subscription,
};
use tree::{Action, Window};
use clap::Parser;

mod cli;
mod tree;
mod build;

struct App {
    window: Window,
    texts: HashMap<String, String>,
    boxes: HashMap<String, bool>,
}

#[derive(Debug, Clone)]
enum Message {
    Perform(Action),
    Button(String),
    Checkbox(String, bool),
    TextInputChange(String, String),
    TextInputSubmit(String, String),
}

impl App {
    fn fixname(&self, name: &mut String) {
        if let Some(prefix) = &self.window.prefix {
            name.insert_str(0, &prefix);
        }
    }
    fn get_text<'a>(&'a self, mut name: String, default: &'a String) -> &'a String {
        self.fixname(&mut name);
        self.texts.get(&name).unwrap_or(default)
    }
    fn get_box(&self, mut name: String, default: bool) -> bool {
        self.fixname(&mut name);
        self.boxes.get(&name).copied().unwrap_or(default)
    }
    fn set_text(&mut self, mut name: String, text: String) {
        self.fixname(&mut name);
        self.texts.insert(name, text);
    }
    fn set_box(&mut self, mut name: String, state: bool) {
        self.fixname(&mut name);
        self.boxes.insert(name, state);
    }
}

impl Application for App {
    type Message = Message;
    type Executor = iced::executor::Default;
    type Theme = Theme;
    type Flags = Window;

    fn new(window: Self::Flags) -> (Self, Command<Message>) {
        (
            Self {
                window,
                texts: HashMap::new(),
                boxes: HashMap::new(),
            },
            Command::none()
        )
    }

    fn title(&self) -> String {
        self.window.title.clone()
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            Message::Button(n) => println!("Button {n:?} pressed."),
            Message::Checkbox(n, v) => { println!("Checkbox {n:?} set to {v}."); self.set_box(n, v); },
            Message::TextInputChange(n, t) => { println!("TextInput {n:?} set to {t:?}."); self.set_text(n, t); },
            Message::TextInputSubmit(n, t) => println!("TextInput {n:?} submitted {t:?}."),
            Message::Perform(action) => {
                match action {
                    Action::Update(w) => self.window = w,
                    Action::Clear { name } => {
                        self.texts.remove(&name);
                        self.boxes.remove(&name);
                    },
                    Action::ClearAll => {
                        self.texts.clear();
                        self.boxes.clear();
                    },
                }
            }
        }
        Command::none()
    }

    fn scale_factor(&self) -> f64 {
        self.window.scale
    }

    fn view(&self) -> Element<'_, Message, iced::Renderer<Self::Theme>> {
        self.window.root.build(self)
    }

    fn subscription(&self) -> Subscription<Message> {
        stdin_listener()
    }
}

fn stdin_listener() -> Subscription<Message> {
    use async_std::io::stdin;
    struct Worker;

    async fn read_action() -> Result<Option<Action>, Box<dyn Error>> {
        let mut line = String::new();
        stdin().read_line(&mut line).await?;
        if line.is_empty() {
            return Ok(None);
        }
        let action = serde_json::from_str(&line)?;
        Ok(Some(action))
    }

    subscription::unfold(
        std::any::TypeId::of::<Worker>(),
        (),
        |_| async move {
            let m = match read_action().await {
                Ok(Some(a))	=> Some(Message::Perform(a)),
                Ok(None)		=> None,
                Err(e)			=> Some(Message::Perform(Action::Update(Window::error(e)))),
            };
            (m, ())
        },
    )
}

fn main() -> iced::Result {
    let cli = Cli::parse();
    let settings = cli.into_settings(Window::default())
        .map_err(|e| iced::Error::WindowCreationFailed(Box::new(e)))?;
    App::run(settings)
}
