use crate::{
    bvh::Bvh,
    either::Either,
    scene::PbrtScene,
    shape::{Shape, ShapeAlpha, ShapeType, TriangleMesh},
    tokenizer::{Token, Tokenizer},
};
use material::PbrtMaterial;
use miette::{IntoDiagnostic, Result, bail, miette};
use ray_tracing_core::{
    affine_transform::AffineTransform,
    color::Color,
    light::Light,
    math::{Dir3, Pos3},
    prelude::{Float, Rng},
};
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
    sync::Arc,
};
use texture::PbrtTexture;

#[macro_use]
mod tokenizer;

mod bvh;
mod either;
mod error;
mod material;
pub mod scene;
mod shape;
mod texture;

struct Lexer {
    input: Tokenizer,
}

impl Lexer {
    fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
        Ok(Self {
            input: Tokenizer::new(path, base_path)?,
        })
    }
}

#[derive(Debug)]
#[allow(dead_code)]
pub enum CameraType {
    Orthographic {
        frame_aspect_ratio: Option<Float>,
        screen_window: Option<Float>,
        lens_radius: Float,
        focal_distance: Float,
    },
    Perspective {
        frame_aspect_ratio: Option<Float>,
        screen_window: Option<Float>,
        lens_radius: Float,
        focal_distance: Float,
        fov: Float,
    },
}

#[derive(Debug)]
#[allow(dead_code)]
pub struct PbrtCamera {
    pub camera_type: CameraType,
    shutter_open: Float,
    shutter_close: Float,
}

#[derive(Debug)]
enum Statement<R> {
    AttributeBegin,
    AttributeEnd,
    WorldBegin,
    Camera(PbrtCamera),
    Include(String),
    ConcatTransform(AffineTransform),
    CoordinateSystem(String),
    CoordSysTransform(String),
    Shape(ShapeType, ShapeAlpha),
    Unknown(String, Vec<Token>),
    Transform(AffineTransform),
    Texture(String, Arc<dyn PbrtTexture>),
    Material(Arc<dyn PbrtMaterial<R>>),
    MakeNamedMaterial(String, Arc<dyn PbrtMaterial<R>>),
    NamedMaterial(String),
    AreaLight(AreaLight),
}

#[derive(Debug, Clone)]
pub struct AreaLight {
    pub color: Color,
}

impl<R: Rng> Light<R> for AreaLight {
    fn emit(&self, w_in: Dir3, _rng: &mut R) -> Color {
        if w_in.y() > 0.0 {
            self.color
        } else {
            Color::black()
        }
    }
}

fn parse_look_at<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
    let eye = Pos3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);
    let look_at = Pos3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);
    let up = Dir3::new(iter.parse_next()?, iter.parse_next()?, iter.parse_next()?);

    Ok(Statement::ConcatTransform(
        AffineTransform::look_at(eye, look_at, up)
            .ok_or(miette!("Unable to calculate inverse of matrix"))?,
    ))
}

