//! This example builds on the AutoLayout example, but adds in animation
//! via `AnimationContext`. Views and layout anchors have special proxy objects that can be cloned
//! into handlers, enabling basic animation support within `AnimationContext`.
//!
//! This one is a bit kludgier than some other examples, but the comments throughout this should
//! clarify why that is.

use cacao::color::Color;
use cacao::layout::{Layout, LayoutConstraint, LayoutConstraintAnimatorProxy};
use cacao::view::{View, ViewAnimatorProxy};

use cacao::appkit::menu::Menu;
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
use cacao::appkit::{AnimationContext, App, AppDelegate};
use cacao::appkit::{Event, EventMask, EventMonitor};

struct BasicApp {
    window: Window<AppWindow>
}

impl AppDelegate for BasicApp {
    fn did_finish_launching(&self) {
        App::set_menu(Menu::standard());
        App::activate();

        self.window.show();
    }

    fn should_terminate_after_last_window_closed(&self) -> bool {
        true
    }
}

/// This map is the four different animation frames that we display, per view type.
/// Why do we have this here?
///
/// Well, it's because there's no random number generator in the standard library, and I really
/// dislike when examples need crates attached to 'em.
///
/// The basic mapping logic is this: each entry is a view's frame(s), and each frame is an array
/// of:
///
/// [top, left, width, height, alpha]
///
/// We then treat each frame index as follows:
///
/// w: 0
/// a: 1
/// s: 2
/// d: 3
const ANIMATIONS: [[[f64; 5]; 4]; 3] = [
    // Blue
    [
        [44., 16., 100., 100., 1.],
        [128., 84., 144., 124., 1.],
        [32., 32., 44., 44., 0.7],
        [328., 157., 200., 200., 0.7]
    ],
    // Red
    [
        [44., 132., 100., 100., 1.],
        [40., 47., 80., 64., 0.7],
        [84., 220., 600., 109., 1.0],
        [48., 600., 340., 44., 0.7]
    ],
    // Green
    [
        [44., 248., 100., 100., 1.],
        [420., 232., 420., 244., 0.7],
        [310., 440., 150., 238., 0.7],
        [32., 32., 44., 44., 1.]
    ]
];

/// A helper method for generating frame constraints that we want to be animating.
fn apply_styles(view: &View, parent: &View, background_color: Color, animation_table_index: usize) -> [LayoutConstraint; 4] {
    view.set_background_color(background_color);
    view.layer.set_corner_radius(16.);
    parent.add_subview(view);

    let animation = ANIMATIONS[animation_table_index][0];

    [
        view.top.constraint_equal_to(&parent.top).offset(animation[0]),
        view.left.constraint_equal_to(&parent.left).offset(animation[1]),
        view.width.constraint_equal_to_constant(animation[2]),
        view.height.constraint_equal_to_constant(animation[3])
    ]
}

#[derive(Default)]
struct AppWindow {
    content: View,
    blue: View,
    red: View,
    green: View,
    key_monitor: Option<EventMonitor>
}

impl WindowDelegate for AppWindow {
    const NAME: &'static str = "WindowDelegate";

    fn did_load(&mut self, window: Window) {
        window.set_title("Animation Example (Use W/A/S/D to change state!)");
        window.set_minimum_content_size(300., 300.);

        window.set_content_view(&self.content);

        let blue_frame = apply_styles(&self.blue, &self.content, Color::SystemBlue, 0);
        let red_frame = apply_styles(&self.red, &self.content, Color::SystemRed, 1);
        let green_frame = apply_styles(&self.green, &self.content, Color::SystemGreen, 2);

        let alpha_animators = [&self.blue, &self.red, &self.green]
            .iter()
            .map(|view| view.animator.clone())
            .collect::<Vec<ViewAnimatorProxy>>();

        let constraint_animators = [blue_frame, red_frame, green_frame]
            .iter()
            .map(|frame| {
                LayoutConstraint::activate(frame);

                vec![
                    frame[0].animator.clone(),
                    frame[1].animator.clone(),
                    frame[2].animator.clone(),
                    frame[3].animator.clone(),
                ]
            })
            .collect::<Vec<Vec<LayoutConstraintAnimatorProxy>>>();

        // Monitor key change events for w/a/s/d, and then animate each view to their correct
        // frame and alpha value.
        self.key_monitor = Some(Event::local_monitor(EventMask::KeyDown, move |evt| {
            let characters = evt.characters();

            let animation_index = match characters.as_ref() {
                "w" => 0,
                "a" => 1,
                "s" => 2,
                "d" => 3,
                _ => 4
            };

            if animation_index == 4 {
                return None;
            }

            let alpha_animators = alpha_animators.clone();
            let constraint_animators = constraint_animators.clone();

            AnimationContext::run(move |_ctx| {
                alpha_animators.iter().enumerate().for_each(move |(index, view)| {
                    let animation = ANIMATIONS[index][animation_index];
                    view.set_alpha(animation[4]);
                });

                constraint_animators.iter().enumerate().for_each(move |(index, frame)| {
                    let animation = ANIMATIONS[index][animation_index];
                    frame[0].set_offset(animation[0]);
                    frame[1].set_offset(animation[1]);
                    frame[2].set_offset(animation[2]);
                    frame[3].set_offset(animation[3]);
                });
            });

            None
        }));
    }
}

fn main() {
    App::new("com.test.window", BasicApp {
        window: Window::with(WindowConfig::default(), AppWindow::default())
    })
    .run();
}