use bytemuck;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver};
use std::time::Instant;
use tracy_client::{frame_mark, set_thread_name, span};
use wgpu::util::DeviceExt;
use winit::{
    event::*,
    event_loop::{EventLoopBuilder, EventLoopProxy},
    window::Window,
    window::WindowBuilder,
};

mod script;
use script::graphics::GraphicsCommand;
mod text;
mod texture;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
    position: [f32; 3], // TODO: Why do I pass in a Z here?
    tex_coords: [f32; 2],
}

const TEXT_SCALE: f32 = 4.0;

impl Vertex {
    fn desc() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &[
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 0,
                    format: wgpu::VertexFormat::Float32x3,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
                    shader_location: 1,
                    format: wgpu::VertexFormat::Float32x2,
                },
            ],
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteInstance {
    src_top_left: [f32; 2],
    src_dims: [f32; 2],

    dest_top_left: [f32; 2],
    dest_dims: [f32; 2],
}

impl SpriteInstance {
    fn desc() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &[
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 5,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
                    shader_location: 6,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
                    shader_location: 7,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
                    shader_location: 8,
                    format: wgpu::VertexFormat::Float32x2,
                },
            ],
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CircleInstance {
    center: [f32; 2],
    radius: f32,
    stroke_width: f32,
    stroke_color: [f32; 4],
    fill_color: [f32; 4],
}

impl CircleInstance {
    fn desc() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &[
                wgpu::VertexAttribute {
                    // center
                    offset: 0,
                    shader_location: 5,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    // radius
                    offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
                    shader_location: 6,
                    format: wgpu::VertexFormat::Float32,
                },
                wgpu::VertexAttribute {
                    // stroke_width
                    offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<f32>())
                        as wgpu::BufferAddress,
                    shader_location: 7,
                    format: wgpu::VertexFormat::Float32,
                },
                wgpu::VertexAttribute {
                    // stroke_color
                    offset: (std::mem::size_of::<[f32; 2]>()
                        + std::mem::size_of::<f32>()
                        + std::mem::size_of::<f32>())
                        as wgpu::BufferAddress,
                    shader_location: 8,
                    format: wgpu::VertexFormat::Float32x4,
                },
                wgpu::VertexAttribute {
                    // fill_color
                    offset: (std::mem::size_of::<[f32; 2]>()
                        + std::mem::size_of::<f32>()
                        + std::mem::size_of::<f32>()
                        + std::mem::size_of::<[f32; 4]>())
                        as wgpu::BufferAddress,
                    shader_location: 9,
                    format: wgpu::VertexFormat::Float32x4,
                },
            ],
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlyphInstance {
    // TODO: If it becomes important I can load the cell information onto the
    //       GPU in a texture or something and then just specify the top-left
    //       and index.
    src_top_left: [f32; 2],
    src_dims: [f32; 2],

    dest_top_left: [f32; 2],
    dest_dims: [f32; 2],

    color: [f32; 4],
}

impl GlyphInstance {
    fn desc() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &[
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 5,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
                    shader_location: 6,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
                    shader_location: 7,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
                    shader_location: 8,
                    format: wgpu::VertexFormat::Float32x2,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
                    shader_location: 9,
                    format: wgpu::VertexFormat::Float32x4,
                },
            ],
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct ScreenUniforms {
    resolution: [f32; 2],
}

impl ScreenUniforms {
    fn new(width: u32, height: u32) -> ScreenUniforms {
        ScreenUniforms {
            resolution: [width as f32, height as f32],
        }
    }
}

#[derive(Debug)]
struct VertexBuffer<T> {
    buffer: wgpu::Buffer,
    vec: Vec<T>,
    capacity: usize,
}

#[derive(Clone, Debug)]
struct VertexBufferHandle {
    index: usize,
    capacity: usize,
}

struct WindowAndDevice {
    pub window: Window,
    // pub instance: wgpu::Instance,
    pub surface: wgpu::Surface,
    pub adapter: wgpu::Adapter,
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
}

impl WindowAndDevice {
    async fn new(window: Window) -> Self {
        // The instance is a handle to our GPU
        // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU
        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
            backends: wgpu::Backends::all(),
            dx12_shader_compiler: Default::default(),
        });

        // # Safety
        //
        // The surface needs to live as long as the window that created it.
        // State owns the window so this should be safe.
        let surface = unsafe { instance.create_surface(&window) }.unwrap();

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await
            .unwrap();

        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    features: wgpu::Features::empty(),
                    // WebGL doesn't support all of wgpu's features, so if
                    // we're building for the web we'll have to disable some.
                    limits: if cfg!(target_arch = "wasm32") {
                        wgpu::Limits::downlevel_webgl2_defaults()
                    } else {
                        wgpu::Limits::default()
                    },
                    label: None,
                },
                None, // Trace path
            )
            .await
            .unwrap();

        WindowAndDevice {
            window,
            surface,
            adapter,
            device,
            queue,
        }
    }
}

