// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::Error,
    argh::FromArgs,
    carnelian::{
        color::Color,
        make_app_assistant,
        render::{
            BlendMode, Context as RenderContext, Fill, FillRule, Layer, Path, PathBuilder, Raster,
            Style,
        },
        scene::{
            facets::Facet,
            scene::{Scene, SceneBuilder},
            LayerGroup,
        },
        App, AppAssistant, Point, RenderOptions, Size, ViewAssistant, ViewAssistantContext,
        ViewAssistantPtr, ViewKey,
    },
    chrono::{Local, Timelike},
    euclid::{point2, size2, vec2, Angle, Transform2D},
    fuchsia_trace_provider,
    fuchsia_zircon::Event,
    std::f32,
};

const BACKGROUND_COLOR: Color = Color { r: 235, g: 213, b: 179, a: 255 };

/// Clockface.
#[derive(Debug, FromArgs)]
#[argh(name = "clockface_rs")]
struct Args {
    /// use spinel (GPU rendering back-end)
    #[argh(switch, short = 's')]
    use_spinel: bool,
}

#[derive(Default)]
struct ClockfaceAppAssistant {
    use_spinel: bool,
}

impl AppAssistant for ClockfaceAppAssistant {
    fn setup(&mut self) -> Result<(), Error> {
        let args: Args = argh::from_env();
        self.use_spinel = args.use_spinel;
        Ok(())
    }

    fn create_view_assistant(&mut self, _: ViewKey) -> Result<ViewAssistantPtr, Error> {
        Ok(Box::new(ClockfaceViewAssistant::new()))
    }

    fn get_render_options(&self) -> RenderOptions {
        RenderOptions { use_spinel: self.use_spinel, ..RenderOptions::default() }
    }
}

struct RoundedLine {
    path: Path,
}

impl RoundedLine {
    fn new(mut path_builder: PathBuilder, pos: Point, length: f32, thickness: f32) -> Self {
        let radius = thickness / 2.0;
        let tl = pos.to_vector();
        let tr = pos.to_vector() + vec2(length, 0.0);
        let br = pos.to_vector() + vec2(length, thickness);
        let bl = pos.to_vector() + vec2(0.0, thickness);
        let radiush = vec2(radius, 0.0);
        let radiusv = vec2(0.0, radius);

        let path = {
            macro_rules! c {
                ( $v:expr ) => {
                    point2($v.x, $v.y)
                };
            }

            path_builder.move_to(c!(tl + radiush));
            path_builder.line_to(c!(tr - radiush));
            path_builder.rat_quad_to(c!(tr), c!(tr + radiusv), 0.7071);
            path_builder.rat_quad_to(c!(br), c!(br - radiush), 0.7071);
            path_builder.line_to(c!(bl + radiush));
            path_builder.rat_quad_to(c!(bl), c!(bl - radiusv), 0.7071);
            path_builder.rat_quad_to(c!(tl), c!(tl + radiush), 0.7071);

            path_builder.build()
        };

        Self { path }
    }
}

struct Hand {
    line: RoundedLine,
    raster: Option<Raster>,
    color: Color,
}

impl Hand {
    fn new(
        path_builder: PathBuilder,
        thickness: f32,
        length: f32,
        offset: f32,
        color: Color,
    ) -> Self {
        let line = RoundedLine::new(
            path_builder,
            point2(-(thickness / 2.0 + offset), -thickness / 2.0),
            length,
            thickness,
        );

        Self { line, raster: None, color }
    }

    fn update(&mut self, context: &mut RenderContext, scale: f32, angle: f32) {
        let rotation = Transform2D::rotation(Angle::radians(angle)).then_scale(scale, scale);
        let mut raster_builder = context.raster_builder().unwrap();
        raster_builder.add(&self.line.path, Some(&rotation));
        self.raster.replace(raster_builder.build());
    }
}

struct ClockFaceFacet {
    size: Size,
    hour_hand: Hand,
    minute_hand: Hand,
    second_hand: Hand,
    hour_index: usize,
    minute_index: usize,
    second_index: usize,
}

impl ClockFaceFacet {
    fn new(context: &mut RenderContext) -> Self {
        const HOUR_HAND_COLOR: Color = Color { r: 254, g: 72, b: 100, a: 255 };
        const MINUTE_HAND_COLOR: Color = Color { r: 255, g: 114, b: 132, a: 127 };
        const SECOND_HAND_COLOR: Color = Color::white();
        const RADIUS: f32 = 0.4;

        let thickness = RADIUS / 20.0;
        let offset = RADIUS / 5.0;
        let hour_hand = Hand::new(
            context.path_builder().unwrap(),
            thickness * 2.0,
            RADIUS,
            offset,
            HOUR_HAND_COLOR,
        );
        let minute_hand =
            Hand::new(context.path_builder().unwrap(), thickness, RADIUS, 0.0, MINUTE_HAND_COLOR);
        let second_hand = Hand::new(
            context.path_builder().unwrap(),
            thickness / 2.0,
            RADIUS + offset,
            offset,
            SECOND_HAND_COLOR,
        );

        Self {
            size: size2(1.0, 1.0),
            hour_hand,
            minute_hand,
            second_hand,
            hour_index: std::usize::MAX,
            minute_index: std::usize::MAX,
            second_index: std::usize::MAX,
        }
    }

