/*
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of Screenplay.

Screenplay is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Screenplay is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/

/// Screenplay is a library that helps visualize GTK scenes. I got my inspiration from working with
/// Storybook and decided that I wanted a similar tool for GTK.
///
/// With this library, you can write an application that loads up a bunch of different user
/// interfaces, and the library will put them on separate pages. Each UI can have as much or as
/// little behavior as you want.
use gtk::prelude::*;

#[derive(Clone, Debug)]
enum Action {
    SelectPage(usize),
    Deselect,
}

/// Unused. In the near future, this will contain useful errors.
#[derive(Clone, Debug)]
pub struct Error;

/// A definition of a single Screen. The screen can be as much or as little as you want, but must
/// contain a title (for the page selector) and a widget to show the screen.
#[derive(Clone)]
pub struct Screen {
    /// Title of the screen. This will only be rendered in the page selector.
    pub title: String,

    /// The screen itself. This can be anything that implements the gtk::Widget interface, and it
    /// will be rendered in the main area of the window.
    pub widget: gtk::Widget,

    /// Unused today, however...
    /// These will be rendered in an area of the window reserved for adjustments. They are meant to
    /// be ways to provide variants to the current scene. You will need to attach behaviors.
    pub adjustments: Vec<gtk::Widget>,
}

/// Screenplay is an object that will render a gtk::ApplicationWindow.
///
/// That window contains a sidebar which lists the names of all of the Screens that have been
/// provided to it. Selecting one of those will cause the application to render that screen and all
/// of its adjustments.
///
/// It is highly likely that in the future this structure will be reformulated as a child of a GTK
/// ApplicationWindow.
#[derive(Clone)]
pub struct Screenplay {
    frame: gtk::Frame,
    screens: Vec<Screen>,
}

impl Screenplay {
    /// Construct a new Screenplay. This will construct and render the ApplicationWindow as the
    /// primary child of the Application.
    ///
    /// This function currently returns no errors, instead panicing if anything goes wrong.
    pub fn new(gtk_app: &gtk::Application, screens: Vec<Screen>) -> Result<Self, Error> {
        let window = gtk::ApplicationWindow::new(gtk_app);
        window.show();

        let (sender, receiver) = gtk::glib::MainContext::channel(gtk::glib::Priority::DEFAULT);

        let layout = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .build();
        window.set_child(Some(&layout));

        let listbox = gtk::ListBox::builder()
            .activate_on_single_click(true)
            .show_separators(true)
            .focusable(false)
            .build();

        let frame = gtk::Frame::builder()
            .height_request(500)
            .width_request(500)
            .build();

        layout.append(&listbox);
        layout.append(&frame);

        listbox.connect_row_activated(move |_, row| {
            match row.index() {
                -1 => sender.send(Action::Deselect),
                idx => sender.send(Action::SelectPage(idx as usize)),
            }
            .unwrap()
        });

        screens.iter().for_each(|Screen { title, .. }| {
            listbox.append(
                &gtk::ListBoxRow::builder()
                    .child(&gtk::Label::new(Some(title.as_ref())))
                    .build(),
            );
        });

        let storybook = Self { frame, screens };

        {
            let mut storybook = storybook.clone();
            receiver.attach(None, move |message| storybook.process_action(message));
        }

        Ok(storybook)
    }

    /// Handle an application action. This generally means changing the current screen.
    fn process_action(&mut self, message: Action) -> glib::ControlFlow {
        let nothing: Option<&gtk::Widget> = None;
        match message {
            Action::SelectPage(index) => match self.screens.get(index) {
                Some(Screen {
                    widget,
                    adjustments,
                    ..
                }) => {
                    let b = gtk::Box::new(gtk::Orientation::Vertical, 0);
                    let control_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
                    b.append(widget);
                    b.append(&control_box);

                    adjustments
                        .iter()
                        .for_each(|control| control_box.append(control));

                    self.frame.set_child(Some(&b));
                }
                None => self.frame.set_child(nothing),
            },
            Action::Deselect => self.frame.set_child(nothing),
        }

        glib::ControlFlow::Continue
    }
}