// A little square of two triangles. Neat!
const SPRITE_VERTICES: &[Vertex] = &[
    Vertex {
        position: [0.0, 0.0, 0.0],
        tex_coords: [0.0, 0.0],
    },
    Vertex {
        position: [0.0, 1.0, 0.0],
        tex_coords: [0.0, 1.0],
    },
    Vertex {
        position: [1.0, 0.0, 0.0],
        tex_coords: [1.0, 0.0],
    },
    Vertex {
        position: [0.0, 1.0, 0.0],
        tex_coords: [0.0, 1.0],
    },
    Vertex {
        position: [1.0, 1.0, 0.0],
        tex_coords: [1.0, 1.0],
    },
    Vertex {
        position: [1.0, 0.0, 0.0],
        tex_coords: [1.0, 0.0],
    },
];

struct VertexBufferPool<T> {
    buffers: Vec<VertexBuffer<T>>,
    next_free_buffer: usize,
}

impl<T: bytemuck::Pod> VertexBufferPool<T> {
    fn new() -> Self {
        VertexBufferPool {
            buffers: Vec::new(),
            next_free_buffer: 0,
        }
    }

    fn clear(&mut self) {
        self.next_free_buffer = 0;
    }

    fn get_a_buffer(&mut self, device: &wgpu::Device) -> (u32, VertexBufferHandle) {
        if self.next_free_buffer > 0 {
            let vb = &mut self.buffers[self.next_free_buffer - 1];
            if vb.vec.len() < vb.capacity {
                let draw_start = vb.vec.len();
                let handle = VertexBufferHandle {
                    index: self.next_free_buffer - 1,
                    capacity: vb.capacity,
                };
                return (draw_start.try_into().unwrap(), handle);
            }
        }

        (0, self.new_buffer(device))
    }

    fn new_buffer(&mut self, device: &wgpu::Device) -> VertexBufferHandle {
        if self.next_free_buffer >= self.buffers.len() {
            let max_sprites: usize = 4096;
            let buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Vertex Buffer"),
                size: (max_sprites * std::mem::size_of::<T>()).try_into().unwrap(),
                mapped_at_creation: false,
                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
            });
            self.buffers.push(VertexBuffer {
                buffer,
                capacity: max_sprites,
                vec: Vec::with_capacity(max_sprites),
            });
        }

        let index = self.next_free_buffer;
        self.next_free_buffer += 1;
        let vb = &mut self.buffers[index];
        vb.vec.clear();
        VertexBufferHandle {
            index,
            capacity: vb.capacity,
        }
    }

    fn get(&self, handle: &VertexBufferHandle) -> &VertexBuffer<T> {
        &self.buffers[handle.index]
    }

    fn get_mut(&mut self, handle: &VertexBufferHandle) -> &mut VertexBuffer<T> {
        &mut self.buffers[handle.index]
    }

    fn copy_instance_buffers(&mut self, queue: &wgpu::Queue) {
        for i in 0..self.next_free_buffer {
            let vb = &self.buffers[i];
            queue.write_buffer(&vb.buffer, 0, bytemuck::cast_slice(&vb.vec));
        }
    }
}

struct State {
    surface: wgpu::Surface,
    device: wgpu::Device,
    queue: wgpu::Queue,
    config: wgpu::SurfaceConfiguration,
    size: winit::dpi::PhysicalSize<u32>,
    window: Window,

    sprite_vertex_buffer: wgpu::Buffer,

    sprite_pipeline: wgpu::RenderPipeline,
    sprite_instance_buffers: VertexBufferPool<SpriteInstance>,

    sprite_bind_group_layout: wgpu::BindGroupLayout,
    sprite_textures: HashMap<u32, wgpu::BindGroup>,

    circle_pipeline: wgpu::RenderPipeline,
    circle_instance_buffers: VertexBufferPool<CircleInstance>,

    text_pipeline: wgpu::RenderPipeline,
    text_instance_buffers: VertexBufferPool<GlyphInstance>,

    text_bind_group_layout: wgpu::BindGroupLayout,
    fonts: HashMap<u32, text::FontCache>,
    font_textures: HashMap<u32, wgpu::BindGroup>,