    fn update(&mut self, context: &mut RenderContext, size: &Size, scale: f32) {
        if self.size != *size {
            self.size = *size;
            self.hour_index = std::usize::MAX;
            self.minute_index = std::usize::MAX;
            self.second_index = std::usize::MAX;
        }
        const MICROSECONDS_PER_SECOND: f32 = 1e+6;
        let now = Local::now();
        let (_is_pm, hour12) = now.hour12();
        let us = now.nanosecond() as f32 / 1000.0;
        let second = now.second() as f32 + us / MICROSECONDS_PER_SECOND;
        let minute = now.minute() as f32 + second / 60.0;
        let hour = hour12 as f32 + minute / 60.0;
        const R0: f32 = -0.25; // Rotate from 3 to 12.
        const STEPS: usize = 60 * 60; // Enough steps to ensure smooth movement
                                      // of second hand each frame on a 60hz display.
        let index = ((R0 + hour / 12.0).rem_euclid(1.0) * STEPS as f32) as usize;
        if index != self.hour_index {
            let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
            self.hour_hand.update(context, scale, angle);
            self.hour_index = index;
        }
        let index = ((R0 + minute / 60.0).rem_euclid(1.0) * STEPS as f32) as usize;
        if index != self.minute_index {
            let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
            self.minute_hand.update(context, scale, angle);
            self.minute_index = index;
        }
        let index = ((R0 + second / 60.0).rem_euclid(1.0) * STEPS as f32) as usize;
        if index != self.second_index {
            let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
            self.second_hand.update(context, scale, angle);
            self.second_index = index;
        }
    }
}

impl Facet for ClockFaceFacet {
    fn update_layers(
        &mut self,
        size: Size,
        layer_group: &mut LayerGroup,
        render_context: &mut RenderContext,
    ) -> std::result::Result<(), anyhow::Error> {
        const SHADOW_COLOR: Color = Color { r: 0, g: 0, b: 0, a: 13 };
        const ELEVATION: f32 = 0.01;

        let scale = size.width.min(size.height);
        self.size = size;

        self.update(render_context, &size, scale);

        let elevation = (ELEVATION * scale) as i32;
        let center = vec2(size.width as i32 / 2, size.height as i32 / 2);
        let shadow_offset = center + vec2(elevation, elevation * 2);

        let hands = [&self.hour_hand, &self.minute_hand, &self.second_hand];

        let layers = hands
            .iter()
            .map(|hand| Layer {
                raster: hand.raster.clone().unwrap().translate(center),
                style: Style {
                    fill_rule: FillRule::NonZero,
                    fill: Fill::Solid(hand.color),
                    blend_mode: BlendMode::Over,
                },
            })
            .chain(std::iter::once(Layer {
                raster: hands
                    .iter()
                    .fold(None, |raster_union: Option<Raster>, hand| {
                        let raster = hand.raster.clone().unwrap().translate(shadow_offset);
                        if let Some(raster_union) = raster_union {
                            Some(raster_union + raster)
                        } else {
                            Some(raster)
                        }
                    })
                    .unwrap(),
                style: Style {
                    fill_rule: FillRule::NonZero,
                    fill: Fill::Solid(SHADOW_COLOR),
                    blend_mode: BlendMode::Over,
                },
            }))
            .chain(std::iter::once(Layer {
                raster: hands
                    .iter()
                    .enumerate()
                    .fold(None, |raster_union: Option<Raster>, (i, hand)| {
                        if i != 1 {
                            let raster = hand.raster.clone().unwrap().translate(shadow_offset);
                            if let Some(raster_union) = raster_union {
                                Some(raster_union + raster)
                            } else {
                                Some(raster)
                            }
                        } else {
                            raster_union
                        }
                    })
                    .unwrap(),
                style: Style {
                    fill_rule: FillRule::NonZero,
                    fill: Fill::Solid(SHADOW_COLOR),
                    blend_mode: BlendMode::Over,
                },
            }));
        layer_group.replace_all(layers);
        Ok(())
    }

    fn get_size(&self) -> Size {
        self.size
    }
}

struct SceneDetails {
    scene: Scene,
}

struct ClockfaceViewAssistant {
    scene_details: Option<SceneDetails>,
}

impl ClockfaceViewAssistant {
    pub fn new() -> Self {
        Self { scene_details: None }
    }
}

impl ViewAssistant for ClockfaceViewAssistant {
    fn resize(&mut self, _new_size: &Size) -> Result<(), Error> {
        self.scene_details = None;
        Ok(())
    }

    fn render(
        &mut self,
        render_context: &mut RenderContext,
        ready_event: Event,
        context: &ViewAssistantContext,
    ) -> Result<(), Error> {
        let mut scene_details = self.scene_details.take().unwrap_or_else(|| {
            let mut builder = SceneBuilder::new().background_color(BACKGROUND_COLOR);
            let clock_face_facet = ClockFaceFacet::new(render_context);
            let _ = builder.facet(Box::new(clock_face_facet));
            SceneDetails { scene: builder.build() }
        });

        scene_details.scene.render(render_context, ready_event, context)?;

        self.scene_details = Some(scene_details);

        context.request_render();

        Ok(())
    }
}

fn main() -> Result<(), Error> {
    fuchsia_trace_provider::trace_provider_create_with_fdio();

    println!("Clockface Example");
    App::run(make_app_assistant::<ClockfaceAppAssistant>())
}