fn parse_shape<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
    let shape_type = iter
        .next_if_string_value()
        .ok_or(miette!("unable to get shape type"))??;

    match shape_type.as_str() {
        "sphere" => {
            let t = parse_dict!(iter =>
                radius, Float, 1.0;
                zmin, Float, {-radius};
                zmax, Float, {radius};
                phimax, Float, 360.0;
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                radius, "float radius", iter.parse_parameter()?;
                zmin, "float zmin", iter.parse_parameter()?;
                zmax, "float zmax", iter.parse_parameter()?;
                phimax, "float phimax", iter.parse_parameter()?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );
            Ok(Statement::Shape(
                ShapeType::Sphere {
                    radius: t.radius,
                    zmin: t.zmin,
                    zmax: t.zmax,
                    phimax: t.phimax,
                },
                t.alpha,
            ))
        }
        "trianglemesh" => {
            let t = parse_dict!(iter =>
                p, Vec<Pos3>, Vec::new();
                n, Vec<Dir3>, Vec::new();
                s, Vec<Dir3>, Vec::new();
                uv, Vec<[Float; 2]>, Vec::new();
                indices, Vec<[usize;3]>, Vec::new();
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                p, "point3 P", iter.parse_list_3(Pos3::new)?;
                n, "normal N", iter.parse_list_3(Dir3::new)?;
                s, "normal S", iter.parse_list_3(Dir3::new)?;
                uv, "point2 uv", iter.parse_list_2(|u, v| [u, v])?;
                indices, "integer indices", iter.parse_list_3(|a, b, c|[a, b, c])?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );

            if t.p.len() < 3 {
                bail!("At least 3 points required.")
            }
            if t.indices.is_empty() && t.p.len() != 3 {
                bail!("Indices required for trianglemesh with more than 3 points.")
            }

            if !t.n.is_empty() && t.n.len() != t.p.len() {
                bail!("Number of normals not equal to number of positions.")
            }

            if !t.s.is_empty() && t.s.len() != t.p.len() {
                bail!("Number of tangents not equal to number of positions.")
            }
            if !t.uv.is_empty() && t.uv.len() != t.p.len() {
                bail!("Number of uvs not equal to number of positions.")
            }

            Ok(Statement::Shape(
                ShapeType::TriangleMesh(Bvh::new(
                    TriangleMesh {
                        indices: t.indices,
                        p: t.p,
                        n: t.n,
                        s: t.s,
                        uv: t.uv,
                    },
                    8,
                )),
                t.alpha,
            ))
        }
        "bilinearmesh" => {
            let t = parse_dict!(iter =>
                p, Vec<Pos3>, Vec::new();
                n, Vec<Dir3>, Vec::new();
                uv, Vec<[Float; 2]>, Vec::new();
                indices, Vec<usize>, Vec::new();
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                p, "point3 P", iter.parse_list_3(Pos3::new)?;
                n, "normal N", iter.parse_list_3(Dir3::new)?;
                uv, "point2 uv", iter.parse_list_2(|u, v| [u, v])?;
                indices, "integer indices", iter.parse_list()?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );

            if t.p.len() < 4 {
                bail!("At least 4 points required.")
            }
            if t.indices.is_empty() && t.p.len() != 4 {
                bail!("Indices required for trianglemesh with more than 4 points.")
            }

            if t.indices.len() % 4 != 0 {
                bail!(
                    "number of indices must be divisible by 4. num indices: {}",
                    t.indices.len()
                )
            }

            if !t.n.is_empty() && t.n.len() != t.p.len() {
                bail!("Number of normals not equal to number of positions.")
            }

            if !t.uv.is_empty() && t.uv.len() != t.p.len() {
                bail!("Number of uvs not equal to number of positions.")
            }

            Ok(Statement::Shape(
                ShapeType::BilinearMesh {
                    indices: t.indices,
                    p: t.p,
                    n: t.n,
                    uv: t.uv,
                },
                t.alpha,
            ))
        }
        "loopsubdiv" => {
            let t = parse_dict!(iter =>
                levels, u32, 3;
                indices, Vec<[usize;3]>, Vec::new();
                p, Vec<Pos3>, Vec::new();
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                levels, "integer levels", iter.parse_parameter()?;
                indices, "integer indices", iter.parse_list_3(|a, b, c|[a, b, c])?;
                p, "point3 P", iter.parse_list_3(Pos3::new)?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );

            if t.indices.is_empty() {
                bail!("indices are a required field")
            }

            if t.p.len() < 3 {
                bail!("at least 3 vertices required")
            }

            eprintln!(
                "WARNING: loopsubdiv is bodged. No subdivisions. levels: {}",
                t.levels
            );

            Ok(Statement::Shape(
                ShapeType::TriangleMesh(Bvh::new(
                    TriangleMesh {
                        indices: t.indices,
                        p: t.p,
                        n: Vec::new(),
                        s: Vec::new(),
                        uv: Vec::new(),
                    },
                    8,
                )),
                t.alpha,
            ))
        }
        "disk" => {
            let t = parse_dict!(iter =>
                height, Float, 0.0;
                radius, Float, 1.0;
                innerradius, Float, 0.0;
                phimax, Float, 360.0;
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                height, "float height", iter.parse_parameter()?;
                radius, "float radius", iter.parse_parameter()?;
                innerradius, "float innerradius", iter.parse_parameter()?;
                phimax, "float phimax", iter.parse_parameter()?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );

            Ok(Statement::Shape(
                ShapeType::Disk {
                    height: t.height,
                    radius: t.radius,
                    innerradius: t.innerradius,
                    phimax: t.phimax,
                },
                t.alpha,
            ))
        }
        "plymesh" => {
            let t = parse_dict!(iter =>
                filename, String, String::new();
                displacement, Option<String>, None;
                edgelength, Float, 1.0;
                alpha, ShapeAlpha, ShapeAlpha::None
                =>
                filename, "string filename", iter.parse_parameter()?;
                displacement, "string displacement", Some(iter.parse_parameter()?);
                edgelength, "float edgelength", iter.parse_parameter()?;
                alpha, "float alpha", ShapeAlpha::Value(iter.parse_parameter()?);
                alpha, "texture alpha", ShapeAlpha::Texture(iter.parse_parameter()?)
            );

            Ok(Statement::Shape(
                ShapeType::PlyMesh {
                    filename: t.filename,
                    displacement: t.displacement,
                    edgelength: t.edgelength,
                },
                t.alpha,
            ))
        }
        _ => Err(miette!("Unknown shape {}", shape_type)),
    }
}