    write_textures: HashMap<u32, texture::Texture>,

    screen_uniform: ScreenUniforms,
    screen_uniform_buffer: wgpu::Buffer,
    screen_uniform_bind_group: wgpu::BindGroup,

    // Garbage
    mouse_x: f64,
    mouse_y: f64,
}

// TUTORIAL FOR BABIES LIKE ME: https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/

impl State {
    // Creating some of the wgpu types requires async code
    fn new(hardware: WindowAndDevice) -> Self {
        let size = hardware.window.inner_size();

        let device = hardware.device;

        let surface_caps = hardware.surface.get_capabilities(&hardware.adapter);

        // Shader code in this tutorial assumes an sRGB surface
        // texture. Using a different one will result all the colors coming
        // out darker. If you want to support non sRGB surfaces, you'll need
        // to account for that when drawing to the frame.
        let surface_format = surface_caps
            .formats
            .iter()
            .copied()
            .find(|f| f.is_srgb())
            .unwrap_or(surface_caps.formats[0]);
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: size.width,
            height: size.height,
            present_mode: wgpu::PresentMode::Fifo,
            alpha_mode: surface_caps.alpha_modes[0],
            view_formats: vec![],
        };
        hardware.surface.configure(&device, &config);

        let sprite_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        // This should match the filterable field of the
                        // corresponding Texture entry above.
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
                label: Some("sprite_bind_group_layout"),
            });

        let screen_uniform = ScreenUniforms::new(size.width, size.height);
        let screen_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Screen Uniform Buffer"),
            contents: bytemuck::cast_slice(&[screen_uniform]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });

        let screen_uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
                label: Some("screen_bind_group_layout"),
            });

        let screen_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &screen_uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: screen_uniform_buffer.as_entire_binding(),
            }],
            label: Some("camera_bind_group"),
        });

        // ====================================================================
        // Sprites
        // ====================================================================
        let sprite_shader =
            Self::load_shader(&device, "Sprite Shader", include_str!("sprite_shader.wgsl"));

        let sprite_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Sprite Pipeline Layout"),
                bind_group_layouts: &[&screen_uniform_bind_group_layout, &sprite_bind_group_layout],
                push_constant_ranges: &[],
            });

        let sprite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Sprite Pipeline"),
            layout: Some(&sprite_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &sprite_shader,
                entry_point: "vs_main",
                buffers: &[Vertex::desc(), SpriteInstance::desc()],
            },
            fragment: Some(wgpu::FragmentState {
                module: &sprite_shader,
                entry_point: "fs_main",
                targets: &[Some(wgpu::ColorTargetState {
                    format: config.format,
                    // TODO: This should be premultiplied alpha blending probably.
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
                polygon_mode: wgpu::PolygonMode::Fill,
                // Requires Features::DEPTH_CLIP_CONTROL
                unclipped_depth: false,
                // Requires Features::CONSERVATIVE_RASTERIZATION
                conservative: false,
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            multiview: None,
        });

        // ====================================================================
        // Circles
        // ====================================================================
        let circle_shader =
            Self::load_shader(&device, "Circle Shader", include_str!("circle_shader.wgsl"));

        let circle_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Circle Pipeline Layout"),
                bind_group_layouts: &[&screen_uniform_bind_group_layout],
                push_constant_ranges: &[],
            });

        let circle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Circle Pipeline"),
            layout: Some(&circle_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &circle_shader,
                entry_point: "vs_main",
                buffers: &[Vertex::desc(), CircleInstance::desc()],
            },
            fragment: Some(wgpu::FragmentState {
                module: &circle_shader,
                entry_point: "fs_main",
                targets: &[Some(wgpu::ColorTargetState {
                    format: config.format,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
                polygon_mode: wgpu::PolygonMode::Fill,
                // Requires Features::DEPTH_CLIP_CONTROL
                unclipped_depth: false,
                // Requires Features::CONSERVATIVE_RASTERIZATION
                conservative: false,
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            multiview: None,
        });

        // ====================================================================
        // Text
        // ====================================================================
        let text_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                ],
                label: Some("text_bind_group_layout"),
            });

        let text_shader =
            Self::load_shader(&device, "Text Shader", include_str!("text_shader.wgsl"));

        let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Text Pipeline Layout"),
            bind_group_layouts: &[&screen_uniform_bind_group_layout, &text_bind_group_layout],
            push_constant_ranges: &[],
        });

        let text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Text Pipeline"),
            layout: Some(&text_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &text_shader,
                entry_point: "vs_main",
                buffers: &[Vertex::desc(), GlyphInstance::desc()],
            },
            fragment: Some(wgpu::FragmentState {
                module: &text_shader,
                entry_point: "fs_main",
                targets: &[Some(wgpu::ColorTargetState {
                    format: config.format,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
                polygon_mode: wgpu::PolygonMode::Fill,
                // Requires Features::DEPTH_CLIP_CONTROL
                unclipped_depth: false,
                // Requires Features::CONSERVATIVE_RASTERIZATION
                conservative: false,
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState {
                // TODO: We could multisample text but it would be the only
                //       thing we multisampled, and it complicates our use of
                //       render passes considerably. Specifically, the pass
                //       needs to be configured to render to a target with
                //       multisampling enabled, and that is only compatible
                //       with pipelines that have multisampling
                //       configured. For now, we don't do this, and remember
                //       that we could do it someday.
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: true,
            },
            multiview: None,
        });

        let inconsolata = include_bytes!("./Inconsolata-Regular.ttf") as &[u8];
        let inconsolata = text::FontCache::new(&device, inconsolata, 8.0 * TEXT_SCALE);

        let mut font_textures = HashMap::new();
        {
            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                layout: &text_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&inconsolata.view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&inconsolata.sampler),
                    },
                ],
                label: Some("inconsolata"),
            });
            font_textures.insert(0, bind_group);
        }
        let mut fonts = HashMap::new();
        fonts.insert(0, inconsolata);

        let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Sprite Vertex Buffer"),
            contents: bytemuck::cast_slice(SPRITE_VERTICES),
            usage: wgpu::BufferUsages::VERTEX,
        });

        Self {
            window: hardware.window,
            surface: hardware.surface,
            device,
            queue: hardware.queue,
            config,
            size,
            sprite_pipeline,

            sprite_vertex_buffer,
            sprite_instance_buffers: VertexBufferPool::new(),

            sprite_bind_group_layout,
            sprite_textures: HashMap::new(),

            circle_pipeline,
            circle_instance_buffers: VertexBufferPool::new(),

            text_pipeline,
            text_instance_buffers: VertexBufferPool::new(),

            text_bind_group_layout,
            fonts,
            font_textures,

            write_textures: HashMap::new(),
            screen_uniform,
            screen_uniform_buffer,
            screen_uniform_bind_group,

            mouse_x: 0.0,
            mouse_y: 0.0,
        }
    }

    fn load_shader(device: &wgpu::Device, label: &str, text: &str) -> wgpu::ShaderModule {
        let common = include_str!("util.wgsl");

        device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some(label),
            source: wgpu::ShaderSource::Wgsl([common, text].join("\n").into()),
        })
    }

    pub fn window(&self) -> &Window {
        &self.window
    }

    fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
        if new_size.width > 0 && new_size.height > 0 {
            self.size = new_size;
            self.config.width = new_size.width;
            self.config.height = new_size.height;
            self.surface.configure(&self.device, &self.config);
        }
    }

    fn update(&mut self) {
        let w = self.size.width as f32;
        let h = self.size.height as f32;
        self.screen_uniform.resolution = [w, h];

        self.queue.write_buffer(
            &self.screen_uniform_buffer,
            0,
            bytemuck::cast_slice(&[self.screen_uniform]),
        );
    }

    fn render(&mut self, commands: Vec<GraphicsCommand>) -> Result<(), wgpu::SurfaceError> {
        let _span = span!("context render");

        // Reset instance buffers.
        self.sprite_instance_buffers.clear();
        self.circle_instance_buffers.clear();
        self.text_instance_buffers.clear();

        let mut builder = FrameBuilder::new(self)?;
        for command in commands {
            builder.handle_command(command);
        }
        FrameBuilder::complete(builder);

        Ok(())
    }

    fn create_texture(&mut self, id: u32, image: image::DynamicImage, label: Option<String>) {
        let texture = texture::Texture::from_image(
            &self.device,
            &self.queue,
            &image,
            match &label {
                Some(l) => Some(&l),
                None => None,
            },
        );
        let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.sprite_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&texture.view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&texture.sampler),
                },
            ],
            label: match &label {
                Some(l) => Some(&l),
                None => None,
            },
        });

        self.sprite_textures.insert(id, sprite_bind_group);
    }

    fn create_writeable_texture(
        &mut self,
        id: u32,
        width: u32,
        height: u32,
        label: Option<String>,
    ) {
        let texture = texture::Texture::new_writable(
            &self.device,
            width,
            height,
            match &label {
                Some(l) => Some(&l),
                None => None,
            },
        );
        let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.sprite_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&texture.view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&texture.sampler),
                },
            ],
            label: match &label {
                Some(l) => Some(&l),
                None => None,
            },
        });

        self.sprite_textures.insert(id, sprite_bind_group);
        self.write_textures.insert(id, texture);
    }

    fn copy_instance_buffers(&mut self) {
        self.sprite_instance_buffers
            .copy_instance_buffers(&self.queue);
        self.circle_instance_buffers
            .copy_instance_buffers(&self.queue);
        self.text_instance_buffers
            .copy_instance_buffers(&self.queue);
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum DrawMode {
    None,
    Sprites(u32),
    Circles,
    Text(u32),
}

trait DrawModeInstance: Sized {
    // const MODE: DrawMode;

    fn assert_compatible_mode(mode: DrawMode);

    fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle);

    fn get_vertex_buffer<'a>(
        state: &'a mut State,
        vb: &VertexBufferHandle,
    ) -> &'a mut VertexBuffer<Self>;
}

