/*
 * Copyright (c) 2021 Callum Newlands
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     The additional term of 7.b applies: Requiring preservation of specified
 *     reasonable legal notices or author attributions in that material or in the
 *     Appropriate Legal Notices displayed by works containing it
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package generation;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.joml.Matrix4f;
import params.ParameterLoader;
import params.Parameters;
import rendering.LevelOfDetail;
import sceneobjects.Tree;

public class TreePool {
	private static final Parameters parameters = ParameterLoader.getParameters();
	private static TreePool instance;

	private final List<List<Tree>> treesByType = new ArrayList<>();
	private final List<Tree.Mask> minMasks = new ArrayList<>();
	private final List<Tree.Mask> maxMasks = new ArrayList<>();

	private TreePool() {
		System.out.println("Calculating tree masks");
		int numTreeTypes = parameters.sceneObjects.trees.size();
		for (int type = 0; type < numTreeTypes; type++) {
			Parameters.SceneObjects.Tree params = parameters.sceneObjects.trees.get(type);
			treesByType.add(new ArrayList<>());
			minMasks.add((new Tree(type, params.minIterations)).getMask());
			maxMasks.add((new Tree(type, params.maxIterations - 1)).getMask());
		}
	}

	public static TreePool getTreePool() {
		if (instance == null) {
			instance = new TreePool();
		}
		return instance;
	}

//	public int getTreeIndex(int type) {
//		Random r = parameters.random.generator;
//		return r.nextInt(treesByType.get(type).size());
//	}

//	public Tree.Reference getTree(int type, Vector2f regionCentre, float regionWidth, TerrainQuadtree quadtree) {
//		Random r = parameters.random.generator;
//		Parameters.SceneObjects.Tree params = parameters.sceneObjects.trees.get(type);
////		float x = (r.nextFloat() - 0.5f) * regionWidth + regionCentre.x;
////		float z = (r.nextFloat() - 0.5f) * regionWidth + regionCentre.y;
////		float y = quadtree.getHeight(x, z) + params.yOffset;
////		Matrix4f model = new Matrix4f()
////				.identity()
////				.translate(x, y, z);
//		Matrix4f model = new Matrix4f().identity();
//		if (params.pitchVariability > 0) {
//			model = model.rotate(
//							r.nextFloat() * (float) Math.PI * params.pitchVariability,
//							new Vector3f(r.nextFloat(), 0, r.nextFloat()).normalize()
//					);
//		}
//		model = model.rotate(r.nextFloat() * (float) Math.PI * 2, new Vector3f(0, 1, 0))
//				.scale(params.scale * (
//						r.nextFloat() * (params.maxScaleFactor - params.minScaleFactor) + params.minScaleFactor)
//				);
//		int index = r.nextInt(trees.get(type).size());
//		return new Tree.Reference(type, index, new Vector3f(), model);
//	}

	public Tree getTree(int type, int index) {
		return treesByType.get(type).get(index);
	}

	public Tree.Mask getMinimumMask(int type) {
		return minMasks.get(type);
	}

	public Tree.Mask getMaximumMask(int type) {
		return maxMasks.get(type);
	}

	public Tree.Mask getInstanceMask(int type, int index) {
		return getTree(type, index).getMask();
	}

	public void renderTreeWithModel(int type, int index, Matrix4f model, LevelOfDetail levelOfDetail, boolean renderForShadows) {
		Tree tree = getTree(type, index);
		tree.setModelMatrix(model);
		tree.render(levelOfDetail, renderForShadows);
	}

	private List<Integer> getIndicesByTypeAndIterations(int type, int iterations) {
		int maxI = treesByType.get(type).size();

		return IntStream.range(0, maxI)
				.filter(i -> treesByType.get(type).get(i).getNumIterations() == iterations)
				.boxed()
				.collect(Collectors.toList());
	}

	public int getTreeIndexWithIterations(int type, int iterations) {
		Parameters.SceneObjects.Tree params = parameters.sceneObjects.trees.get(type);
		List<Integer> treesOfIterationSize = getIndicesByTypeAndIterations(type, iterations);
		if (treesOfIterationSize.size() < params.numPerIterationSize) {
			treesByType.get(type).add(new Tree(type, iterations));
			return treesByType.get(type).size() - 1;
		}
		Random r = parameters.random.generator;
		return treesOfIterationSize.get(r.nextInt(treesOfIterationSize.size()));
	}

	public void printGenerationStatistics() {
		int numTreeTypes = parameters.sceneObjects.trees.size();
		for (int type = 0; type < numTreeTypes; type++) {
			int numModels = treesByType.get(type).size();
			String name = parameters.sceneObjects.trees.get(type).name;
			System.out.println(numModels + " tree model(s) generated for tree: " + name);
		}
	}
}