fn parse_camera<R>(tokenizer: &mut Tokenizer) -> Result<Statement<R>> {
    let camera_type = tokenizer
        .next_if_string_value()
        .ok_or(miette!("unable to get shape type"))??;

    match camera_type.as_str() {
        "orthographic" => {
            let t = parse_dict!(tokenizer =>
                shutteropen, Float, 0.0;
                shutterclose, Float, 1.0;
                frame_aspect_ratio, Option<Float>, None;
                screen_window, Option<Float>, None;
                lens_radius, Float, 0.0;
                focal_distance, Float, Float::powi(10.0, 30)
                =>
                shutteropen, "float shutteropen", tokenizer.parse_parameter()?;
                shutterclose, "float shutterclose", tokenizer.parse_parameter()?;
                frame_aspect_ratio, "float frameaspectratio", Some(tokenizer.parse_parameter()?);
                screen_window, "float screenwindow", Some(tokenizer.parse_parameter()?);
                lens_radius, "float lensradius", tokenizer.parse_parameter()?;
                focal_distance, "float focaldistance", tokenizer.parse_parameter()?
            );

            Ok(Statement::Camera(PbrtCamera {
                camera_type: CameraType::Orthographic {
                    frame_aspect_ratio: t.frame_aspect_ratio,
                    screen_window: t.screen_window,
                    lens_radius: t.lens_radius,
                    focal_distance: t.focal_distance,
                },
                shutter_open: t.shutteropen,
                shutter_close: t.shutterclose,
            }))
        }
        "perspective" => {
            let t = parse_dict!(tokenizer =>
                shutteropen, Float, 0.0;
                shutterclose, Float, 1.0;
                frame_aspect_ratio, Option<Float>, None;
                screen_window, Option<Float>, None;
                lens_radius, Float, 0.0;
                focal_distance, Float, Float::powi(10.0, 30);
                fov, Float, 90.0
                =>
                shutteropen, "float shutteropen", tokenizer.parse_parameter()?;
                shutterclose, "float shutterclose", tokenizer.parse_parameter()?;
                frame_aspect_ratio, "float frameaspectratio", Some(tokenizer.parse_parameter()?);
                screen_window, "float screenwindow", Some(tokenizer.parse_parameter()?);
                lens_radius, "float lensradius", tokenizer.parse_parameter()?;
                focal_distance, "float focaldistance", tokenizer.parse_parameter()?;
                fov, "float fov", tokenizer.parse_parameter()?
            );

            Ok(Statement::Camera(PbrtCamera {
                camera_type: CameraType::Perspective {
                    frame_aspect_ratio: t.frame_aspect_ratio,
                    screen_window: t.screen_window,
                    lens_radius: t.lens_radius,
                    focal_distance: t.focal_distance,
                    fov: t.fov,
                },
                shutter_open: t.shutteropen,
                shutter_close: t.shutterclose,
            }))
        }
        _ => Err(miette!("Unknown camera_type {}", camera_type)),
    }
}

