use crate::helpers::{gl_parameter_string, nul_terminated_bytes, supported_extension_bytes};
use crate::types::{
    Buffer, Framebuffer, GLintptr, GLuint, Program, Query, Renderbuffer, ResourceHandle, Sampler,
    Shader, SyncHandle, Texture, TransformFeedback, UniformLocation, VertexArray,
};
use std::collections::HashMap;
use web_sys::{
    WebGl2RenderingContext as Gl, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlQuery,
    WebGlRenderbuffer, WebGlSampler, WebGlShader, WebGlSync, WebGlTexture, WebGlTransformFeedback,
    WebGlUniformLocation, WebGlVertexArrayObject,
};

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct MappedBuffer {
    pub(crate) target: u32,
    pub(crate) offset: GLintptr,
    pub(crate) data: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct PixelStoreState {
    pub(crate) unpack_alignment: i32,
    pub(crate) unpack_row_length: i32,
    pub(crate) unpack_skip_rows: i32,
    pub(crate) unpack_skip_pixels: i32,
    pub(crate) pack_alignment: i32,
    pub(crate) pack_row_length: i32,
    pub(crate) pack_skip_rows: i32,
    pub(crate) pack_skip_pixels: i32,
}

impl Default for PixelStoreState {
    fn default() -> Self {
        Self {
            unpack_alignment: 4,
            unpack_row_length: 0,
            unpack_skip_rows: 0,
            unpack_skip_pixels: 0,
            pack_alignment: 4,
            pack_row_length: 0,
            pack_skip_rows: 0,
            pack_skip_pixels: 0,
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct CachedStrings {
    pub(crate) version: Vec<u8>,
    pub(crate) vendor: Vec<u8>,
    pub(crate) renderer: Vec<u8>,
    pub(crate) shading_language_version: Vec<u8>,
    pub(crate) extensions: Vec<Vec<u8>>,
}

pub(crate) struct ContextState {
    pub(crate) gl: Gl,
    pub(crate) textures: HashMap<Texture, WebGlTexture>,
    pub(crate) buffers: HashMap<Buffer, WebGlBuffer>,
    pub(crate) programs: HashMap<Program, WebGlProgram>,
    pub(crate) shaders: HashMap<Shader, WebGlShader>,
    pub(crate) framebuffers: HashMap<Framebuffer, WebGlFramebuffer>,
    pub(crate) renderbuffers: HashMap<Renderbuffer, WebGlRenderbuffer>,
    pub(crate) vertex_arrays: HashMap<VertexArray, WebGlVertexArrayObject>,
    pub(crate) queries: HashMap<Query, WebGlQuery>,
    pub(crate) samplers: HashMap<Sampler, WebGlSampler>,
    pub(crate) syncs: HashMap<SyncHandle, WebGlSync>,
    pub(crate) uniform_locations: HashMap<UniformLocation, WebGlUniformLocation>,
    pub(crate) transform_feedbacks: HashMap<TransformFeedback, WebGlTransformFeedback>,
    pub(crate) cached_strings: CachedStrings,
    pub(crate) pixel_store: PixelStoreState,
    pub(crate) next_resource_id: GLuint,
    pub(crate) mapped_buffer: Option<MappedBuffer>,
}

impl ContextState {
    pub(crate) fn new(gl: Gl) -> Self {
        Self {
            cached_strings: CachedStrings {
                version: nul_terminated_bytes("OpenGL ES 3.0".to_owned()),
                vendor: gl_parameter_string(&gl, Gl::VENDOR, "WebGL"),
                renderer: gl_parameter_string(&gl, Gl::RENDERER, "WebGL2 Renderer"),
                shading_language_version: nul_terminated_bytes("OpenGL ES GLSL ES 3.00".to_owned()),
                extensions: supported_extension_bytes(&gl),
            },
            gl,
            textures: HashMap::new(),
            buffers: HashMap::new(),
            programs: HashMap::new(),
            shaders: HashMap::new(),
            framebuffers: HashMap::new(),
            renderbuffers: HashMap::new(),
            vertex_arrays: HashMap::new(),
            queries: HashMap::new(),
            samplers: HashMap::new(),
            syncs: HashMap::new(),
            uniform_locations: HashMap::new(),
            transform_feedbacks: HashMap::new(),
            pixel_store: PixelStoreState::default(),
            next_resource_id: 1,
            mapped_buffer: None,
        }
    }

    pub(crate) fn alloc_id(&mut self) -> GLuint {
        let id = self.next_resource_id;
        self.next_resource_id = self
            .next_resource_id
            .checked_add(1)
            .expect("resource id overflowed");
        id
    }

    pub(crate) fn alloc_handle<H>(&mut self) -> H
    where
        H: ResourceHandle,
    {
        H::new(self.alloc_id())
    }
}
