use crate::aabb::Aabb;

//da algaritum
//lista boxens
//sort by {minx,maxx,miny,maxy,minz,maxz} (6 lists)
//find the sets that minimizes the sum of surface areas
//splitting is done when the minimum split sum of surface areas is larger than the node's own surface area

//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
//sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list

pub enum RecursiveContent<R,T>{
	Branch(Vec<R>),
	Leaf(T),
}
impl<R,T> Default for RecursiveContent<R,T>{
	fn default()->Self{
		Self::Branch(Vec::new())
	}
}
pub struct BvhNode<T>{
	content:RecursiveContent<BvhNode<T>,T>,
	aabb:Aabb,
}
impl<T> Default for BvhNode<T>{
	fn default()->Self{
		Self{
			content:Default::default(),
			aabb:Aabb::default(),
		}
	}
}
pub struct BvhWeightNode<W,T>{
	content:RecursiveContent<BvhWeightNode<W,T>,T>,
	weight:W,
	aabb:Aabb,
}

impl<T> BvhNode<T>{
	pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
		match &self.content{
			RecursiveContent::Leaf(model)=>f(model),
			RecursiveContent::Branch(children)=>for child in children{
				//this test could be moved outside the match statement
				//but that would test the root node aabb
				//you're probably not going to spend a lot of time outside the map,
				//so the test is extra work for nothing
				if aabb.intersects(&child.aabb){
					child.the_tester(aabb,f);
				}
			},
		}
	}
	pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
		match self.content{
			RecursiveContent::Leaf(model)=>f(model),
			RecursiveContent::Branch(children)=>for child in children{
				child.into_visitor(f)
			},
		}
	}
	pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
		match self.content{
			RecursiveContent::Leaf(model)=>BvhWeightNode{
				weight:f(&model),
				content:RecursiveContent::Leaf(model),
				aabb:self.aabb,
			},
			RecursiveContent::Branch(children)=>{
				let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
					child.weigh_contents(f)
				).collect();
				BvhWeightNode{
					weight:branch.iter().map(|node|node.weight).sum(),
					content:RecursiveContent::Branch(branch),
					aabb:self.aabb,
				}
			},
		}
	}
}

impl <W,T> BvhWeightNode<W,T>{
	pub const fn weight(&self)->&W{
		&self.weight
	}
	pub const fn aabb(&self)->&Aabb{
		&self.aabb
	}
	pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
		self.content
	}
	pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
		match self.content{
			RecursiveContent::Leaf(model)=>f(model),
			RecursiveContent::Branch(children)=>for child in children{
				child.into_visitor(f)
			},
		}
	}
}

pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
	generate_bvh_node(boxen,false)
}

fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
	let n=boxen.len();
	if force||n<20{
		let mut aabb=Aabb::default();
		let nodes=boxen.into_iter().map(|b|{
			aabb.join(&b.1);
			BvhNode{
				content:RecursiveContent::Leaf(b.0),
				aabb:b.1,
			}
		}).collect();
		BvhNode{
			content:RecursiveContent::Branch(nodes),
			aabb,
		}
	}else{
		let mut sort_x=Vec::with_capacity(n);
		let mut sort_y=Vec::with_capacity(n);
		let mut sort_z=Vec::with_capacity(n);
		for (i,(_,aabb)) in boxen.iter().enumerate(){
			let center=aabb.center();
			sort_x.push((i,center.x));
			sort_y.push((i,center.y));
			sort_z.push((i,center.z));
		}
		sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
		sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
		sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
		let h=n/2;
		let median_x=sort_x[h].1;
		let median_y=sort_y[h].1;
		let median_z=sort_z[h].1;
		//locate a run of values equal to the median
		//partition point gives the first index for which the predicate evaluates to false
		let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
		let first_index_eq_median_y=sort_y.partition_point(|&(_,y)|y<median_y);
		let first_index_eq_median_z=sort_z.partition_point(|&(_,z)|z<median_z);
		let first_index_gt_median_x=sort_x.partition_point(|&(_,x)|x<=median_x);
		let first_index_gt_median_y=sort_y.partition_point(|&(_,y)|y<=median_y);
		let first_index_gt_median_z=sort_z.partition_point(|&(_,z)|z<=median_z);
		//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
		let partition_point_x=if n.abs_diff(2*first_index_eq_median_x)<n.abs_diff(2*first_index_gt_median_x){first_index_eq_median_x}else{first_index_gt_median_x};
		let partition_point_y=if n.abs_diff(2*first_index_eq_median_y)<n.abs_diff(2*first_index_gt_median_y){first_index_eq_median_y}else{first_index_gt_median_y};
		let partition_point_z=if n.abs_diff(2*first_index_eq_median_z)<n.abs_diff(2*first_index_gt_median_z){first_index_eq_median_z}else{first_index_gt_median_z};
		//this ids which octant the boxen is put in
		let mut octant=vec![0;n];
		for &(i,_) in &sort_x[partition_point_x..]{
			octant[i]+=1<<0;
		}
		for &(i,_) in &sort_y[partition_point_y..]{
			octant[i]+=1<<1;
		}
		for &(i,_) in &sort_z[partition_point_z..]{
			octant[i]+=1<<2;
		}
		//generate lists for unique octant values
		let mut list_list=Vec::with_capacity(8);
		let mut octant_list=Vec::with_capacity(8);
		for (i,(data,aabb)) in boxen.into_iter().enumerate(){
			let octant_id=octant[i];
			let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){
				list_id
			}else{
				let list_id=list_list.len();
				octant_list.push(octant_id);
				list_list.push(Vec::new());
				list_id
			};
			list_list[list_id].push((data,aabb));
		}
		let mut aabb=Aabb::default();
		if list_list.len()==1{
			generate_bvh_node(list_list.remove(0),true)
		}else{
			BvhNode{
				content:RecursiveContent::Branch(
					list_list.into_iter().map(|b|{
						let node=generate_bvh_node(b,false);
						aabb.join(&node.aabb);
						node
					}).collect()
				),
				aabb,
			}
		}
	}
}
