use std::collections::*;

use crate::glezz;
use crate::shader_closure::*;

#[derive (Copy, Clone, PartialEq, Eq)]
pub enum FrontFace {
	Cw,
	Ccw,
}

impl From <FrontFace> for u32 {
	fn from (v: FrontFace) -> Self {
		use FrontFace::*;
		{
		use gl::*;
		match v {
			Cw => CW,
			Ccw => CCW,
		}
		}
	}
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub enum StencilOp {
	Keep, 
	Zero, 
	Replace, 
	Incr, 
	Decr, 
	Invert,
	IncrWrap, 
	DecrWrap,
}

impl From <StencilOp> for u32 {
	fn from (v: StencilOp) -> Self {
		use StencilOp::*;
		{
		use gl::*;
		match v {
			Keep => KEEP,
			Zero => ZERO,
			Replace => REPLACE,
			Incr => INCR,
			Decr => DECR,
			Invert => INVERT,
			IncrWrap => INCR_WRAP,
			DecrWrap => DECR_WRAP,
		}
		}
	}
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub enum StencilFunc {
	Never, 
	Always, 
	Less, 
	LessEqual, 
	Equal, 
	Greater, 
	GreaterEqual,
	NotEqual,
}

impl From <StencilFunc> for u32 {
	fn from (v: StencilFunc) -> Self {
		use StencilFunc::*;
		{
		use gl::*;
		match v {
			Never => NEVER,
			Always => ALWAYS,
			Less => LESS,
			LessEqual => LEQUAL,
			Equal => EQUAL,
			Greater => GREATER,
			GreaterEqual => GEQUAL,
			NotEqual => NOTEQUAL,
		}
		}
	}
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub enum DepthFunc {
	Never, 
	Always, 
	Less, 
	LessEqual, 
	Equal, 
	Greater, 
	GreaterEqual,
	NotEqual,
}

impl From <DepthFunc> for u32 {
	fn from (v: DepthFunc) -> Self {
		use DepthFunc::*;
		{
		use gl::*;
		match v {
			Never => NEVER,
			Always => ALWAYS,
			Less => LESS,
			LessEqual => LEQUAL,
			Equal => EQUAL,
			Greater => GREATER,
			GreaterEqual => GEQUAL,
			NotEqual => NOTEQUAL,
		}
		}
	}
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub struct StencilOpState {
	pub sfail: StencilOp,
	pub dpfail: StencilOp,
	pub dppass: StencilOp,
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub struct StencilFuncState {
	pub func: StencilFunc,
	pub reference: i32,
	pub mask: u32,
}

#[derive (Copy, Clone, PartialEq, Eq)]
pub struct StencilState {
	pub op: StencilOpState,
	pub func: StencilFuncState,
}

// These are POD where no extra data is needed
// to safely use the flags / numbers

pub struct IsoGlState {
	pub shader_id: Option <u32>,
	
	pub flags: HashMap <u32, bool>,
	pub front_face: Option <FrontFace>,
	pub stencil: Option <StencilState>,
	pub depth_func: Option <DepthFunc>,
	
	pub color_mask: Option <(u8, u8, u8, u8)>,
	pub depth_mask: Option <u8>,
	pub stencil_mask: Option <u32>,
}

impl std::default::Default for IsoGlState {
	fn default () -> Self {
		Self {
			shader_id: None,
			flags: hashmap! {},
			front_face: None,
			stencil: None,
			depth_func: None,
			color_mask: None,
			depth_mask: None,
			stencil_mask: None,
		}
	}
}

// These are POD IDs or hashes of non-POD data

pub struct NonIsoGlState {
}

impl std::default::Default for NonIsoGlState {
	fn default () -> Self {
		Self {
		}
	}
}

pub struct GlState {
	pub iso: IsoGlState,
	pub non_iso: NonIsoGlState,
}

impl std::default::Default for GlState {
	fn default () -> Self {
		Self {
			iso: Default::default (),
			non_iso: Default::default (),
		}
	}
}

pub struct Pass {
	// In the context of a Pass, "None" means "Don't care"
	
	pub iso: IsoGlState,
}

impl Pass {
	pub fn apply_diff (&self, old_state: &mut IsoGlState) {
		let state = &self.iso;
		
		let mut flag_elision_count = 0;
		
		for (flag, value) in state.flags.iter () {
			let old_entry = old_state.flags.entry (*flag);
			
			if match &old_entry {
				hash_map::Entry::Vacant (_) => true,
				hash_map::Entry::Occupied (o) => o.get () != value,
			} {
				if *value {
					glezz::enable (*flag);
				}
				else {
					glezz::disable (*flag);
				}
				
				(*old_entry.or_insert (*value)) = *value;
			}
			else {
				flag_elision_count += 1;
			}
		}
		if flag_elision_count > 0 {
			//println! ("Elided {} flags", flag_elision_count);
		}
		
		if let Some (v) = &state.front_face {
			if old_state.front_face != state.front_face {
				glezz::front_face ((*v).into ());
				old_state.front_face = state.front_face;
			}
			else {
				//println! ("Elided front_face ()");
			}
		}
		
		if let Some (v) = &state.stencil {
			if old_state.stencil != state.stencil {
				let func = &v.func;
				unsafe {
					gl::StencilFunc (
						func.func.into (), 
						func.reference.into (), 
						func.mask.into ()
					);
				}
				
				let op = &v.op;
				unsafe {
					gl::StencilOp (
						op.sfail.into (), 
						op.dpfail.into (), 
						op.dppass.into ()
					);
				}
			}
			else {
				//println! ("Elided stencil state");
			}
		}
		
		if let Some (v) = &state.depth_func {
			if old_state.depth_func != state.depth_func {
				glezz::depth_func ((*v).into ());
				old_state.depth_func = state.depth_func;
			}
			else {
				//println! ("Elided depth_func ()");
			}
		}
		
		if let Some ((r, g, b, a)) = &state.color_mask {
			if old_state.color_mask != state.color_mask {
				glezz::color_mask (*r, *g, *b, *a);
				old_state.color_mask = state.color_mask;
			}
			else {
				//println! ("Elided color_mask ()");
			}
		}
		
		if let Some (v) = &state.depth_mask {
			if old_state.depth_mask != state.depth_mask {
				glezz::depth_mask (*v);
				old_state.depth_mask = state.depth_mask;
			}
			else {
				//println! ("Elided depth_mask ()");
			}
		}
		
		if let Some (v) = &state.stencil_mask {
			if old_state.stencil_mask != state.stencil_mask {
				glezz::stencil_mask (*v);
				old_state.stencil_mask = state.stencil_mask;
			}
			else {
				//println! ("Elided stencil_mask ()");
			}
		}
	}
	
	pub fn apply_slow (&self) {
		let mut iso = IsoGlState::default ();
		
		self.apply_diff (&mut iso);
	}
	
	pub fn with <F> (&self, gl_state: &mut GlState, callback: F)
	where F: Fn ()
	{
		self.apply_diff (&mut gl_state.iso);
		callback ();
	}
	
	pub fn with_shader <F, S> (
		&self, 
		gl_state: &mut GlState, 
		shader_component: &S, 
		callback: F
	) 
	where F: Fn (BorrowedShaderVars), S: ShaderLookup
	{
		if let Some (s) = self.iso.shader_id {
			self.apply_diff (&mut gl_state.iso);
			//self.apply_slow ();
			shader_component.lookup (s).with (gl_state.iso.shader_id, |shader_vars| {
				callback (shader_vars);
			});
			gl_state.iso.shader_id = Some (s);
		}
		else {
			panic! ("Called with_shader on a pass with no shader");
		}
	}
}