impl DrawModeInstance for SpriteInstance {
    // const MODE: DrawMode = DrawMode::Sprites;

    fn assert_compatible_mode(mode: DrawMode) {
        let compat = match mode {
            DrawMode::Sprites(_) => true,
            _ => false,
        };
        assert!(compat, "{mode:?} is not compatible with SpriteInstance");
    }

    fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) {
        state.sprite_instance_buffers.get_a_buffer(&state.device)
    }

    fn get_vertex_buffer<'a>(
        state: &'a mut State,
        vb: &VertexBufferHandle,
    ) -> &'a mut VertexBuffer<Self> {
        state.sprite_instance_buffers.get_mut(&vb)
    }
}

impl DrawModeInstance for CircleInstance {
    // const MODE: DrawMode = DrawMode::Circles;

    fn assert_compatible_mode(mode: DrawMode) {
        let compat = match mode {
            DrawMode::Circles => true,
            _ => false,
        };
        assert!(compat, "{mode:?} is not compatible with CircleInstance");
    }

    fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) {
        state.circle_instance_buffers.get_a_buffer(&state.device)
    }

    fn get_vertex_buffer<'a>(
        state: &'a mut State,
        vb: &VertexBufferHandle,
    ) -> &'a mut VertexBuffer<Self> {
        state.circle_instance_buffers.get_mut(&vb)
    }
}