impl Lexer {
    fn next<R: Rng>(&mut self, context: &PbrtContext<R>) -> Option<Result<Statement<R>>> {
        match self.input.next() {
            Some(Ok(Token::Identifier(s))) => match s.as_str() {
                "AttributeBegin" => Some(Ok(Statement::AttributeBegin)),
                "AttributeEnd" => Some(Ok(Statement::AttributeEnd)),
                "Include" => {
                    let s = self
                        .input
                        .next_if_string_value()
                        .unwrap()
                        .unwrap()
                        .to_string();

                    Some(Ok(Statement::Include(s)))
                }
                "Camera" => Some(parse_camera(&mut self.input)),
                "LookAt" => Some(parse_look_at(&mut self.input)),
                "Identity" => Some(Ok(Statement::ConcatTransform(AffineTransform::identity()))),
                "Translate" => Some(parse_translate(&mut self.input)),
                "Scale" => Some(parse_scale(&mut self.input)),
                "Shape" => Some(parse_shape(&mut self.input)),
                "Rotate" => Some(parse_rotate(&mut self.input)),
                "Transform" => Some(parse_transform(&mut self.input).map(Statement::Transform)),
                "Texture" => Some(
                    texture::parse_texture(&mut self.input, context)
                        .map(|(name, texture)| Statement::Texture(name, texture)),
                ),
                "Material" => Some(
                    material::parse_material(&mut self.input, context).map(Statement::Material),
                ),
                "MakeNamedMaterial" => Some(
                    material::parse_make_named_material(&mut self.input, context)
                        .map(|(name, material)| Statement::MakeNamedMaterial(name, material)),
                ),
                "NamedMaterial" => {
                    Some(self.input.next_string_value().map(Statement::NamedMaterial))
                }
                "ConcatTransform" => {
                    Some(parse_transform(&mut self.input).map(Statement::ConcatTransform))
                }
                "CoordinateSystem" => Some(match self.input.parse_parameter() {
                    Ok(s) => Ok(Statement::CoordinateSystem(s)),
                    Err(e) => Err(e),
                }),
                "CoordSysTransform" => Some(match self.input.parse_parameter() {
                    Ok(s) => Ok(Statement::CoordSysTransform(s)),
                    Err(e) => Err(e),
                }),

                "AreaLightSource" => Some(parse_area_light(&mut self.input)),
                "WorldBegin" => Some(Ok(Statement::WorldBegin)),
                _ => {
                    if s.chars().any(|c| !c.is_ascii_alphabetic()) {
                        Some(Err(miette!("malformed identifier")))
                    } else {
                        let mut v = Vec::new();

                        while let Some(p) =
                            self.input.next_if(|s| !matches!(s, Token::Identifier(_)))
                        {
                            match p {
                                Ok(c) => v.push(c),
                                Err(e) => return Some(Err(e)),
                            }
                        }

                        Some(Ok(Statement::Unknown(s, v)))
                    }
                }
            },
            Some(Ok(s)) => Some(Err(miette!(
                labels = vec![self.input.last_span_labeled(Some("here"))],
                "expected identifier got {s:?}"
            ))),
            Some(Err(e)) => Some(Err(e)),
            None => None,
        }
    }
}

fn parse_area_light<R>(input: &mut Tokenizer) -> Result<Statement<R>> {
    let s = input.next_string_value()?;

    if s.as_str() != "diffuse" {
        return Err(miette!(
            labels = vec![input.last_span_labeled(Some("here"))],
            "Only diffuse area light supported."
        )
        .with_source_code(input.get_src()));
    }

    Ok(Statement::AreaLight(parse_dict2!(input, AreaLight;
        color, Color::white(), ["rgb L", texture::parse_rgb(input)?]
    )))
}

fn parse_transform(input: &mut Tokenizer) -> Result<AffineTransform> {
    input.next_expect_bracket_open()?;
    let mut v = [0.0; 16];

    for i in &mut v {
        *i = input.parse_next()?;
    }

    input.next_expect_bracket_close()?;

    if v[3] != 0.0 || v[7] != 0.0 || v[11] != 0.0 || v[15] != 1.0 {
        bail!("invalid transform entry")
    }

    AffineTransform::new([
        [v[0], v[4], v[8], v[12]],
        [v[1], v[5], v[9], v[13]],
        [v[2], v[6], v[10], v[14]],
        [v[3], v[7], v[11], v[15]],
    ])
    .ok_or(miette!("Unable to invert transformation"))
}

fn parse_translate<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
    let pos = Pos3::new(
        -iter.parse_next()?,
        -iter.parse_next()?,
        -iter.parse_next()?,
    );

    Ok(Statement::ConcatTransform(AffineTransform::translation(
        pos,
    )))
}

fn parse_scale<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
    Ok(Statement::ConcatTransform(AffineTransform::scale(
        iter.parse_next()?,
        iter.parse_next()?,
        iter.parse_next()?,
    )))
}

fn parse_rotate<R>(iter: &mut Tokenizer) -> Result<Statement<R>> {
    let angle = iter.parse_parameter()?;
    let dir = Dir3::new(
        iter.parse_parameter()?,
        iter.parse_parameter()?,
        iter.parse_parameter()?,
    );

    Ok(Statement::ConcatTransform(AffineTransform::rotation(
        angle, dir,
    )))
}
struct BytesToChar<I> {
    count: usize,
    iter: I,
}

