/**
 * Skript zajistuje spravnou funkcnost Octree, reprezentuje jeden uzel stromu.
 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/**
 * Author:			Karel Zavrel
 * 
 * Thesis:			An interactive application for development of creativity
 * Application:		Little Builder
 */
public class MyOctreeNode {
	/**
	 * promenne:
	 * 	- nadrazeny uzel daneho uzlu
	 * 	- pripadni potomci daneho uzlu
	 * 	- stred obaloveho objektu uzlu
	 * 	- obalovy objekt uzlu
	 * 	- velikost obaloveho objektu
	 * 	- minimalni velikost uzlu, pod kterou jiz neni dale delen
	 * 	- pocet s kolika z 8 potomku se dana kosticka protina
	 * 	- index potomka, se kterym bylo nalezeno protnuti (pouze kdyz existuje jen jeden protnuty potomek)
	 * 	- objekty v prostoru, ktere nespadaji do poduzlu prislusneho uzlu a jsou uchovane v tomto
	 * 	- podminka pro overeni, zda ma uzel vytvorene potomky
	 */
	private MyOctreeNode parent;
	private MyOctreeNode[] children = new MyOctreeNode[8];
	private Vector3 centerOfNode;
	private Bounds boundsOfNode;
	private float sizeOfBounds, minSize = 4.0f;
	private int countOfIntersectedChildren = 0;
	private int indexOfIntersectedSpace = -1;
	private List<OctreeObjects> objectsInNode = new List<OctreeObjects> ();
	private bool childrenAreSet = false;
	/**
	 * Vnitrni trida reprezentujici objekty ulozene v Octree
	 * 	- Vsechny objekty v Octree sestavaji z herniho objektu kosticky GameObject a jejiho obaloveho objektu Bounds
	 */
	class OctreeObjects{
		/**
		 * promenne:
		 * 	-	jedna z kosticek v modelu
		 * 	-	obalovy objekt dane kosticky
		 */
		private GameObject brickInModel;
		private Bounds boundsOfThisBrick;
		/**
		 * verejny konstruktor
		 */
		public OctreeObjects(GameObject brick, Bounds bounds){
			this.brickInModel = brick;
			this.boundsOfThisBrick = bounds;
		}
		/**
		 * getter pro promennou brickInModel
		 */
		public GameObject getBrickInModel(){
			return brickInModel;
		}
		/**
		 * setter pro promennou brickInModel
		 */
		public void setBrickInModel(GameObject brick){
			this.brickInModel = brick;
		}
		/**
		 * getter pr promennou boundsOfThisBrick
		 */
		public Bounds getBoundsOfThisBrick(){
			return boundsOfThisBrick;
		}
		/**
		 * setter pro promennou boundsOfThisBrick
		 */
		public void setBoundsOfThisBrick(Bounds bounds){
			this.boundsOfThisBrick = bounds;
		}
	}
	/**
	 * LHP, PHP, LDP, PDP, LHZ, PHZ, LDZ, PDZ
	 */
	private bool[] mask = new bool[8];
	/**
	 * verejny konstruktor
	 */
	public MyOctreeNode (Bounds bounds, MyOctreeNode parent){
		this.boundsOfNode = bounds;
		this.parent = parent;
		setMaskToFalse ();
		this.sizeOfBounds = boundsOfNode.extents.x * 2.0f;
	}
	/**
	 * getter pro promennou boundsOfNode
	 */
	public Bounds getBoundsOfNode(){
		return boundsOfNode;
	}
	/**
	 * funkce pro pridani kosticky do stromu
	 */
	public bool addBrickToOctree(GameObject brick, Bounds bounds){
		if (intersectsWithObjectsInNode (bounds)) {
			return false;
		} else {
			if (sizeOfBounds > minSize) {
				return subAddBrickToOctree (brick, bounds);
			} else {
				// size je 4.0, nemuzu dale delit a z predchozi podminky mi vyplyva, ze se neprotina s zadnym objektem zde vlozenym
				objectsInNode.Add(new OctreeObjects(brick, bounds));
				return true;
			}
		}
	}
	/**
	 * funkce pro zjisteni, zda se objekt neprotina jiz s nejakym pridanym v tomto uzlu
	 */
	private bool intersectsWithObjectsInNode(Bounds bounds){
		for (int i = 0; i < objectsInNode.Count; i++) {
			if (objectsInNode [i].getBoundsOfThisBrick ().Intersects (bounds)) {
				return true;
			}
		}
		return false;
	}
	/**
	 * funkce, ktera:
	 * 	-	nastavi potomky, a dalsi promenne
	 * 	-	projde potomky a zjisti, ktere objekt protina
	 * 	-	dle toho nastavi masku
	 */
	private void getIntersectedChildren(Bounds bounds){
		if (!childrenAreSet) {
			setChildren ();
		}
		countOfIntersectedChildren = 0;
		indexOfIntersectedSpace = -1;
		setMaskToFalse ();
		for (int i = 0; i < children.Length; i++) {
			if (children [i].intersectsWithNode (bounds)) {
				countOfIntersectedChildren++;
				mask [i] = true;
				indexOfIntersectedSpace = i;
			}
		}
	}
	/**
	 * funkce pro pridani kosticky do uzlu, kdyz ho lze dale delit
	 */
	private bool subAddBrickToOctree(GameObject brick, Bounds bounds){
		getIntersectedChildren (bounds);
		if (countOfIntersectedChildren == 1) {
			return children[indexOfIntersectedSpace].addBrickToOctree(brick, bounds);
		} else {
			if (!checkIntersectsWithObjectsInEachOfChildren (bounds)) {
				return false;
			} else {
				objectsInNode.Add (new OctreeObjects (brick, bounds));
				return true;
			}
		}
	}
	/**
	 * funkce overi, zda se kosticka neprotina jiz s nejakou v celem podstromu
	 * 	-	aby bylo mozne ji zaradit do daneho uzlu
	 */
	private bool checkIntersectsWithObjectsInEachOfChildren(Bounds bounds){
		bool condition = true;
		for (int i = 0; i < children.Length; i++) {
			if (children [i].intersectsWithObjectsInNode (bounds)) {
				return false;
			} else {
				if (children [i].getChildrenAreSet ()) {
					if (condition) {
						condition = children [i].checkIntersectsWithObjectsInEachOfChildren (bounds);
					} else {
						return false;
					}
				} else {
					if (condition) {
						condition = true;
					} else {
						return false;
					}
				}
			}
		}
		return condition;
	}
		public bool getChildrenAreSet(){
				return childrenAreSet;
		}
	/**
	 * funkce pro overeni, zda se kostka nachazi v danem uzlu
	 */
	private bool intersectsWithNode(Bounds bounds){
		return boundsOfNode.Intersects (bounds);
	}
	/**
	 * funkce, ktera nastavi mask na sama false
	 */
	private void setMaskToFalse(){
		for (int i = 0; i < mask.Length; i++) {
			mask [i] = false;
		}
	}
	/**
	 * funkce pro vytvoreni vsech detskych uzlu
	 * 	-	LHP, PHP, LDP, PDP, LHZ, PHZ, LDZ, PDZ
	 */
	private void setChildren(){
		float difference = boundsOfNode.extents.x / 2.0f;
		children[0] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x - difference, boundsOfNode.center.y + difference, boundsOfNode.center.z - difference), boundsOfNode.extents), this);
		children[1] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x + difference, boundsOfNode.center.y + difference, boundsOfNode.center.z - difference), boundsOfNode.extents), this);
		children[2] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x - difference, boundsOfNode.center.y - difference, boundsOfNode.center.z - difference), boundsOfNode.extents), this);
		children[3] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x + difference, boundsOfNode.center.y - difference, boundsOfNode.center.z - difference), boundsOfNode.extents), this);
		children[4] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x - difference, boundsOfNode.center.y + difference, boundsOfNode.center.z + difference), boundsOfNode.extents), this);
		children[5] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x + difference, boundsOfNode.center.y + difference, boundsOfNode.center.z + difference), boundsOfNode.extents), this);
		children[6] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x - difference, boundsOfNode.center.y - difference, boundsOfNode.center.z + difference), boundsOfNode.extents), this);
		children[7] = new MyOctreeNode(new Bounds(new Vector3(boundsOfNode.center.x + difference, boundsOfNode.center.y - difference, boundsOfNode.center.z + difference), boundsOfNode.extents), this);
		childrenAreSet = true;
	}
	/**
	 * funkce pro odstraneni objektu z octree
	 */
	public bool removeBrickFromOctree(GameObject brick, Bounds bounds){
		bool condition = false;
		if (isBrickInNode (brick, bounds)) {
			return true;
		} else {
			if (getChildrenAreSet ()) {
				for (int i = 0; i < children.Length; i++) {
					if (!condition) {
						condition = children [i].removeBrickFromOctree (brick, bounds);
					} else {
						return true;
					}
				}
			} else {
				return false;
			}
		}
		return condition;
	}
	/**
	 * funkce zjisti, zda je objekt v uzlu a pripadne ho vymaze
	 */
	private bool isBrickInNode(GameObject brick, Bounds bounds){
		for (int i = 0; i < objectsInNode.Count; i++) {
			if (objectsInNode [i].getBrickInModel ().Equals (brick) && objectsInNode [i].getBoundsOfThisBrick ().Equals (bounds)) {
				objectsInNode.Remove (objectsInNode [i]);
				return true;
			}
		}
		return false;
	}
	/**
	 * funkce pro detekci kolize s paprskem
	 * 	- pokud se neprotina s obalovym objektem uzlu, vrati false
	 * 	- jinak hleda protnuti v seznamu objetu v tomto uzlu a pokud nenachazi a uzel ma potomky, tak cely proces provede napric vsemi potomky
	 */
	public bool checkCollisions(Ray ray){
		bool condition = false;
		if (boundsOfNode.IntersectRay (ray)) {
			if (checkCollisionsWithObjectsInNode (ray)) {
				return true;
			} else {
				if (childrenAreSet) {
					for (int i = 0; i < children.Length; i++) {
						if (!condition) {
							condition = children [i].checkCollisions (ray);
						}
					}
				} else {
					return false;
				}
			}
		}
		return condition;
	}
	/**
	 * funkce, ktera zkontroluje kolize paprsku s objekty v uzlu
	 * 	-	pokud zadny objekt neprotina, tak vrati false
	 */
	private bool checkCollisionsWithObjectsInNode(Ray ray){
		for (int i = 0; i < objectsInNode.Count; i++) {
			if (objectsInNode [i].getBoundsOfThisBrick ().IntersectRay (ray)) {
				InnerStoringScript.setSelectedBrick (objectsInNode [i].getBrickInModel ());
				InnerStoringScript.setSelectedBrickBounds (objectsInNode [i].getBoundsOfThisBrick ());
				return true;
			}
		}
		return false;
	}
	/**
	 * funkce na vymazani obsahu celeho stromu
	 */
	public bool cleanTree(List<GameObject> allB, List<Bounds> allBB){
		bool condition = true;
		for (int i = 0; i < allBB.Count; i++) {
			if (condition) {
				condition = removeBrickFromOctree (allB [i + 1], allBB [i]);
			}
		}
		return condition;
	}
}