/* 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 https://mozilla.org/MPL/2.0/. */

use std::cell::Cell;

use dom_struct::dom_struct;
use js::rust::MutableHandleValue;
use webxr_api::SubImages;

use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::window::Window;
use crate::dom::xrlayer::XRLayer;
use crate::dom::xrwebgllayer::XRWebGLLayer;
use crate::script_runtime::{CanGc, JSContext};

#[dom_struct]
pub(crate) struct XRRenderState {
    reflector_: Reflector,
    depth_near: Cell<f64>,
    depth_far: Cell<f64>,
    inline_vertical_fov: Cell<Option<f64>>,
    base_layer: MutNullableDom<XRWebGLLayer>,
    layers: DomRefCell<Vec<Dom<XRLayer>>>,
}

impl XRRenderState {
    pub(crate) fn new_inherited(
        depth_near: f64,
        depth_far: f64,
        inline_vertical_fov: Option<f64>,
        layer: Option<&XRWebGLLayer>,
        layers: Vec<&XRLayer>,
    ) -> XRRenderState {
        debug_assert!(layer.is_none() || layers.is_empty());
        XRRenderState {
            reflector_: Reflector::new(),
            depth_near: Cell::new(depth_near),
            depth_far: Cell::new(depth_far),
            inline_vertical_fov: Cell::new(inline_vertical_fov),
            base_layer: MutNullableDom::new(layer),
            layers: DomRefCell::new(layers.into_iter().map(Dom::from_ref).collect()),
        }
    }

    pub(crate) fn new(
        window: &Window,
        depth_near: f64,
        depth_far: f64,
        inline_vertical_fov: Option<f64>,
        layer: Option<&XRWebGLLayer>,
        layers: Vec<&XRLayer>,
        can_gc: CanGc,
    ) -> DomRoot<XRRenderState> {
        reflect_dom_object(
            Box::new(XRRenderState::new_inherited(
                depth_near,
                depth_far,
                inline_vertical_fov,
                layer,
                layers,
            )),
            window,
            can_gc,
        )
    }

    pub(crate) fn clone_object(&self) -> DomRoot<Self> {
        XRRenderState::new(
            self.global().as_window(),
            self.depth_near.get(),
            self.depth_far.get(),
            self.inline_vertical_fov.get(),
            self.base_layer.get().as_deref(),
            self.layers.borrow().iter().map(|x| &**x).collect(),
            CanGc::note(),
        )
    }

    pub(crate) fn set_depth_near(&self, depth: f64) {
        self.depth_near.set(depth)
    }
    pub(crate) fn set_depth_far(&self, depth: f64) {
        self.depth_far.set(depth)
    }
    pub(crate) fn set_inline_vertical_fov(&self, fov: f64) {
        debug_assert!(self.inline_vertical_fov.get().is_some());
        self.inline_vertical_fov.set(Some(fov))
    }
    pub(crate) fn set_base_layer(&self, layer: Option<&XRWebGLLayer>) {
        self.base_layer.set(layer)
    }
    pub(crate) fn set_layers(&self, layers: Vec<&XRLayer>) {
        *self.layers.borrow_mut() = layers.into_iter().map(Dom::from_ref).collect();
    }
    pub(crate) fn with_layers<F, R>(&self, f: F) -> R
    where
        F: FnOnce(&[Dom<XRLayer>]) -> R,
    {
        let layers = self.layers.borrow();
        f(&layers)
    }
    pub(crate) fn has_sub_images(&self, sub_images: &[SubImages]) -> bool {
        if let Some(base_layer) = self.base_layer.get() {
            match sub_images.len() {
                // For inline sessions, there may be a base layer, but it won't have a framebuffer
                0 => base_layer.layer_id().is_none(),
                // For immersive sessions, the base layer will have a framebuffer,
                // so we make sure the layer id's match up
                1 => base_layer.layer_id() == Some(sub_images[0].layer_id),
                _ => false,
            }
        } else {
            // The layers API is only for immersive sessions
            let layers = self.layers.borrow();
            sub_images.len() == layers.len() &&
                sub_images
                    .iter()
                    .zip(layers.iter())
                    .all(|(sub_image, layer)| Some(sub_image.layer_id) == layer.layer_id())
        }
    }
}

impl XRRenderStateMethods<crate::DomTypeHolder> for XRRenderState {
    /// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthnear>
    fn DepthNear(&self) -> Finite<f64> {
        Finite::wrap(self.depth_near.get())
    }

    /// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthfar>
    fn DepthFar(&self) -> Finite<f64> {
        Finite::wrap(self.depth_far.get())
    }

    /// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-inlineverticalfieldofview>
    fn GetInlineVerticalFieldOfView(&self) -> Option<Finite<f64>> {
        self.inline_vertical_fov.get().map(Finite::wrap)
    }

    /// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-baselayer>
    fn GetBaseLayer(&self) -> Option<DomRoot<XRWebGLLayer>> {
        self.base_layer.get()
    }

    /// <https://immersive-web.github.io/layers/#dom-xrrenderstate-layers>
    fn Layers(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
        // TODO: cache this array?
        let layers = self.layers.borrow();
        let layers: Vec<&XRLayer> = layers.iter().map(|x| &**x).collect();
        to_frozen_array(&layers[..], cx, retval, can_gc)
    }
}