impl<I> BytesToChar<I> {
    fn new(iter: I) -> Self {
        Self { count: 0, iter }
    }
}

impl<I: Iterator<Item = Result<u8, std::io::Error>>> Iterator for BytesToChar<I> {
    type Item = Result<(usize, char)>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.iter.next()? {
            Ok(a) => {
                self.count += 1;
                if a & 0x80 == 0 {
                    Some(Ok((self.count - 1, char::from(a))))
                } else {
                    todo!()
                }
            }
            Err(e) => Some(Err(e).into_diagnostic()),
        }
    }
}

struct Parser {
    path: PathBuf,
    base_path: PathBuf,
    inner: Option<Box<Parser>>,
    iter: Lexer,
}

impl Parser {
    fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
        Ok(Self {
            iter: Lexer::new(path.clone(), base_path.clone())?,
            base_path,
            path,
            inner: None,
        })
    }
}

impl Parser {
    fn next<R: Rng>(&mut self, context: &PbrtContext<R>) -> Option<Result<Statement<R>>> {
        if let Some(iter) = &mut self.inner {
            if let Some(statement) = iter.next(context) {
                return Some(statement);
            }
            self.inner = None;
        }

        match self.iter.next(context) {
            Some(Ok(Statement::Include(s))) => {
                let path = self.path.parent().unwrap().join(s);
                self.inner = Some(Box::new(Parser::new(path, self.base_path.clone()).unwrap()));

                self.next(context)
            }
            Some(s) => Some(s),
            None => None,
        }
    }
}

#[derive(Debug)]
pub struct Pbrt<R: Rng> {
    pub settings: PbrtWorldSettings,
    pub scene: PbrtScene<R>,
}

impl<R: Rng> Pbrt<R> {
    fn new(settings: PbrtWorldSettings, scene: PbrtScene<R>) -> Self {
        Self { settings, scene }
    }
}

#[derive(Debug)]
pub struct PbrtWorldSettings {
    pub camera: PbrtCamera,
    pub camera_ctm: AffineTransform,
}

#[derive(Debug)]
pub struct PbrtContext<R> {
    ctm: Vec<AffineTransform>,
    textures: HashMap<String, Arc<dyn PbrtTexture>>,
    material: Vec<Arc<dyn PbrtMaterial<R>>>,
    area_light: Vec<AreaLight>,
    materials: HashMap<String, Arc<dyn PbrtMaterial<R>>>,
}

impl<R> PbrtContext<R> {
    fn new() -> Self {
        Self {
            ctm: vec![AffineTransform::identity()],
            textures: HashMap::new(),
            area_light: Vec::new(),
            material: Vec::new(),
            materials: HashMap::new(),
        }
    }

    pub fn get_ctm(&self) -> AffineTransform {
        *self.ctm.last().unwrap()
    }

    pub fn get_texture(&self, name: &String) -> Option<&Arc<dyn PbrtTexture>> {
        self.textures.get(name)
    }

    pub fn get_named_material(&self, name: &String) -> Option<&Arc<dyn PbrtMaterial<R>>> {
        self.materials.get(name)
    }

    pub fn get_material(&self) -> Option<&Arc<dyn PbrtMaterial<R>>> {
        self.material.last()
    }

    fn push(&mut self) {
        self.ctm.push(*self.ctm.last().unwrap());

        if !self.material.is_empty() {
            self.material
                .push(Arc::clone(self.material.last().unwrap()));
        }

        if !self.area_light.is_empty() {
            self.area_light
                .push(self.area_light.last().unwrap().clone());
        }
    }

    fn pop(&mut self) -> Result<()> {
        self.ctm.pop();

        if self.ctm.is_empty() {
            return Err(miette!("Attributes do not matcch"));
        }

        self.material.pop();
        self.area_light.pop();

        Ok(())
    }
}

