use super::BasicScene;
use super::entities::*;
use crate::Arena;
use crate::spectra::get_colorspace_device;
use crate::utils::error::FileLoc;
use crate::utils::normalize_utf8;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::{ParserError, ParserTarget};
use shared::Float;
use shared::core::camera::CameraTransform;
use shared::core::geometry::Vector3f;
use shared::spectra::RGBColorSpace;
use shared::utils::options::RenderingCoordinateSystem;
use shared::utils::transform;
use shared::utils::transform::{AnimatedTransform, Transform};
use std::collections::{HashMap, HashSet};
use std::ops::{Index, IndexMut};
use std::sync::Arc;

const MAX_TRANSFORMS: usize = 2;

#[derive(Debug, Default, Clone, Copy)]
struct TransformSet {
    t: [Transform; MAX_TRANSFORMS],
}

impl TransformSet {
    fn is_animated(&self) -> bool {
        self.t[0] != self.t[1]
    }

    fn inverse(&self) -> Self {
        Self {
            t: [self.t[0].inverse(), self.t[1].inverse()],
        }
    }
}

impl Index<usize> for TransformSet {
    type Output = Transform;
    fn index(&self, i: usize) -> &Self::Output {
        &self.t[i]
    }
}

impl IndexMut<usize> for TransformSet {
    fn index_mut(&mut self, i: usize) -> &mut Self::Output {
        &mut self.t[i]
    }
}

#[derive(Clone, Debug)]
struct PendingAreaLight {
    name: String,
    params: ParsedParameterVector,
    loc: FileLoc,
}

#[derive(Default, Debug, Clone)]
struct GraphicsState {
    pub current_inside_medium: String,
    pub current_outside_medium: String,
    pub current_material_name: String,
    pub current_material_index: Option<usize>,
    pub pending_area_light: Option<PendingAreaLight>,
    pub shape_attributes: ParsedParameterVector,
    pub light_attributes: ParsedParameterVector,
    pub material_attributes: ParsedParameterVector,
    pub medium_attributes: ParsedParameterVector,
    pub texture_attributes: ParsedParameterVector,
    pub reverse_orientation: bool,
    pub color_space: Option<Arc<RGBColorSpace>>,
    pub ctm: TransformSet,
    pub active_transform_bits: u32,
    pub transform_start_time: Float,
    pub transform_end_time: Float,
}

#[derive(PartialEq, Eq)]
enum BlockState {
    OptionsBlock,
    WorldBlock,
}

pub struct BasicSceneBuilder {
    scene: Arc<BasicScene>,
    current_block: BlockState,
    graphics_state: GraphicsState,
    pushed_graphics_states: Vec<GraphicsState>,
    push_stack: Vec<(char, FileLoc)>,
    render_from_world: Transform,
    named_coordinate_systems: HashMap<String, TransformSet>,
    active_instance_definition: Option<InstanceDefinitionSceneEntity>,

    float_texture_names: HashSet<String>,
    spectrum_texture_names: HashSet<String>,
    named_material_names: HashSet<String>,
    medium_names: HashSet<String>,

    current_camera: Option<CameraSceneEntity>,
    current_film: Option<SceneEntity>,
    current_integrator: Option<SceneEntity>,
    current_sampler: Option<SceneEntity>,
    current_filter: Option<SceneEntity>,
    current_accelerator: Option<SceneEntity>,
}

impl BasicSceneBuilder {
    pub const START_TRANSFORM_BITS: u32 = 1 << 0;
    pub const END_TRANSFORM_BITS: u32 = 1 << 1;
    pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1;