impl DrawModeInstance for GlyphInstance {
    // const MODE: DrawMode = DrawMode::Text;

    fn assert_compatible_mode(mode: DrawMode) {
        let compat = match mode {
            DrawMode::Text(_) => true,
            _ => false,
        };
        assert!(compat, "{mode:?} is not compatible with GlyphInstance");
    }

    fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) {
        state.text_instance_buffers.get_a_buffer(&state.device)
    }

    fn get_vertex_buffer<'a>(
        state: &'a mut State,
        vb: &VertexBufferHandle,
    ) -> &'a mut VertexBuffer<Self> {
        state.text_instance_buffers.get_mut(&vb)
    }
}

#[derive(Debug)]
struct DrawCall {
    mode: DrawMode,
    vertex_buffer: VertexBufferHandle,
    draw_start: u32,
    draw_end: u32,
}

impl DrawCall {
    pub fn new(mode: DrawMode, vertex_buffer: VertexBufferHandle, draw_start: u32) -> Self {
        DrawCall {
            mode,
            vertex_buffer,
            draw_start,
            draw_end: draw_start,
        }
    }

    pub fn new_at_buffer_tail(&self) -> Self {
        DrawCall::new(self.mode, self.vertex_buffer.clone(), self.draw_end)
    }

    pub fn allocate_capacity(&mut self, capacity: u32) -> Option<VertexBufferHandle> {
        // TODO: FIX: CAPACITY == 1 ALWAYS??
        if self.vertex_buffer.capacity >= (self.draw_end + capacity) as usize {
            self.draw_end += capacity;
            Some(self.vertex_buffer.clone())
        } else {
            None
        }
    }