fn inner_parse_pbrt<R: Rng + std::fmt::Debug>(
    path: impl AsRef<Path> + std::fmt::Debug,
) -> Result<Pbrt<R>> {
    // unwrap on context.last() ok because context is never empty
    let mut context = PbrtContext::new();

    let mut parser = Parser::new(
        path.as_ref().to_path_buf(),
        path.as_ref()
            .parent()
            .ok_or_else(|| miette!("parent from file not found"))?
            .to_path_buf(),
    )?;

    // parse global settings

    let mut camera = None;

    let mut named_transforms = HashMap::new();

    loop {
        let p = parser.next(&context).ok_or_else(|| miette!(""))??;
        // dbg!(&p);
        match p {
            Statement::AttributeBegin => context.push(),
            Statement::AttributeEnd => {
                context.pop()?;
            }
            Statement::Include(_) => unreachable!(),
            Statement::ConcatTransform(affine_transform) => {
                *context.ctm.last_mut().unwrap() *= affine_transform
            }
            Statement::Transform(affine_transform) => {
                *context.ctm.last_mut().unwrap() = affine_transform
            }
            Statement::Unknown(s, _items) => {
                eprintln!("Unknown statement: {s}")
            }
            Statement::Camera(c) => {
                if camera.is_some() {
                    return Err(miette!("The camera can only be set once."));
                }
                camera = Some((c, context.get_ctm()));
                named_transforms.insert(String::from("camera"), context.get_ctm());
            }
            Statement::CoordinateSystem(s) => {
                named_transforms.insert(s, context.get_ctm());
            }
            Statement::CoordSysTransform(s) => {
                *context.ctm.last_mut().unwrap() = *named_transforms
                    .get(&s)
                    .ok_or_else(|| miette!("unknown transform"))?;
            }
            Statement::WorldBegin => break,
            s => bail!("unexpected statemnet in global settings: {s:?}"),
        }
    }

    let (camera, camera_ctm) = camera.ok_or(miette!("A camera has to be specified"))?;

    context.ctm = vec![AffineTransform::identity()];

    // let mut context_material = vec![];

    let mut shapes = Vec::new();

    while let Some(p) = parser.next(&context).transpose()? {
        match p {
            Statement::AttributeBegin => context.push(),
            Statement::AttributeEnd => {
                context.pop()?;
            }
            Statement::Include(_) => unreachable!(),
            Statement::ConcatTransform(affine_transform) => {
                *context.ctm.last_mut().unwrap() *= affine_transform
            }
            Statement::Transform(affine_transform) => {
                *context.ctm.last_mut().unwrap() = affine_transform
            }
            Statement::Shape(shape_type, shape_alpha) => {
                // dbg!(&context);
                if context.area_light.is_empty() {
                    shapes.push(Shape::new(
                        context.get_ctm(),
                        Either::A(Arc::clone(
                            context
                                .material
                                .last()
                                .ok_or_else(|| miette!("No material specified"))?,
                        )),
                        shape_type,
                        shape_alpha,
                    ));
                } else {
                    shapes.push(Shape::new(
                        context.get_ctm(),
                        Either::B(context.area_light.last().unwrap().clone()),
                        shape_type,
                        shape_alpha,
                    ));
                }
            }
            Statement::CoordinateSystem(s) => {
                named_transforms.insert(s, context.get_ctm());
            }
            Statement::CoordSysTransform(s) => {
                *context.ctm.last_mut().unwrap() = *named_transforms
                    .get(&s)
                    .ok_or_else(|| miette!("unknown transform"))?;
            }
            Statement::Material(m) => {
                if context.material.is_empty() {
                    context.material.push(m);
                } else {
                    *context.material.last_mut().unwrap() = m;
                }
            }
            Statement::AreaLight(l) => {
                if context.area_light.is_empty() {
                    context.area_light.push(l);
                } else {
                    *context.area_light.last_mut().unwrap() = l;
                }
            }
            Statement::MakeNamedMaterial(n, m) => {
                context.materials.insert(n, m);
            }
            Statement::NamedMaterial(n) => {
                let m = Arc::clone(
                    context
                        .get_named_material(&n)
                        .ok_or_else(|| miette!("Unknown named material {n}"))?,
                );
                if context.material.is_empty() {
                    context.material.push(m);
                } else {
                    *context.material.last_mut().unwrap() = m;
                }
            }
            Statement::Unknown(s, _items) => {
                eprintln!("Unknown statement: {s}")
            }
            Statement::Texture(name, texture) => {
                context.textures.insert(name, texture);
            }
            s => bail!("unexpected statemnet in world settings: {s:?}"),
        }
    }

    let pbrt = Pbrt::new(
        PbrtWorldSettings { camera, camera_ctm },
        PbrtScene {
            shapes: Bvh::new(shapes, 1),
            infinite_light: Some(scene::PbrtInfiniteLight {
                color: Color::white(),
            }),
        },
    );
    // dbg!(context);

    Ok(pbrt)
}

pub fn parse_pbrt_v4<R: Rng + std::fmt::Debug>(
    path: impl AsRef<Path> + std::fmt::Debug,
) -> Result<Pbrt<R>> {
    inner_parse_pbrt(path)
}
