use std::cmp::Ordering;
use std::collections::BTreeMap;

use crate::aabb::Aabb;
use crate::ray::Ray;
use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction};

//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 fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
	// n.(o+d*t)==n.p
	// n.o + n.d * t == n.p
	// t == (n.p - n.o)/n.d
	let mut hit=None;
	match ray.direction.x.cmp(&Planar64::ZERO){
		Ordering::Less=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dy=rel_max.x*ray.direction.y;
			let dz=rel_max.x*ray.direction.z;
			// x is negative, so inequalities are flipped
			if rel_min.y*ray.direction.x>dy&&dy>rel_max.y*ray.direction.x
			 &&rel_min.z*ray.direction.x>dz&&dz>rel_max.z*ray.direction.x{
				let t=rel_max.x/ray.direction.x;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
		Ordering::Equal=>(),
		Ordering::Greater=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dy=rel_min.x*ray.direction.y;
			let dz=rel_min.x*ray.direction.z;
			// x is positive, so inequalities are normal
			if rel_min.y*ray.direction.x<dy&&dy<rel_max.y*ray.direction.x
			 &&rel_min.z*ray.direction.x<dz&&dz<rel_max.z*ray.direction.x{
				let t=rel_min.x/ray.direction.x;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
	}
	match ray.direction.z.cmp(&Planar64::ZERO){
		Ordering::Less=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dx=rel_max.z*ray.direction.x;
			let dy=rel_max.z*ray.direction.y;
			// z is negative, so inequalities are flipped
			if rel_min.x*ray.direction.z>dx&&dx>rel_max.x*ray.direction.z
			 &&rel_min.y*ray.direction.z>dy&&dy>rel_max.y*ray.direction.z{
				let t=rel_max.z/ray.direction.z;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
		Ordering::Equal=>(),
		Ordering::Greater=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dx=rel_min.z*ray.direction.x;
			let dy=rel_min.z*ray.direction.y;
			// z is positive, so inequalities are normal
			if rel_min.x*ray.direction.z<dx&&dx<rel_max.x*ray.direction.z
			 &&rel_min.y*ray.direction.z<dy&&dy<rel_max.y*ray.direction.z{
				let t=rel_min.z/ray.direction.z;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
	}
	match ray.direction.y.cmp(&Planar64::ZERO){
		Ordering::Less=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dz=rel_max.y*ray.direction.z;
			let dx=rel_max.y*ray.direction.x;
			// y is negative, so inequalities are flipped
			if rel_min.z*ray.direction.y>dz&&dz>rel_max.z*ray.direction.y
			 &&rel_min.x*ray.direction.y>dx&&dx>rel_max.x*ray.direction.y{
				let t=rel_max.y/ray.direction.y;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
		Ordering::Equal=>(),
		Ordering::Greater=>{
			let rel_min=aabb.min()-ray.origin;
			let rel_max=aabb.max()-ray.origin;
			let dz=rel_min.y*ray.direction.z;
			let dx=rel_min.y*ray.direction.x;
			// y is positive, so inequalities are normal
			if rel_min.z*ray.direction.y<dz&&dz<rel_max.z*ray.direction.y
			 &&rel_min.x*ray.direction.y<dx&&dx<rel_max.x*ray.direction.y{
				let t=rel_min.y/ray.direction.y;
				hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
			}
		},
	}
	hit
}

pub enum RecursiveContent<N,L>{
	Branch(Vec<N>),
	Leaf(L),
}
impl<N,L> RecursiveContent<N,L>{
	pub fn empty()->Self{
		Self::Branch(Vec::new())
	}
}
pub struct BvhNode<L>{
	content:RecursiveContent<BvhNode<L>,L>,
	aabb:Aabb,
}
impl<L> BvhNode<L>{
	pub fn empty()->Self{
		Self{
			content:RecursiveContent::empty(),
			aabb:Aabb::default(),
		}
	}
	pub fn sample_aabb<F:FnMut(&L)>(&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.sample_aabb(aabb,f);
				}
			},
		}
	}
	fn populate_nodes<'a,T,F>(
		&'a self,
		collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
		nodes:&mut BTreeMap<Ratio<Planar64,Planar64>,&'a BvhNode<L>>,
		ray:&Ray,
		start_time:Ratio<Planar64,Planar64>,
		f:&F,
	)
		where
			T:Ord+Copy,
			Ratio<Planar64,Planar64>:From<T>,
			F:Fn(&L,&Ray)->Option<T>,
	{
		match &self.content{
			RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
				let ins=TimedInstruction{time:time.into(),instruction:leaf};
				if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
					collector.collect(Some(ins));
				}
			},
			RecursiveContent::Branch(children)=>for child in children{
				if child.aabb.contains(ray.origin){
					child.populate_nodes(collector,nodes,ray,start_time,f);
				}else{
					// Am I an upcoming superstar?
					if let Some(t)=intersect_aabb(ray,&child.aabb){
						if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
							nodes.insert(t,child);
						}
					}
				}
			},
		}
	}
	pub fn sample_ray<T,F>(
		&self,
		ray:&Ray,
		start_time:T,
		time_limit:T,
		f:F,
	)->Option<(T,&L)>
		where
			T:Ord+Copy,
			T:From<Ratio<Planar64,Planar64>>,
			Ratio<Planar64,Planar64>:From<T>,
			F:Fn(&L,&Ray)->Option<T>,
	{
		// source of nondeterminism when Aabb boundaries are coplanar
		let mut nodes=BTreeMap::new();

		let start_time=start_time.into();
		let time_limit=time_limit.into();
		let mut collector=InstructionCollector::new(time_limit);
		// break open all nodes that contain ray.origin and populate nodes with future intersection times
		self.populate_nodes(&mut collector,&mut nodes,ray,start_time,&f);

		// swim through nodes one at a time
		while let Some((t,node))=nodes.pop_first(){
			if collector.time()<t{
				break;
			}
			match &node.content{
				RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
					let ins=TimedInstruction{time:time.into(),instruction:leaf};
					// this lower bound can also be omitted
					// but it causes type inference errors lol
					if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
						collector.collect(Some(ins));
					}
				},
				// break open the node and predict collisions with the child nodes
				RecursiveContent::Branch(children)=>for child in children{
					// Am I an upcoming superstar?
					if let Some(t)=intersect_aabb(ray,&child.aabb){
						// we don't need to check the lower bound
						// because child aabbs are guaranteed to be within the parent bounds.
						if t<collector.time(){
							nodes.insert(t,child);
						}
					}
				},
			}
		}

		collector.take().map(|TimedInstruction{time,instruction:leaf}|(time.into(),leaf))
	}
	pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
		(self.content,self.aabb)
	}
	pub fn into_visitor<F:FnMut(L)>(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_key(|&(_,c)|c);
		sort_y.sort_by_key(|&(_,c)|c);
		sort_z.sort_by_key(|&(_,c)|c);
		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,
			}
		}
	}
}