    pub fn draw<'a>(&self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) {
        if self.draw_end > self.draw_start {
            let vb = match self.mode {
                DrawMode::None => return,
                DrawMode::Sprites(id) => {
                    let bind_group = state
                        .sprite_textures
                        .get(&id)
                        .expect("sprite mode has unknown sprite texture");
                    pass.set_bind_group(1, bind_group, &[]);

                    &state
                        .sprite_instance_buffers
                        .get(&self.vertex_buffer)
                        .buffer
                }

                DrawMode::Text(id) => {
                    let bind_group = state
                        .font_textures
                        .get(&id)
                        .expect("text mode has unknown font texture");
                    pass.set_bind_group(1, bind_group, &[]);

                    &state.text_instance_buffers.get(&self.vertex_buffer).buffer
                }

                DrawMode::Circles => {
                    &state
                        .circle_instance_buffers
                        .get(&self.vertex_buffer)
                        .buffer
                }
            };
            pass.set_bind_group(0, &state.screen_uniform_bind_group, &[]);
            pass.set_vertex_buffer(0, state.sprite_vertex_buffer.slice(..));
            pass.set_vertex_buffer(1, vb.slice(..));

            pass.draw(
                0..SPRITE_VERTICES.len() as u32,
                self.draw_start..self.draw_end,
            );
        }
    }
}

struct FrameBuilder<'a> {
    state: &'a mut State,
    screen_view: Rc<wgpu::TextureView>,
    last_view: Rc<wgpu::TextureView>,
    encoder: wgpu::CommandEncoder,
    output: wgpu::SurfaceTexture,

    // TODO: Should we be persisting these settings across frames?
    //       Right now the whole GPU resets every frame, which I guess is
    //       good and bad?
    //
    // TODO: Should we actually be trying to maintain a RenderPassDescriptor?
    mode: DrawMode,
    last_texture: Option<u32>,
    fill_color: [f32; 4],
    stroke_color: [f32; 4],
    target: Rc<wgpu::TextureView>,
    color: Option<[f64; 4]>,
    draw_calls: Vec<DrawCall>,
    scale: [f32; 2],
}