    pub fn new(scene: Arc<BasicScene>) -> Self {
        Self {
            scene,
            current_block: BlockState::OptionsBlock,
            graphics_state: GraphicsState {
                active_transform_bits: Self::ALL_TRANSFORM_BITS,
                ..Default::default()
            },
            pushed_graphics_states: Vec::new(),
            push_stack: Vec::new(),
            render_from_world: Transform::identity(),
            named_coordinate_systems: HashMap::new(),
            active_instance_definition: None,
            float_texture_names: HashSet::new(),
            spectrum_texture_names: HashSet::new(),
            named_material_names: HashSet::new(),
            medium_names: HashSet::new(),
            current_camera: Some(CameraSceneEntity {
                base: SceneEntity {
                    name: "perspective".into(),
                    ..Default::default()
                },
                camera_transform: CameraTransform::from_world(
                    AnimatedTransform::default(),
                    RenderingCoordinateSystem::World,
                ),
                medium: String::new(),
            }),
            current_sampler: Some(SceneEntity {
                name: "zsobol".into(),
                ..Default::default()
            }),
            current_filter: Some(SceneEntity {
                name: "gaussian".into(),
                ..Default::default()
            }),
            current_integrator: Some(SceneEntity {
                name: "volpath".into(),
                ..Default::default()
            }),
            current_accelerator: Some(SceneEntity {
                name: "bvh".into(),
                ..Default::default()
            }),
            current_film: Some(SceneEntity {
                name: "rgb".into(),
                ..Default::default()
            }),
        }
    }

    fn for_active_transforms<F>(&mut self, f: F)
    where
        F: Fn(&Transform) -> Transform,
    {
        let bits = self.graphics_state.active_transform_bits;
        if (bits & 1) != 0 {
            self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]);
        }
        if (bits & 2) != 0 {
            self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]);
        }
    }

    fn verify_world(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> {
        if self.current_block != BlockState::WorldBlock {
            return Err(ParserError::Generic(
                format!("{} not allowed outside WorldBlock", name),
                loc.clone(),
            ));
        }
        Ok(())
    }

    fn verify_options(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> {
        if self.current_block != BlockState::OptionsBlock {
            return Err(ParserError::Generic(
                format!("{} not allowed inside WorldBlock", name),
                loc.clone(),
            ));
        }
        Ok(())
    }

    fn make_params(
        &self,
        params: ParsedParameterVector,
        loc: &FileLoc,
    ) -> Result<ParameterDictionary, ParserError> {
        ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone())
            .map_err(|e| ParserError::Generic(format!("parameter error: {}", e), loc.clone()))
    }
}

impl From<anyhow::Error> for ParserError {
    fn from(e: anyhow::Error) -> Self {
        ParserError::Generic(e.to_string(), FileLoc::default())
    }
}

impl ParserTarget for BasicSceneBuilder {
    fn reverse_orientation(&mut self, loc: FileLoc) -> Result<(), ParserError> {
        self.verify_world("ReverseOrientation", &loc)?;
        self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation;
        Ok(())
    }

    fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> {
        let stdcs = get_colorspace_device();
        let _ = match stdcs.get_named(name) {
            Some(cs) => {
                self.graphics_state.color_space = unsafe { Some(Arc::new(*cs.as_ref())) };
            }
            None => {
                eprintln!("Error: Color space '{}' unknown at {}", name, loc);
            }
        };
        Ok(())
    }

    fn identity(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
        self.for_active_transforms(|_| Transform::identity());
        Ok(())
    }

    fn translate(
        &mut self,
        dx: Float,
        dy: Float,
        dz: Float,
        _loc: FileLoc,
    ) -> Result<(), ParserError> {
        let t = Transform::translate(Vector3f::new(dx, dy, dz));
        self.for_active_transforms(|cur| cur * &t);
        Ok(())
    }

    fn rotate(
        &mut self,
        angle: Float,
        ax: Float,
        ay: Float,
        az: Float,
        _loc: FileLoc,
    ) -> Result<(), ParserError> {
        let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
        self.for_active_transforms(|cur| cur * &t);
        Ok(())
    }

    fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) -> Result<(), ParserError> {
        let t = Transform::scale(sx, sy, sz);
        self.for_active_transforms(|cur| cur * &t);
        Ok(())
    }

    fn look_at(
        &mut self,
        ex: Float,
        ey: Float,
        ez: Float,
        lx: Float,
        ly: Float,
        lz: Float,
        ux: Float,
        uy: Float,
        uz: Float,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
        match result {
            Some(t) => {
                self.for_active_transforms(|cur| cur * &t);
            }
            None => {
                eprintln!("Error: Could not invert transform at {}", loc);
            }
        }
        Ok(())
    }

    fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> {
        let result = Transform::from_flat(m);
        match result {
            Some(t) => {
                self.for_active_transforms(|cur| cur * &t);
            }
            None => {
                eprintln!("Error: Could not invert transform at {}", loc);
            }
        }
        Ok(())
    }

    fn transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> {
        let result = Transform::from_flat(m);
        match result {
            Some(t) => {
                self.for_active_transforms(|_| t);
            }
            None => {
                eprintln!("Error: Could not invert transform at {}", loc);
            }
        }
        Ok(())
    }

    fn coordinate_system(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> {
        self.named_coordinate_systems
            .insert(name.to_string(), self.graphics_state.ctm);
        Ok(())
    }

    fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> {
        if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
            self.graphics_state.ctm = *saved_ctm;
        } else {
            eprintln!(
                "Warning: Couldn't find named coordinate system \"{}\" at {}",
                name, loc
            );
        }
        Ok(())
    }

    fn camera(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("Camera", &loc)?;

        let camera_from_world = self.graphics_state.ctm;

        let world_from_camera = camera_from_world.inverse();

        self.named_coordinate_systems
            .insert("camera".to_string(), world_from_camera);

        let animated_world_from_cam = AnimatedTransform::new(
            &world_from_camera.t[0],
            self.graphics_state.transform_start_time,
            &world_from_camera.t[1],
            self.graphics_state.transform_end_time,
        );

        let rendering_space = RenderingCoordinateSystem::CameraWorld;
        let camera_transform =
            CameraTransform::from_world(animated_world_from_cam, rendering_space);
        self.render_from_world = camera_from_world.t[0];

        let parameters = self.make_params(params, &loc)?;

        self.current_camera = Some(CameraSceneEntity {
            base: SceneEntity {
                name: name.to_string(),
                loc,
                parameters,
            },
            camera_transform,

            medium: self.graphics_state.current_outside_medium.clone(),
        });
        Ok(())
    }

    fn active_transform_all(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
        self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
        Ok(())
    }

    fn active_transform_end_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
        self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS;
        Ok(())
    }
    fn active_transform_start_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
        self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
        Ok(())
    }

    fn transform_times(
        &mut self,
        start: Float,
        end: Float,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("TransformTimes", &loc)?;
        self.graphics_state.transform_start_time = start;
        self.graphics_state.transform_end_time = end;
        Ok(())
    }

    fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) -> Result<(), ParserError> {
        todo!()
    }

    fn pixel_filter(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("PixelFilter", &loc)?;
        let parameters = self.make_params(params, &loc)?;
        self.current_filter = Some(SceneEntity {
            name: name.to_string(),
            loc,
            parameters,
        });
        Ok(())
    }

    fn film(
        &mut self,
        type_name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("Film", &loc)?;
        let parameters = self.make_params(params, &loc)?;
        self.current_film = Some(SceneEntity {
            name: type_name.to_string(),
            loc,
            parameters,
        });
        Ok(())
    }

    fn accelerator(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("Accelerator", &loc)?;
        let parameters = self.make_params(params, &loc)?;
        self.current_accelerator = Some(SceneEntity {
            name: name.to_string(),
            loc,
            parameters,
        });
        Ok(())
    }

    fn integrator(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("Integrator", &loc)?;
        let parameters = self.make_params(params, &loc)?;
        self.current_integrator = Some(SceneEntity {
            name: name.to_string(),
            loc,
            parameters,
        });
        Ok(())
    }

    fn make_named_medium(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_world("MakeNamedMaterial", &loc)?;
        let curr_name = normalize_utf8(name);

        if !self.named_material_names.insert(curr_name.to_string()) {
            return Err(ParserError::Generic(
                format!("Named material '{}' redefined.", name),
                loc,
            ));
        }
        let parameters = self.make_params(params, &loc)?;
        let entity = SceneEntity {
            name: name.to_string(),
            loc,
            parameters,
        };

        self.scene.add_named_material(&curr_name, entity);
        Ok(())
    }

    fn medium_interface(
        &mut self,
        inside_name: &str,
        outside_name: &str,
        _loc: FileLoc,
    ) -> Result<(), ParserError> {
        let inside = normalize_utf8(inside_name);
        let outside = normalize_utf8(outside_name);

        self.graphics_state.current_inside_medium = inside;
        self.graphics_state.current_outside_medium = outside;
        Ok(())
    }

    fn sampler(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_options("Sampler", &loc)?;
        let parameters = self.make_params(params, &loc)?;
        self.current_sampler = Some(SceneEntity {
            name: name.to_string(),
            loc,
            parameters,
        });
        Ok(())
    }

    fn world_begin(&mut self, loc: FileLoc, arena: Arc<Arena>) -> Result<(), ParserError> {
        self.verify_options("WorldBegin", &loc)?;
        self.current_block = BlockState::WorldBlock;
        for i in 0..MAX_TRANSFORMS {
            self.graphics_state.ctm[i] = Transform::default();
        }
        self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
        self.named_coordinate_systems
            .insert("world".to_string(), self.graphics_state.ctm);
        let scene = Arc::clone(&self.scene);
        scene.set_options(
            self.current_filter
                .take()
                .expect("Filter not set before WorldBegin"),
            self.current_film
                .take()
                .expect("Film not set before WorldBegin"),
            self.current_camera
                .take()
                .expect("Camera not set before WorldBegin"),
            self.current_sampler
                .take()
                .expect("Sampler not set before WorldBegin"),
            self.current_integrator
                .take()
                .expect("Integrator not set before WorldBegin"),
            self.current_accelerator
                .take()
                .expect("Accelerator not set before WorldBegin"),
            &arena,
        );
        Ok(())
    }

    fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError> {
        self.verify_world("AttributeBegin", &loc)?;
        self.pushed_graphics_states
            .push(self.graphics_state.clone());
        self.push_stack.push(('a', loc));
        Ok(())
    }

    fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError> {
        self.verify_world("AttributeEnd", &loc)?;
        if self.pushed_graphics_states.is_empty() {
            return Err(ParserError::Generic(
                "Unmatched AttributeEnd encountered".into(),
                loc,
            ));
        }

        if let Some(state) = self.pushed_graphics_states.pop() {
            self.graphics_state = state;
        }

        if let Some((kind, start_loc)) = self.push_stack.pop() {
            if kind == 'o' {
                return Err(ParserError::Generic(
                    format!("Mismatched nesting: open ObjectBegin from {}", start_loc),
                    loc,
                ));
            } else {
                debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack");
            }
        }

        Ok(())
    }

    fn attribute(
        &mut self,
        target: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        let current_attributes = match target {
            "shape" => &mut self.graphics_state.shape_attributes,
            "light" => &mut self.graphics_state.light_attributes,
            "material" => &mut self.graphics_state.material_attributes,
            "medium" => &mut self.graphics_state.medium_attributes,
            "texture" => &mut self.graphics_state.texture_attributes,
            _ => {
                return Err(ParserError::Generic(
                    format!(
                        "Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
                \"material\", \"medium\", or \"texture\".",
                        target
                    ),
                    loc,
                ));
            }
        };

        let active_color_space = self.graphics_state.color_space.clone();

        for mut p in params {
            p.may_be_unused = true;
            p.color_space = active_color_space.clone();

            current_attributes.push(p.clone());
        }
        Ok(())
    }

    fn texture(
        &mut self,
        orig_name: &str,
        type_name: &str,
        tex_name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
        arena: Arc<Arena>,
    ) -> Result<(), ParserError> {
        eprintln!("TEXTURE: name='{}' type='{}' tex='{}'", orig_name, type_name, tex_name);
        let name = normalize_utf8(orig_name);
        self.verify_world("Texture", &loc)?;
        let dict = ParameterDictionary::from_array(
            params.clone(),
            &self.graphics_state.texture_attributes,
            self.graphics_state.color_space.clone(),
        )?;

        if type_name != "float" && type_name != "spectrum" {
            return Err(ParserError::Generic(
                format!(
                    "{}: texture type unknown. Must be \"float\" or \"spectrum\".",
                    tex_name
                ),
                loc,
            ));
        }

        {
            let names = if type_name == "float" {
                &mut self.float_texture_names
            } else {
                &mut self.spectrum_texture_names
            };

            if names.contains(&name) {
                return Err(ParserError::Generic(
                    format!("Redefining texture \"{}\".", name),
                    loc,
                ));
            }
            names.insert(name.to_string());
        }

        let base = SceneEntity {
            name: tex_name.to_string(),
            parameters: dict,
            loc,
        };
        let entity = TextureSceneEntity {
            base,
            render_from_object: AnimatedTransform::from_transform(&self.graphics_state.ctm[0]),
        };

        if type_name == "float" {
            self.scene
                .add_float_texture(name.to_string(), entity, arena);
        } else {
            self.scene
                .add_spectrum_texture(name.to_string(), entity, arena);
        }

        Ok(())
    }

    fn material(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_world("material", &loc)?;
        let entity = SceneEntity {
            name: name.to_string(),
            loc,
            parameters: ParameterDictionary::new(params.clone(), None).unwrap(),
        };
        self.graphics_state.current_material_name = self.scene.add_material(entity).to_string();
        Ok(())
    }
    fn make_named_material(
        &mut self,
        _name: &str,
        _params: ParsedParameterVector,
        _loc: FileLoc,
    ) -> Result<(), ParserError> {
        todo!()
    }

    fn named_material(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
        todo!()
    }

    fn light_source(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_world("LightSource", &loc)?;
        let dict = ParameterDictionary::from_array(
            params.clone(),
            &self.graphics_state.medium_attributes,
            self.graphics_state.color_space.clone(),
        )?;

        let render_from_light = AnimatedTransform::new(
            &self.graphics_state.ctm.t[0],
            self.graphics_state.transform_start_time,
            &self.graphics_state.ctm.t[1],
            self.graphics_state.transform_end_time,
        );

        let entity = LightSceneEntity {
            transformed_base: TransformedSceneEntity {
                base: SceneEntity {
                    name: name.to_string(),
                    loc,
                    parameters: dict,
                },
                render_from_object: render_from_light,
            },
            medium: self.graphics_state.current_outside_medium.clone(),
        };

        self.scene.add_light(entity);
        Ok(())
    }

    fn area_light_source(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_world("AreaLightSource", &loc)?;
        self.graphics_state.pending_area_light = Some(PendingAreaLight {
            name: name.to_string(),
            params: params.clone(),
            loc,
        });
        Ok(())
    }

    fn shape(
        &mut self,
        name: &str,
        params: ParsedParameterVector,
        loc: FileLoc,
    ) -> Result<(), ParserError> {
        self.verify_world("Shape", &loc)?;

        let dict = ParameterDictionary::from_array(
            params.clone(),
            &self.graphics_state.shape_attributes,
            self.graphics_state.color_space.clone(),
        )?;

        let render_from_object = self.graphics_state.ctm[0];
        let object_from_render = render_from_object.inverse();

        let light_index = if let Some(ref al) = self.graphics_state.pending_area_light {
            let al_dict = self.make_params(al.params.clone(), &al.loc)?;
            let light_entity = SceneEntity {
                name: al.name.clone(),
                loc: al.loc.clone(),
                parameters: al_dict,
            };
            Some(self.scene.add_area_light(light_entity))
        } else {
            None
        };

        let material = if !self.graphics_state.current_material_name.is_empty() {
            MaterialRef::Name(self.graphics_state.current_material_name.clone())
        } else if let Some(idx) = self.graphics_state.current_material_index {
            MaterialRef::Index(idx)
        } else {
            MaterialRef::None
        };

        let entity = ShapeSceneEntity {
            base: SceneEntity {
                name: name.to_string(),
                loc,
                parameters: dict,
            },
            render_from_object: Arc::new(render_from_object),
            object_from_render: Arc::new(object_from_render),
            reverse_orientation: self.graphics_state.reverse_orientation,
            material,
            light_index,
            inside_medium: self.graphics_state.current_inside_medium.clone(),
            outside_medium: self.graphics_state.current_outside_medium.clone(),
        };

        if self.active_instance_definition.is_some() {
            self.active_instance_definition
                .as_mut()
                .unwrap()
                .shapes
                .push(entity)
        } else {
            self.scene.add_shape(entity);
        }

        Ok(())
    }
    fn object_begin(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
        Ok(())
    }
    fn object_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
        Ok(())
    }
    fn object_instance(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
        Ok(())
    }
    fn end_of_files(&mut self) -> Result<(), ParserError> {
        Ok(())
    }
}
