use std::borrow::Cow;
use std::collections::{HashSet,HashMap};
use strafesnet_common::map;
use strafesnet_settings::settings;
use strafesnet_session::session;
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};

pub fn required_limits()->wgpu::Limits{
	wgpu::Limits::default()
}

struct Indices{
	count:u32,
	buf:wgpu::Buffer,
	format:wgpu::IndexFormat,
}
impl Indices{
	fn new<T:bytemuck::Pod>(device:&wgpu::Device,indices:&Vec<T>,format:wgpu::IndexFormat)->Self{
		Self{
			buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
				label:Some("Index"),
				contents:bytemuck::cast_slice(indices),
				usage:wgpu::BufferUsages::INDEX,
			}),
			count:indices.len() as u32,
			format,
		}
	}
}
struct GraphicsModel{
	indices:Indices,
	vertex_buf:wgpu::Buffer,
	bind_group:wgpu::BindGroup,
	instance_count:u32,
}

struct GraphicsSamplers{
	repeat:wgpu::Sampler,
}

struct GraphicsBindGroupLayouts{
	model:wgpu::BindGroupLayout,
}

struct GraphicsBindGroups{
	camera:wgpu::BindGroup,
	skybox_texture:wgpu::BindGroup,
}

struct GraphicsPipelines{
	skybox:wgpu::RenderPipeline,
	model:wgpu::RenderPipeline,
}

struct GraphicsCamera{
	screen_size:glam::UVec2,
	fov:glam::Vec2,//slope
	//camera angles and such are extrapolated and passed in every time
}

#[inline]
fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{
	//glam_assert!(z_near > 0.0 && z_far > 0.0);
	let r=z_far/(z_near-z_far);
	glam::Mat4::from_cols(
		glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0),
		glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0),
		glam::Vec4::new(0.0,0.0,r,-1.0),
		glam::Vec4::new(0.0,0.0,r*z_near,0.0),
	)
}
impl GraphicsCamera{
	pub fn proj(&self)->glam::Mat4{
		perspective_rh(self.fov.x,self.fov.y,0.4,4000.0)
	}
	pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
		//f32 good enough for view matrix
		glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
	}

	pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{
		let proj=self.proj();
		let proj_inv=proj.inverse();
		let view_inv=self.world(pos,angles);
		let view=view_inv.inverse();

		let mut raw=[0f32; 16 * 4];
		raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]);
		raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]);
		raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]);
		raw[48..64].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]);
		raw
	}
}
impl Default for GraphicsCamera{
	fn default()->Self{
		Self{
			screen_size:glam::UVec2::ONE,
			fov:glam::Vec2::ONE,
		}
	}
}

const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
	let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
	for mi in instances{
		//model transform
		raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
		//normal transform
		raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
		raw.extend_from_slice(&[0.0]);
		raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
		raw.extend_from_slice(&[0.0]);
		raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
		raw.extend_from_slice(&[0.0]);
		//color
		raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
	}
	raw
}

pub struct GraphicsState{
	pipelines:GraphicsPipelines,
	bind_groups:GraphicsBindGroups,
	bind_group_layouts:GraphicsBindGroupLayouts,
	samplers:GraphicsSamplers,
	camera:GraphicsCamera,
	camera_buf:wgpu::Buffer,
	temp_squid_texture_view:wgpu::TextureView,
	models:Vec<GraphicsModel>,
	depth_view:wgpu::TextureView,
	staging_belt:wgpu::util::StagingBelt,
}