impl<'a> FrameBuilder<'a> {
    fn new(state: &'a mut State) -> Result<Self, wgpu::SurfaceError> {
        let encoder = state
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Render Encoder"),
            });

        let output = state.surface.get_current_texture()?;

        let screen_view = Rc::new(
            output
                .texture
                .create_view(&wgpu::TextureViewDescriptor::default()),
        );
        let last_view = screen_view.clone();

        Ok(FrameBuilder {
            state,
            screen_view,
            last_view: last_view.clone(),
            encoder,
            output,

            mode: DrawMode::None,
            last_texture: None,
            stroke_color: [0.0, 0.0, 0.0, 1.0],
            fill_color: [1.0, 1.0, 1.0, 1.0],
            target: last_view,
            color: None,
            draw_calls: Vec::new(),
            scale: [1.0, 1.0],
        })
    }

    fn complete(builder: FrameBuilder<'a>) {
        let mut builder = builder;
        builder.flush();

        // At this point the state needs to copy the vertex buffers we know about.
        builder.state.copy_instance_buffers();

        // Submit will accept anything that implements IntoIter
        builder
            .state
            .queue
            .submit(std::iter::once(builder.encoder.finish()));

        builder.output.present();
    }

    fn handle_command(&mut self, command: GraphicsCommand) {
        match command {
            // ================================================================
            // Pass commands
            // ================================================================
            // NOTE: You would expect a "change target" command to be
            //       followed nearly immediately by a clear command and we
            //       wind up starting an extra pass in that case which sucks.
            GraphicsCommand::Clear(cc) => self.start_pass(Some(cc.color), self.last_view.clone()),
            GraphicsCommand::Scale(s) => self.scale = s,
            GraphicsCommand::WriteToTexture(id) => {
                let texture = self.state.write_textures.get(&id).unwrap();
                self.last_view = Rc::new(
                    texture
                        .texture
                        .create_view(&wgpu::TextureViewDescriptor::default()),
                );
                self.start_pass(None, self.last_view.clone());
            }
            GraphicsCommand::WriteToScreen => {
                if !Rc::ptr_eq(&self.last_view, &self.screen_view) {
                    self.last_view = self.screen_view.clone();
                    self.start_pass(None, self.last_view.clone());
                }
            }
            GraphicsCommand::FillColor(c) => {
                self.fill_color = c;
            }
            GraphicsCommand::StrokeColor(c) => {
                self.stroke_color = c;
            }
            GraphicsCommand::Print(pc) => self.push_text(pc),
            GraphicsCommand::Sprite(sc) => self.push_sprite(sc),
            GraphicsCommand::Circle(cc) => self.push_circle(cc),
            GraphicsCommand::UseTexture(id) => self.use_texture(id),
            GraphicsCommand::EndFrame => self.flush(),

            // ================================================================
            // Resource commands
            // ================================================================
            GraphicsCommand::CreateTexture(ct) => {
                self.state.create_texture(ct.id, ct.image, ct.label)
            }
            GraphicsCommand::CreateWritableTexture {
                id,
                width,
                height,
                label,
            } => self
                .state
                .create_writeable_texture(id, width, height, label),
        }
    }

    fn start_pass(&mut self, color: Option<[f64; 4]>, target: Rc<wgpu::TextureView>) {
        // NOTE: We're not changing drawing modes here which means we can preserve the
        //       buffer tail if we want to.
        // TODO: Do we need to do this actually?
        let first_call = match self.draw_calls.last() {
            Some(call) => Some(call.new_at_buffer_tail()),
            None => None,
        };

        self.flush();

        match first_call {
            Some(fc) => self.draw_calls.push(fc),
            None => {}
        }
        self.color = color;
        self.target = target;
    }

    fn use_texture(&mut self, texture_id: u32) {
        self.last_texture = Some(texture_id);
    }

    fn get_instance_buffer<T: DrawModeInstance>(&mut self, mode: DrawMode) -> &mut VertexBuffer<T> {
        T::assert_compatible_mode(mode);

        self.mode = mode;
        match self.draw_calls.last_mut() {
            Some(call) => {
                if call.mode == self.mode {
                    match call.allocate_capacity(1) {
                        Some(vb) => return T::get_vertex_buffer(self.state, &vb),
                        None => {}
                    }
                }
            }
            None => {}
        };

        let (draw_start, buffer) = T::new_vertex_buffer(self.state);
        let mut call = DrawCall::new(self.mode, buffer, draw_start);
        let vb = call.allocate_capacity(1).unwrap();
        self.draw_calls.push(call);
        T::get_vertex_buffer(self.state, &vb)
    }

    fn push_sprite(&mut self, sc: script::graphics::SpriteCommand) {
        if let Some(id) = self.last_texture {
            let sprite = SpriteInstance {
                src_top_left: sc.src_top_left,
                src_dims: sc.src_dims,

                dest_top_left: [
                    sc.dest_top_left[0] * self.scale[0],
                    sc.dest_top_left[1] * self.scale[1],
                ],
                dest_dims: [
                    sc.dest_dims[0] * self.scale[0],
                    sc.dest_dims[1] * self.scale[1],
                ],
            };

            self.get_instance_buffer(DrawMode::Sprites(id))
                .vec
                .push(sprite);
        } else {
            eprintln!("WARNING: sprite drawn with no active texture");
        }
    }

    fn push_circle(&mut self, cc: script::graphics::CircleCommand) {
        let circle = CircleInstance {
            center: [cc.center[0] * self.scale[0], cc.center[1] * self.scale[1]],
            radius: cc.radius * self.scale[0],             // WRONG!
            stroke_width: cc.stroke_width * self.scale[0], // ALSO WRONG!
            stroke_color: self.stroke_color,
            fill_color: self.fill_color,
        };

        self.get_instance_buffer(DrawMode::Circles).vec.push(circle);
    }

    fn push_text(&mut self, pc: script::graphics::PrintCommand) {
        let mut cursor_x = pc.pos[0];
        let cursor_y = pc.pos[1];

        let font_id: u32 = 0;

        let font = self.state.fonts.get_mut(&font_id).unwrap();
        let mut glyphs = vec![];
        for char in pc.text.chars() {
            let color = self.fill_color.clone();
            let glyph = font.get_char(&self.state.queue, char);
            glyphs.push(GlyphInstance {
                src_top_left: [glyph.x, glyph.y],
                src_dims: [glyph.w, glyph.h],
                dest_top_left: [
                    self.scale[0] * (cursor_x + (glyph.adjust_x / TEXT_SCALE)),
                    self.scale[1] * (cursor_y + (glyph.adjust_y / TEXT_SCALE)),
                ],
                dest_dims: [
                    self.scale[0] * (glyph.w / TEXT_SCALE),
                    self.scale[1] * (glyph.h / TEXT_SCALE),
                ],
                color,
            });
            cursor_x += glyph.advance_width / TEXT_SCALE;
        }

        for glyph in glyphs {
            // TODO: Get new instance buffer when we don't have space for
            // this glyph instead of doing this over and over.
            self.get_instance_buffer(DrawMode::Text(font_id))
                .vec
                .push(glyph);
        }
    }

    fn flush(&mut self) {
        if self.draw_calls.len() > 0 {
            let mut pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Render Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &self.target,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: if let Some([r, g, b, a]) = self.color {
                            wgpu::LoadOp::Clear(wgpu::Color {
                                r, //0.1,
                                g, //0.2,
                                b,
                                a,
                            })
                        } else {
                            wgpu::LoadOp::Load
                        },
                        store: true,
                    },
                })],
                depth_stencil_attachment: None,
            });

            let mut last_mode = None;
            for call in &self.draw_calls {
                let need_set_pipeline = match last_mode {
                    None => true,
                    Some(m) => call.mode != m,
                };
                if need_set_pipeline {
                    match call.mode {
                        DrawMode::None => {
                            continue; // Skip this draw call I guess.
                        }
                        DrawMode::Sprites(_) => pass.set_pipeline(&self.state.sprite_pipeline),
                        DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline),
                        DrawMode::Text(_) => pass.set_pipeline(&self.state.text_pipeline),
                    }
                    last_mode = Some(call.mode);
                }
                call.draw(&self.state, &mut pass);
            }
        }

        self.color = None;
        self.draw_calls.clear();
    }
}

