/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::frame_builder::FrameBuildingContext;
use crate::internal_types::FastHashMap;
use crate::prim_store::PictureIndex;
use crate::picture::{PicturePrimitive, SurfaceIndex, SurfaceInfo};
use crate::picture::{TileCacheInstance, SliceId};
use smallvec::SmallVec;

#[derive(Debug)]
pub struct PictureInfo {
    pub update_pass: Option<usize>,
    pub surface_index: Option<SurfaceIndex>,
    pub parent: Option<PictureIndex>,
}

#[derive(Default)]
/// A graph of picture dependencies, allowing updates to be processed without recursion
/// by building a list of passes.
pub struct PictureGraph {
    roots: Vec<PictureIndex>,
    pic_info: Vec<PictureInfo>,
    update_passes: Vec<Vec<PictureIndex>>,
}

impl PictureGraph {
    pub fn new() -> Self {
        PictureGraph::default()
    }

    pub fn reset(&mut self) {
        self.roots.clear();
        self.pic_info.clear();
        self.update_passes.clear();
    }

    /// Add a root picture to the graph
    pub fn add_root(
        &mut self,
        pic_index: PictureIndex,
    ) {
        self.roots.push(pic_index);
    }

    /// Build a list of update passes based on the dependencies between pictures
    pub fn build_update_passes(
        &mut self,
        pictures: &mut [PicturePrimitive],
        frame_context: &FrameBuildingContext
    ) {
        self.pic_info.clear();
        self.pic_info.reserve(pictures.len());

        for _ in 0 .. pictures.len() {
            self.pic_info.push(PictureInfo {
                update_pass: None,
                parent: None,
                surface_index: None,
            })
        };

        let mut max_pass_index = 0;

        for pic_index in &self.roots {
            assign_update_pass(
                *pic_index,
                None,
                0,
                pictures,
                &mut self.pic_info,
                &mut max_pass_index,
                frame_context,
            );
        }

        let pass_count = max_pass_index + 1;

        self.update_passes.clear();
        self.update_passes.resize_with(pass_count, Vec::new);

        for (pic_index, info) in self.pic_info.iter().enumerate() {
            if let Some(update_pass) = info.update_pass {
                let pass = &mut self.update_passes[update_pass];
                pass.push(PictureIndex(pic_index));
            }
        }
    }

    /// Assign surfaces and scale factors to each picture (root -> leaf ordered pass)
    pub fn assign_surfaces(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut Vec<SurfaceInfo>,
        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
        frame_context: &FrameBuildingContext,
    ) {
        for pass in &self.update_passes {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let parent_surface_index = parent.map(|parent| {
                    // Can unwrap here as by the time we have a parent that parent's
                    // surface must have been assigned.
                    self.pic_info[parent.0].surface_index.unwrap()
                });

                let info = &mut self.pic_info[pic_index.0];

                match pictures[pic_index.0].assign_surface(
                    frame_context,
                    parent_surface_index,
                    tile_caches,
                    surfaces,
                ) {
                    Some(surface_index) => {
                        info.surface_index = Some(surface_index);
                    }
                    None => {
                        info.surface_index = Some(parent_surface_index.unwrap());
                    }
                }
            }
        }
    }

    /// Propegate bounding rects from pictures to parents (leaf -> root ordered pass)
    pub fn propagate_bounding_rects(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut [SurfaceInfo],
        frame_context: &FrameBuildingContext,
    ) {
        for pass in self.update_passes.iter().rev() {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let surface_index = self.pic_info[pic_index.0]
                    .surface_index
                    .expect("bug: no surface assigned during propagate_bounding_rects");

                let parent_surface_index = parent.map(|parent| {
                    // Can unwrap here as by the time we have a parent that parent's
                    // surface must have been assigned.
                    self.pic_info[parent.0].surface_index.unwrap()
                });

                pictures[pic_index.0].propagate_bounding_rect(
                    surface_index,
                    parent_surface_index,
                    surfaces,
                    frame_context,
                );
            }
        }
    }
}

/// Recursive function that assigns pictures to the earliest pass possible that they
/// can be processed in, while maintaining dependency ordering.
fn assign_update_pass(
    pic_index: PictureIndex,
    parent_pic_index: Option<PictureIndex>,
    pass: usize,
    pictures: &mut [PicturePrimitive],
    pic_info: &mut [PictureInfo],
    max_pass_index: &mut usize,
    frame_context: &FrameBuildingContext
) {
    let pic = &mut pictures[pic_index.0];
    let info = &mut pic_info[pic_index.0];

    info.parent = parent_pic_index;

    // Run pre-update to resolve animation properties etc
    pic.pre_update(frame_context);

    let can_be_drawn = match info.update_pass {
        Some(update_pass) => {
            // No point in recursing into paths in the graph if this picture already
            // has been set to update after this pass.
            if update_pass > pass {
                return;
            }

            true
        }
        None => {
            // Check if this picture can be dropped from the graph we're building this frame
            pic.is_visible(frame_context.spatial_tree)
        }
    };

    if can_be_drawn {
        info.update_pass = Some(pass);

        *max_pass_index = pass.max(*max_pass_index);

        let mut child_pictures: SmallVec<[PictureIndex; 8]> = SmallVec::new();
        child_pictures.extend_from_slice(&pic.prim_list.child_pictures);

        for child_pic_index in child_pictures {
            assign_update_pass(
                child_pic_index,
                Some(pic_index),
                pass + 1,
                pictures,
                pic_info,
                max_pass_index,
                frame_context,
            );
        }
    }
}