impl GraphicsState{
	const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus;
	fn create_depth_texture(
		config:&wgpu::SurfaceConfiguration,
		device:&wgpu::Device,
	)->wgpu::TextureView{
		let depth_texture=device.create_texture(&wgpu::TextureDescriptor{
			size:wgpu::Extent3d{
				width:config.width,
				height:config.height,
				depth_or_array_layers:1,
			},
			mip_level_count:1,
			sample_count:1,
			dimension:wgpu::TextureDimension::D2,
			format:Self::DEPTH_FORMAT,
			usage:wgpu::TextureUsages::RENDER_ATTACHMENT,
			label:None,
			view_formats:&[],
		});

		depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
	}
	pub fn clear(&mut self){
		self.models.clear();
	}
	pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
		self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
	}
	pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
		//generate texture view per texture
		let texture_views:HashMap<model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
			let texture_id=model::TextureId::new(texture_id as u32);
			let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
				Ok(image)=>image,
				Err(e)=>{
					println!("Error loading texture: {e}");
					return None;
				},
			};

			let (mut width,mut height)=(image.get_width(),image.get_height());

			let format=match image.header10.unwrap().dxgi_format{
				ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb,
				ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{
					//floor(w,4),should be ceil(w,4)
					width=width/4*4;
					height=height/4*4;
					wgpu::TextureFormat::Bc7RgbaUnormSrgb
				},
				other=>{
					println!("unsupported texture format{:?}",other);
					return None;
				},
			};

			let size=wgpu::Extent3d{
				width,
				height,
				depth_or_array_layers:1,
			};

			let layer_size=wgpu::Extent3d{
				depth_or_array_layers:1,
				..size
			};
			let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);

			let texture=device.create_texture_with_data(
				queue,
				&wgpu::TextureDescriptor{
					size,
					mip_level_count:max_mips,
					sample_count:1,
					dimension:wgpu::TextureDimension::D2,
					format,
					usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
					label:Some(format!("Texture{}",texture_id.get()).as_str()),
					view_formats:&[],
				},
				wgpu::util::TextureDataOrder::LayerMajor,
				&image.data,
			);
			Some((texture_id,texture.create_view(&wgpu::TextureViewDescriptor{
				label:Some(format!("Texture{} View",texture_id.get()).as_str()),
				dimension:Some(wgpu::TextureViewDimension::D2),
				..wgpu::TextureViewDescriptor::default()
			})))
		}).collect();
		let num_textures=texture_views.len();

		//split groups with different textures into separate models
		//the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups.
		let indexed_models_len=map.models.len();
		//models split into graphics_group.RenderConfigId
		let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap<model::MeshId,HashMap<RenderConfigId,IndexedGraphicsMeshOwnedRenderConfigId>>=HashMap::new();
		let mut unique_render_config_models:Vec<IndexedGraphicsMeshOwnedRenderConfig>=Vec::with_capacity(indexed_models_len);
		for model in &map.models{
			//wow
			let instance=GraphicsModelOwned{
				transform:model.transform.into(),
				normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
				color:GraphicsModelColor4::new(model.color),
			};
			//get or create owned mesh map
			let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id
			.entry(model.mesh).or_insert_with(||{
				let mut owned_mesh_map=HashMap::new();
				//add mesh if renderid never before seen for this model
				//add instance
				//convert Model into GraphicsModelOwned
				//check each group, if it's using a new render config then make a new clone of the model
				if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){
					for graphics_group in mesh.graphics_groups.iter(){
						//get or create owned mesh
						let owned_mesh_id=owned_mesh_map
						.entry(graphics_group.render).or_insert_with(||{
							//create
							let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32);
							unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{
								unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(),
								unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(),
								unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(),
								unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(),
								unique_vertices:mesh.unique_vertices.clone(),
								render_config:graphics_group.render,
								polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())),
								instances:Vec::new(),
							});
							owned_mesh_id
						});
						let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
						match &mut owned_mesh.polys{
							model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend(
								graphics_group.groups.iter().flat_map(|polygon_group_id|{
									mesh.polygon_groups[polygon_group_id.get() as usize].polys()
								})
								.map(|vertex_id_slice|
									vertex_id_slice.to_vec()
								)
							),
						}
					}
				}
				owned_mesh_map
			});
			for owned_mesh_id in owned_mesh_map.values(){
				let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
				let render_config=&map.render_configs[owned_mesh.render_config.get() as usize];
				if model.color.w==0.0&&render_config.texture.is_none(){
					continue;
				}
				owned_mesh.instances.push(instance.clone());
			}
		}
		//check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model
		//1. collect unique instances of texture and color,note model id
		//2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one
		//3. transpose all models that stay in the set

		//best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed
		//check if the estimated render performance is better by transposing multiple model instances into one model instance

		//for now:just deduplicate single models...
		let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger
		let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)]
		for (model_id,model) in unique_render_config_models.iter().enumerate(){
			//for now:filter out models with more than one instance
			if 1<model.instances.len(){
				continue;
			}
			//populate hashmap
			let unique_color=unique_texture_color
			.entry(model.render_config)
			.or_insert_with(||HashMap::new());
			//separate instances by color
			for (instance_id,instance) in model.instances.iter().enumerate(){
				let model_instance_list=unique_color
				.entry(instance.color)
				.or_insert_with(||Vec::new());
				//add model instance to list
				model_instance_list.push((model_id,instance_id));
			}
		}
		//populate a hashset of models selected for transposition
		//construct transposed models
		let mut selected_model_instances=HashSet::new();
		for (render_config,unique_color) in unique_texture_color.into_iter(){
			for (color,model_instance_list) in unique_color.into_iter(){
				//world transforming one model does not meet the definition of deduplicaiton
				if 1<model_instance_list.len(){
					//create model
					let mut unique_pos=Vec::new();
					let mut pos_id_from=HashMap::new();
					let mut unique_tex=Vec::new();
					let mut tex_id_from=HashMap::new();
					let mut unique_normal=Vec::new();
					let mut normal_id_from=HashMap::new();
					let mut unique_color=Vec::new();
					let mut color_id_from=HashMap::new();
					let mut unique_vertices=Vec::new();
					let mut vertex_id_from=HashMap::new();

					let mut polys=Vec::new();
					//transform instance vertices
					for (model_id,instance_id) in model_instance_list.into_iter(){
						//populate hashset to prevent these models from being copied
						selected_model_instances.insert(model_id);
						//there is only one instance per model
						let model=&unique_render_config_models[model_id];
						let instance=&model.instances[instance_id];
						//just hash word slices LOL
						let map_pos_id:Vec<PositionId>=model.unique_pos.iter().map(|untransformed_pos|{
							let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array();
							let h=bytemuck::cast::<[f32;3],[u32;3]>(pos);
							PositionId::new(*pos_id_from.entry(h).or_insert_with(||{
								let pos_id=unique_pos.len();
								unique_pos.push(pos);
								pos_id
							}) as u32)
						}).collect();
						let map_tex_id:Vec<TextureCoordinateId>=model.unique_tex.iter().map(|&tex|{
							let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
							TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
								let tex_id=unique_tex.len();
								unique_tex.push(tex);
								tex_id
							}) as u32)
						}).collect();
						let map_normal_id:Vec<NormalId>=model.unique_normal.iter().map(|untransformed_normal|{
							let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array();
							let h=bytemuck::cast::<[f32;3],[u32;3]>(normal);
							NormalId::new(*normal_id_from.entry(h).or_insert_with(||{
								let normal_id=unique_normal.len();
								unique_normal.push(normal);
								normal_id
							}) as u32)
						}).collect();
						let map_color_id:Vec<ColorId>=model.unique_color.iter().map(|&color|{
							let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
							ColorId::new(*color_id_from.entry(h).or_insert_with(||{
								let color_id=unique_color.len();
								unique_color.push(color);
								color_id
							}) as u32)
						}).collect();
						//map the indexed vertices onto new indices
						//creating the vertex map is slightly different because the vertices are directly hashable
						let map_vertex_id:Vec<VertexId>=model.unique_vertices.iter().map(|unmapped_vertex|{
							let vertex=model::IndexedVertex{
								pos:map_pos_id[unmapped_vertex.pos.get() as usize],
								tex:map_tex_id[unmapped_vertex.tex.get() as usize],
								normal:map_normal_id[unmapped_vertex.normal.get() as usize],
								color:map_color_id[unmapped_vertex.color.get() as usize],
							};
							VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
								let vertex_id=unique_vertices.len();
								unique_vertices.push(vertex);
								vertex_id
							}) as u32)
						}).collect();
						polys.extend(model.polys.polys().map(|poly|
							poly.iter().map(|vertex_id|
								map_vertex_id[vertex_id.get() as usize]
							).collect()
						));
					}
					//push model into dedup
					deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{
						unique_pos,
						unique_tex,
						unique_normal,
						unique_color,
						unique_vertices,
						render_config,
						polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)),
						instances:vec![GraphicsModelOwned{
							transform:glam::Mat4::IDENTITY,
							normal_transform:glam::Mat3::IDENTITY,
							color
						}],
					});
				}
			}
		}
		//fill untouched models
		for (model_id,model) in unique_render_config_models.into_iter().enumerate(){
			if !selected_model_instances.contains(&model_id){
				deduplicated_models.push(model);
			}
		}

		//de-index models
		let deduplicated_models_len=deduplicated_models.len();
		let models:Vec<GraphicsMeshOwnedRenderConfig>=deduplicated_models.into_iter().map(|model|{
			let mut vertices=Vec::new();
			let mut index_from_vertex=HashMap::new();//::<IndexedVertex,usize>
			//this mut be combined in a more complex way if the models use different render patterns per group
			let mut indices=Vec::new();
			for poly in model.polys.polys(){
				let mut poly_vertices=poly.iter()
				.map(|&vertex_index|*index_from_vertex.entry(vertex_index).or_insert_with(||{
					let i=vertices.len();
					let vertex=&model.unique_vertices[vertex_index.get() as usize];
					vertices.push(GraphicsVertex{
						pos:model.unique_pos[vertex.pos.get() as usize],
						tex:model.unique_tex[vertex.tex.get() as usize],
						normal:model.unique_normal[vertex.normal.get() as usize],
						color:model.unique_color[vertex.color.get() as usize],
					});
					i
				}));

				let a=poly_vertices.next().unwrap();
				let mut b=poly_vertices.next().unwrap();

				poly_vertices.for_each(|c|{
					indices.extend([a,b,c]);
					b=c;
				});
			}
			GraphicsMeshOwnedRenderConfig{
				instances:model.instances,
				indices:if (u32::MAX as usize)<vertices.len(){
					panic!("Model has too many vertices!")
				}else if (u16::MAX as usize)<vertices.len(){
					model_graphics::Indices::U32(indices.into_iter().map(|vertex_idx|vertex_idx as u32).collect())
				}else{
					model_graphics::Indices::U16(indices.into_iter().map(|vertex_idx|vertex_idx as u16).collect())
				},
				vertices,
				render_config:model.render_config,
			}
		}).collect();
		//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
		let mut model_count=0;
		let mut instance_count=0;
		let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
		let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
		self.models.reserve(models.len());
		for model in models.into_iter(){
			instance_count+=model.instances.len();
			for instances_chunk in model.instances.rchunks(chunk_size){
				model_count+=1;
				let mut model_uniforms=get_instances_buffer_data(instances_chunk);
				//TEMP: fill with zeroes to pass validation
				model_uniforms.resize(MODEL_BUFFER_SIZE*512,0.0f32);
				let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
					label:Some(format!("Model{} Buf",model_count).as_str()),
					contents:bytemuck::cast_slice(&model_uniforms),
					usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
				});
				let render_config=&map.render_configs[model.render_config.get() as usize];
				let texture_view=render_config.texture.and_then(|texture_id|
					texture_views.get(&texture_id)
				).unwrap_or(&self.temp_squid_texture_view);
				let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
					layout:&self.bind_group_layouts.model,
					entries:&[
						wgpu::BindGroupEntry{
							binding:0,
							resource:model_buf.as_entire_binding(),
						},
						wgpu::BindGroupEntry{
							binding:1,
							resource:wgpu::BindingResource::TextureView(texture_view),
						},
						wgpu::BindGroupEntry{
							binding:2,
							resource:wgpu::BindingResource::Sampler(&self.samplers.repeat),
						},
					],
					label:Some(format!("Model{} Bind Group",model_count).as_str()),
				});
				let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
					label:Some("Vertex"),
					contents:bytemuck::cast_slice(&model.vertices),
					usage:wgpu::BufferUsages::VERTEX,
				});
				//all of these are being moved here
				self.models.push(GraphicsModel{
					instance_count:instances_chunk.len() as u32,
					vertex_buf,
					indices:match &model.indices{
						model_graphics::Indices::U32(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint32),
						model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16),
					},
					bind_group,
				});
			}
		}
		println!("Texture References={}",num_textures);
		println!("Textures Loaded={}",texture_views.len());
		println!("Indexed Models={}",indexed_models_len);
		println!("Deduplicated Models={}",deduplicated_models_len);
		println!("Graphics Objects:{}",self.models.len());
		println!("Graphics Instances:{}",instance_count);
	}

	pub fn new(
		device:&wgpu::Device,
		queue:&wgpu::Queue,
		config:&wgpu::SurfaceConfiguration,
	)->Self{
		let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
			label:None,
			entries:&[
				wgpu::BindGroupLayoutEntry{
					binding:0,
					visibility:wgpu::ShaderStages::VERTEX,
					ty:wgpu::BindingType::Buffer{
						ty:wgpu::BufferBindingType::Uniform,
						has_dynamic_offset:false,
						min_binding_size:None,
					},
					count:None,
				},
			],
		});
		let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
			label:Some("Skybox Texture Bind Group Layout"),
			entries:&[
				wgpu::BindGroupLayoutEntry{
					binding:0,
					visibility:wgpu::ShaderStages::FRAGMENT,
					ty:wgpu::BindingType::Texture{
						sample_type:wgpu::TextureSampleType::Float{filterable:true},
						multisampled:false,
						view_dimension:wgpu::TextureViewDimension::Cube,
					},
					count:None,
				},
				wgpu::BindGroupLayoutEntry{
					binding:1,
					visibility:wgpu::ShaderStages::FRAGMENT,
					ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
					count:None,
				},
			],
		});
		let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
			label:Some("Model Bind Group Layout"),
			entries:&[
				wgpu::BindGroupLayoutEntry{
					binding:0,
					visibility:wgpu::ShaderStages::VERTEX,
					ty:wgpu::BindingType::Buffer{
						ty:wgpu::BufferBindingType::Uniform,
						has_dynamic_offset:false,
						min_binding_size:None,
					},
					count:None,
				},
				wgpu::BindGroupLayoutEntry{
					binding:1,
					visibility:wgpu::ShaderStages::FRAGMENT,
					ty:wgpu::BindingType::Texture{
						sample_type:wgpu::TextureSampleType::Float{filterable:true},
						multisampled:false,
						view_dimension:wgpu::TextureViewDimension::D2,
					},
					count:None,
				},
				wgpu::BindGroupLayoutEntry{
					binding:2,
					visibility:wgpu::ShaderStages::FRAGMENT,
					ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
					count:None,
				},
			],
		});

		let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
			label:Some("Clamp Sampler"),
			address_mode_u:wgpu::AddressMode::ClampToEdge,
			address_mode_v:wgpu::AddressMode::ClampToEdge,
			address_mode_w:wgpu::AddressMode::ClampToEdge,
			mag_filter:wgpu::FilterMode::Linear,
			min_filter:wgpu::FilterMode::Linear,
			mipmap_filter:wgpu::MipmapFilterMode::Linear,
			..Default::default()
		});
		let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
			label:Some("Repeat Sampler"),
			address_mode_u:wgpu::AddressMode::Repeat,
			address_mode_v:wgpu::AddressMode::Repeat,
			address_mode_w:wgpu::AddressMode::Repeat,
			mag_filter:wgpu::FilterMode::Linear,
			min_filter:wgpu::FilterMode::Linear,
			mipmap_filter:wgpu::MipmapFilterMode::Linear,
			anisotropy_clamp:16,
			..Default::default()
		});

		// Create the render pipeline
		let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
			label:None,
			source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
		});

		//load textures
		let device_features=device.features();

		let skybox_texture_view={
			let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){
				println!("Using ASTC");
				wgpu::TextureFormat::Astc{
					block:AstcBlock::B4x4,
					channel:AstcChannel::UnormSrgb,
				}
			}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){
				println!("Using ETC2");
				wgpu::TextureFormat::Etc2Rgb8UnormSrgb
			}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){
				println!("Using BC");
				wgpu::TextureFormat::Bc1RgbaUnormSrgb
			}else{
				println!("Using plain");
				wgpu::TextureFormat::Bgra8UnormSrgb
			};

			let bytes=match skybox_format{
				wgpu::TextureFormat::Astc{
					block:AstcBlock::B4x4,
					channel:AstcChannel::UnormSrgb,
				}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
				wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
				wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
				wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
				_=>unreachable!(),
			};

			let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();

			let size=wgpu::Extent3d{
				width:skybox_image.get_width(),
				height:skybox_image.get_height(),
				depth_or_array_layers:6,
			};

			let layer_size=wgpu::Extent3d{
				depth_or_array_layers:1,
				..size
			};
			let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);

			let skybox_texture=device.create_texture_with_data(
				queue,
				&wgpu::TextureDescriptor{
					size,
					mip_level_count:max_mips,
					sample_count:1,
					dimension:wgpu::TextureDimension::D2,
					format:skybox_format,
					usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
					label:Some("Skybox Texture"),
					view_formats:&[],
				},
				wgpu::util::TextureDataOrder::LayerMajor,
				&skybox_image.data,
			);

			skybox_texture.create_view(&wgpu::TextureViewDescriptor{
				label:Some("Skybox Texture View"),
				dimension:Some(wgpu::TextureViewDimension::Cube),
				..wgpu::TextureViewDescriptor::default()
			})
		};

		//squid
		let squid_texture_view={
			let bytes=include_bytes!("../../../strafe-client/images/squid.dds");

			let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();

			let size=wgpu::Extent3d{
				width:image.get_width(),
				height:image.get_height(),
				depth_or_array_layers:1,
			};

			let layer_size=wgpu::Extent3d{
				depth_or_array_layers:1,
				..size
			};
			let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);

			let texture=device.create_texture_with_data(
				queue,
				&wgpu::TextureDescriptor{
					size,
					mip_level_count:max_mips,
					sample_count:1,
					dimension:wgpu::TextureDimension::D2,
					format:wgpu::TextureFormat::Bc7RgbaUnorm,
					usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
					label:Some("Squid Texture"),
					view_formats:&[],
				},
				wgpu::util::TextureDataOrder::LayerMajor,
				&image.data,
			);

			texture.create_view(&wgpu::TextureViewDescriptor{
				label:Some("Squid Texture View"),
				dimension:Some(wgpu::TextureViewDimension::D2),
				..wgpu::TextureViewDescriptor::default()
			})
		};

		let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
			label:None,
			bind_group_layouts:&[
				&camera_bind_group_layout,
				&skybox_texture_bind_group_layout,
				&model_bind_group_layout,
			],
			immediate_size:0,
		});
		let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
			label:None,
			bind_group_layouts:&[
				&camera_bind_group_layout,
				&skybox_texture_bind_group_layout,
			],
			immediate_size:0,
		});

		// Create the render pipelines
		let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
			label:Some("Sky Pipeline"),
			layout:Some(&sky_pipeline_layout),
			vertex:wgpu::VertexState{
				module:&shader,
				entry_point:Some("vs_sky"),
				buffers:&[],
				compilation_options:wgpu::PipelineCompilationOptions::default(),
			},
			fragment:Some(wgpu::FragmentState{
				module:&shader,
				entry_point:Some("fs_sky"),
				targets:&[Some(config.view_formats[0].into())],
				compilation_options:wgpu::PipelineCompilationOptions::default(),
			}),
			primitive:wgpu::PrimitiveState{
				front_face:wgpu::FrontFace::Cw,
				..Default::default()
			},
			depth_stencil:Some(wgpu::DepthStencilState{
				format:Self::DEPTH_FORMAT,
				depth_write_enabled:false,
				depth_compare:wgpu::CompareFunction::LessEqual,
				stencil:wgpu::StencilState::default(),
				bias:wgpu::DepthBiasState::default(),
			}),
			multisample:wgpu::MultisampleState::default(),
			multiview_mask:None,
			cache:None,
		});
		let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
			label:Some("Model Pipeline"),
			layout:Some(&model_pipeline_layout),
			vertex:wgpu::VertexState{
				module:&shader,
				entry_point:Some("vs_entity_texture"),
				buffers:&[wgpu::VertexBufferLayout{
					array_stride:size_of::<GraphicsVertex>() as wgpu::BufferAddress,
					step_mode:wgpu::VertexStepMode::Vertex,
					attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4],
				}],
				compilation_options:wgpu::PipelineCompilationOptions::default(),
			},
			fragment:Some(wgpu::FragmentState{
				module:&shader,
				entry_point:Some("fs_entity_texture"),
				targets:&[Some(config.view_formats[0].into())],
				compilation_options:wgpu::PipelineCompilationOptions::default(),
			}),
			primitive:wgpu::PrimitiveState{
				front_face:wgpu::FrontFace::Cw,
				cull_mode:Some(wgpu::Face::Front),
				..Default::default()
			},
			depth_stencil:Some(wgpu::DepthStencilState{
				format:Self::DEPTH_FORMAT,
				depth_write_enabled:true,
				depth_compare:wgpu::CompareFunction::LessEqual,
				stencil:wgpu::StencilState::default(),
				bias:wgpu::DepthBiasState::default(),
			}),
			multisample:wgpu::MultisampleState::default(),
			multiview_mask:None,
			cache:None,
		});

		let camera=GraphicsCamera::default();
		let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
		let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
			label:Some("Camera"),
			contents:bytemuck::cast_slice(&camera_uniforms),
			usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
		});
		let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
			layout:&camera_bind_group_layout,
			entries:&[
				wgpu::BindGroupEntry{
					binding:0,
					resource:camera_buf.as_entire_binding(),
				},
			],
			label:Some("Camera"),
		});

		let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
			layout:&skybox_texture_bind_group_layout,
			entries:&[
				wgpu::BindGroupEntry{
					binding:0,
					resource:wgpu::BindingResource::TextureView(&skybox_texture_view),
				},
				wgpu::BindGroupEntry{
					binding:1,
					resource:wgpu::BindingResource::Sampler(&clamp_sampler),
				},
			],
			label:Some("Sky Texture"),
		});

		let depth_view=Self::create_depth_texture(config,device);

		Self{
			pipelines:GraphicsPipelines{
				skybox:sky_pipeline,
				model:model_pipeline
			},
			bind_groups:GraphicsBindGroups{
				camera:camera_bind_group,
				skybox_texture:skybox_texture_bind_group,
			},
			camera,
			camera_buf,
			models:Vec::new(),
			depth_view,
			staging_belt:wgpu::util::StagingBelt::new(device.clone(),0x100),
			bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
			samplers:GraphicsSamplers{repeat:repeat_sampler},
			temp_squid_texture_view:squid_texture_view,
		}
	}
	pub fn resize(
		&mut self,
		device:&wgpu::Device,
		config:&wgpu::SurfaceConfiguration,
		user_settings:&settings::UserSettings,
	){
		self.depth_view=Self::create_depth_texture(config,device);
		self.camera.screen_size=glam::uvec2(config.width,config.height);
		self.load_user_settings(user_settings);
	}
	pub fn render(
		&mut self,
		view:&wgpu::TextureView,
		device:&wgpu::Device,
		queue:&wgpu::Queue,
		frame_state:session::FrameState,
	){
		//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input

		let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});

		// update rotation
		let camera_uniforms=self.camera.to_uniform_data(
			frame_state.body.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
			frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
		);
		self.staging_belt
			.write_buffer(
				&mut encoder,
				&self.camera_buf,
				0,
				wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
			)
			.copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
		//This code only needs to run when the uniforms change
		/*
		for model in self.models.iter(){
			let model_uniforms=get_instances_buffer_data(&model.instances);
			self.staging_belt
				.write_buffer(
					&mut encoder,
					&model.model_buf,//description of where data will be written when command is executed
					0,//offset in staging belt?
					wgpu::BufferSize::new((model_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
					device,
				)
				.copy_from_slice(bytemuck::cast_slice(&model_uniforms));
		}
		*/
		self.staging_belt.finish();

		{
			let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
				label:None,
				color_attachments:&[Some(wgpu::RenderPassColorAttachment{
					view,
					resolve_target:None,
					ops:wgpu::Operations{
						load:wgpu::LoadOp::Clear(wgpu::Color{
							r:0.1,
							g:0.2,
							b:0.3,
							a:1.0,
						}),
						store:wgpu::StoreOp::Store,
					},
					depth_slice:None,
				})],
				depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{
					view:&self.depth_view,
					depth_ops:Some(wgpu::Operations{
						load:wgpu::LoadOp::Clear(1.0),
						store:wgpu::StoreOp::Discard,
					}),
					stencil_ops:None,
				}),
				timestamp_writes:Default::default(),
				occlusion_query_set:Default::default(),
				multiview_mask:None,
			});

			rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
			rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);

			rpass.set_pipeline(&self.pipelines.model);
			for model in &self.models{
				rpass.set_bind_group(2,&model.bind_group,&[]);
				rpass.set_vertex_buffer(0,model.vertex_buf.slice(..));
				rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format);
				//TODO: loop over triangle strips
				rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count);
			}

			rpass.set_pipeline(&self.pipelines.skybox);
			rpass.draw(0..3,0..1);
		}

		queue.submit(std::iter::once(encoder.finish()));

		self.staging_belt.recall();
	}
}