enum OdenEvent {
    Close,
}

struct UIEvent {
    winit: Event<'static, OdenEvent>,
    #[allow(unused)]
    time: Instant,
}

// TODO: flume? (https://docs.rs/flume/latest/flume/)

fn main_thread(event_loop: EventLoopProxy<OdenEvent>, state: State, reciever: Receiver<UIEvent>) {
    let mut state = state;

    let (script_reload_send, script_reload_recv) = channel();

    let mut script = script::ScriptContext::new(None, script_reload_send.clone())
        .expect("Unable to create initial script context");

    const SPF: f64 = 1.0 / 60.0;
    loop {
        frame_mark();
        let start = Instant::now();

        {
            let _span = span!("events");
            while let Ok(event) = reciever.try_recv() {
                match event.winit {
                    Event::WindowEvent {
                        ref event,
                        window_id,
                    } if window_id == state.window().id() => {
                        if !script.input(event) {
                            match event {
                                WindowEvent::CloseRequested => {
                                    let _ = event_loop.send_event(OdenEvent::Close);
                                }

                                WindowEvent::CursorMoved { position, .. } => {
                                    state.mouse_x = position.x;
                                    state.mouse_y = position.y;
                                }

                                WindowEvent::Resized(physical_size) => {
                                    state.resize(*physical_size);
                                }

                                WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
                                    // new_inner_size is &&mut so we have to dereference it twice
                                    state.resize(**new_inner_size);
                                }

                                _ => {}
                            }
                        }
                    }

                    _ => {}
                }
            }
        }

        {
            let _span = span!("check script reload");
            let mut reload_script = false;
            while let Ok(_) = script_reload_recv.try_recv() {
                reload_script = true;
            }
            if reload_script {
                eprintln!("RELOADING SCRIPT");
                let suspend_state = script.suspend();
                match script::ScriptContext::new(suspend_state, script_reload_send.clone()) {
                    Ok(new_script) => script = new_script,
                    Err(e) => eprintln!("WARNING: Script reload aborted, load failure: {e:?}"),
                };
            }
        }

        {
            let _span = span!("update");
            script.update((state.size.width as f32, state.size.height as f32));
            state.update();
        }

        {
            let _span = span!("render");
            match state.render(script.render()) {
                Ok(_) => {}
                // Reconfigure the surface if lost
                Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
                // The system is out of memory, we should probably quit
                Err(wgpu::SurfaceError::OutOfMemory) => panic!("Out of memory"),
                // All other errors (Outdated, Timeout) should be resolved by the next frame
                Err(e) => eprintln!("{:?}", e),
            }
        }

        {
            let _span = span!("sleep");
            while start.elapsed().as_secs_f64() < SPF {
                std::thread::yield_now();
            }
        }
    }
}

pub async fn run() {
    let _client = tracy_client::Client::start();
    set_thread_name!("ui thread");

    env_logger::init();
    let event_loop = EventLoopBuilder::<OdenEvent>::with_user_event().build();
    let window = WindowBuilder::new().build(&event_loop).unwrap();
    let event_loop_proxy = event_loop.create_proxy();

    let hardware = WindowAndDevice::new(window).await;
    let (sender, reciever) = std::sync::mpsc::channel();

    std::thread::spawn(move || {
        set_thread_name!("game thread");
        let state = State::new(hardware);
        main_thread(event_loop_proxy, state, reciever);
    });

    event_loop.run(move |event, _, control_flow| {
        control_flow.set_wait();

        match event {
            Event::UserEvent(OdenEvent::Close) => {
                control_flow.set_exit();
            }
            _ => {
                if let Some(e) = event.to_static() {
                    sender
                        .send(UIEvent {
                            winit: e,
                            time: Instant::now(),
                        })
                        .unwrap();
                }
            }
        }
    });
}
