/*
 * 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 params;

import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.joml.Vector3f;

@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
public class Parameters {

	public String resourcesRoot = "./resources/";
	public Random random = new Random();
	public Input input = new Input();
	public Output output = new Output();
	public Camera camera = new Camera();
	public Terrain terrain = new Terrain();
	public EcosystemSimulation ecosystemSimulation = new EcosystemSimulation();
	public Quadtree quadtree = new Quadtree();
	public SceneObjects sceneObjects = new SceneObjects();
	public Lighting lighting = new Lighting();

	@NoArgsConstructor
	@Setter
	public static class Random {
		public long seed = -1;
		@JsonIgnore
		public java.util.Random generator = null;
	}

	@NoArgsConstructor
	@Setter
	public static class Input {

		public RandomDataCollection randomDataCollection = new RandomDataCollection();
		@NoArgsConstructor
		@Setter
		public static class RandomDataCollection {
			public boolean enabled = false;
			public int maxFrames = 100;
		}

		public boolean manual = true;
		public StdIn stdin = new StdIn();

		@NoArgsConstructor
		@Setter
		public static class StdIn {
			public boolean enabled = false;
			public float lookOffset = 10f;
			public int fps = 30;
		}
	}

	@NoArgsConstructor
	@Setter
	public static class Output {
		public FrameImages frameImages = new FrameImages();
		public Window window = new Window();
		public boolean colour = true;
		public boolean segmentation = false;
		public boolean depth = false;
		public boolean invertDepth = true;
		public float renderDistance = 300.0f;
		public float maxDepthOutput = 20.0f;
		public Collisions collisions = new Collisions();

		@NoArgsConstructor
		@Setter
		public static class Window {
			public boolean visible = true;
			public boolean fullscreen = false;
			public int width = 800;
			public int height = 600;
		}

		@NoArgsConstructor
		@Setter
		public static class FrameImages {
			public boolean enabled = false;
			public String fileExtension = "jpg";
			public String filePrefix = "";
		}

		@NoArgsConstructor
		@Setter
		public static class Collisions {
			public boolean enabled = false;
			public float userRadius = 0.5f;
		}
	}

	@NoArgsConstructor
	@Setter
	public static class Camera {
		@JsonDeserialize(using = ParameterLoader.Vec3Deserializer.class)
		@JsonSerialize(using = ParameterLoader.Vec3Serializer.class)
		public Vector3f startPosition = new Vector3f(0, 3.3f, 0);
		@JsonDeserialize(using = ParameterLoader.Vec3Deserializer.class)
		@JsonSerialize(using = ParameterLoader.Vec3Serializer.class)
		public Vector3f startDirection = new Vector3f(0, 0, 1);
		public boolean verticalMovement = true;
	}

	@AllArgsConstructor
	@NoArgsConstructor
	@Setter
	public static class Texture {
		public String diffuse;
		public String normal = null;
		public String glossiness = null;

		public Texture(String diffuse, String normal) {
			this.diffuse = diffuse;
			this.normal = normal;
		}

		public Texture(String diffuse) {
			this.diffuse = diffuse;
		}
	}

	@NoArgsConstructor
	@AllArgsConstructor
	@Setter
	public static class ColourFilter {
		@JsonDeserialize(using = ParameterLoader.Vec3Deserializer.class)
		@JsonSerialize(using = ParameterLoader.Vec3Serializer.class)
		public Vector3f colour = new Vector3f(1, 0, 1);
		public float mixFactor = 0f;
		public boolean expMix = false;

		public ColourFilter(ColourFilter old) {
			this.colour = new Vector3f(old.colour);
			this.mixFactor = old.mixFactor;
			this.expMix = old.expMix;
		}
	}

	@NoArgsConstructor
	@Setter
	public static class Terrain {
		public float width = 100f;
		public float verticalScale = 3.0f;
		public int textureScale = 2;
		public Noise noise = new Noise();
		public float vertexDensity = 0.7f;
		public Texture texture = new Texture("/textures/floor2.png");

		@NoArgsConstructor
		@Setter
		public static class Noise {
			public float xScale = 1.0f;
			public float yScale = 1.0f;
			public int octaves = 8;
			public float persistence = 0.5f;
			public float lacunarity = 2.0f;
		}
	}

	@NoArgsConstructor
	@Setter
	public static class EcosystemSimulation {
		public int numIterations = 400;
		public int yearLength = 20;
		public float ageThreshold = 0.7f;
		public float radiusWeight = 0.3f;
		public float smallRadiusViability = 0.4f;
		public float averageRadiusViability = 0.4f;
	}

	@NoArgsConstructor
	@Setter
	public static class Quadtree {
		public int levels = 3;
		public float thresholdCoefficient = 1.5f;
		public boolean frustumCulling = true;
	}

	@NoArgsConstructor
	@Setter
	public static class SceneObjects {
		public boolean display = true;
		@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
		public List<Tree> trees = List.of(
				new TreeTypes.BranchingTree(),
				new TreeTypes.MonopodialTree(),
				new TreeTypes.AspenTree(),
				new TreeTypes.PoplarTree()
		);
		public Twigs twigs = new Twigs();
		public List<ExternalModel> externalModels = List.of(
				new ExternalModel("/models/Rock1/Rock1.obj", "/models/Rock1", true, 0.3f)
		);
		public List<CrossedBillboard> crossedBillboards = List.of(
				new CrossedBillboard(new Texture("/textures/grass2.png"), 4, 1.0f, 0.35f, 20.0f),
				new CrossedBillboard(new Texture("/textures/fern1_rotated.png"), 4, 1.2f, 1.2f, 0.2f),
				new CrossedBillboard(new Texture("/textures/fern2_rotated.png"), 4, 1.2f, 1.2f, 0.2f)
		);
		public SceneObject fallenLeaves = new SceneObject(1f, 0.75f, 1.25f, 0.7f, 0.0f, 0.1f);

		@NoArgsConstructor
		@AllArgsConstructor
		@Setter
		public static class SceneObject {
			public float density = 1.0f;
			public float minScaleFactor = 0.75f;
			public float maxScaleFactor = 1.25f;
			public float scale = 1.0f;
			public float yOffset = 0.0f;
			public float pitchVariability = 0.1f;
		}

		@Setter
		public static class Twigs extends SceneObject {
			public int typesPerQuad = 2;
			public int numSides = 5;
			public Texture texture = new Texture(
					"/textures/Bark_Pine_baseColor.jpg",
					"/textures/Bark_Pine_normal.jpg",
					"/textures/Bark_Pine_glossiness.jpg"
			);

			public Twigs() {
				super();
				this.yOffset = 0.1f;
				this.scale = 0.05f;
			}
		}

		@AllArgsConstructor
		@Setter
		public static class CrossedBillboard extends SceneObject {
			public Texture texture = new Texture("/textures/grass2.png");
			public int numBoards = 4;
			public float xScale = 1.0f;
			public float yScale = 1.0f;

			public CrossedBillboard() {
				super();
				this.scale = 0.7f;
			}

			public CrossedBillboard(Texture texture, int numBoards, float xScale, float yScale, float density) {
				this(texture, numBoards, xScale, yScale);
				this.density = density;
				this.scale = 0.7f;
			}
		}

		@AllArgsConstructor
		@Setter
		public static class ExternalModel extends SceneObject {
			public String modelPath = "/models/Rock1/Rock1.obj";
			public String texturesDir = "/models/Rock1";
			public boolean collidable = true;

			public ExternalModel() {
				super();
				this.yOffset = -0.1f;
			}

			public ExternalModel(String modelPath, String texturesDir, boolean collidable, float scale) {
				this(modelPath, texturesDir, collidable);
				this.scale = scale;
				this.yOffset = -0.1f;
			}
		}

		@Setter
		public static class Tree extends SceneObject {
			public String name = "Tree";
			public Map<String, Number> lSystemParamsLower;
			public Map<String, Number> lSystemParamsUpper;
			public Texture barkTexture = new Texture(
					"/textures/Bark_Pine_baseColor.jpg",
					"/textures/Bark_Pine_normal.jpg",
					"/textures/Bark_Pine_glossiness.jpg");
			public LeafTextures leafTextures = new LeafTextures();
			public ColourFilter leafColourFilter = null;
			public float numPerIterationSize = 3;
			public int numSides = 5;
			public float leafXScale = 1.0f;
			public float leafYScale = 1.0f;
			public int minIterations = 7;
			public int maxIterations = 9;
			public boolean widenBase = true;
			public int maxAge = 200;
			public float seedRadiusMultiplier = 2.0f;
			public int lowLODEdges = 2;
			public int lowLODLeafMerges = 1;

			public Tree() {
				super();
				this.pitchVariability = 0;
				this.minScaleFactor = 0.7f;
				this.maxScaleFactor = 1.1f;
				this.density = 0.5f;
			}
		}

		@NoArgsConstructor
		@AllArgsConstructor
		@Setter
		public static class LeafTextures {
			public String frontAlbedo = "/textures/Leaf2/Leaf2_front.tga";
			public String frontNormal = "/textures/Leaf2/Leaf2_normals_front.tga";
			public String frontTranslucency = "/textures/Leaf2/Leaf2_front_t.tga";
			public String frontHalfLife = "/textures/Leaf2/Leaf2_halflife_front_t.tga";
			public String backAlbedo = "/textures/Leaf2/Leaf2_back.tga";
			public String backNormal = "/textures/Leaf2/Leaf2_normals_back.tga";
			public String backTranslucency = "/textures/Leaf2/Leaf2_back_t.tga";
			public String backHalfLife = "/textures/Leaf2/Leaf2_halflife_back_t.tga";
		}

	}

	@NoArgsConstructor
	@Setter
	public static class Lighting {
		public float ambientStrength = 0.4f;
		public int specularPower = 16;
		public Sun sun = new Sun();
		public Sky sky = new Sky();
		public SSAO ssao = new SSAO();
		public HDR hdr = new HDR();
		public GammaCorrection gammaCorrection = new GammaCorrection();
		public Translucency translucency = new Translucency();
		public Shadows shadows = new Shadows();
		public VolumetricScattering volumetricScattering = new VolumetricScattering();

		@NoArgsConstructor
		@Setter
		public static class Sun {
			public boolean display = false;
			public boolean autoPosition = true;
			public int numSides = 10;
			@JsonDeserialize(using = ParameterLoader.Vec3Deserializer.class)
			@JsonSerialize(using = ParameterLoader.Vec3Serializer.class)
			public Vector3f strength = new Vector3f(3.8f, 3.3f, 3.2f);
			public float scale = 20f;
			@JsonDeserialize(using = ParameterLoader.Vec3Deserializer.class)
			@JsonSerialize(using = ParameterLoader.Vec3Serializer.class)
			public Vector3f position = new Vector3f(50.0f, 200.0f, -50.0f);
		}

		@NoArgsConstructor
		@Setter
		public static class Sky {
			public String hdrFile = "textures/gamrig_8k.hdr";
			public int resolution = 2048;
		}

		@NoArgsConstructor
		@Setter
		public static class SSAO {
			public boolean enabled = true;
			public int kernelSize = 32;
			public float radius = 0.5f;
			public float bias = 0.025f;
		}

		@NoArgsConstructor
		@Setter
		public static class Translucency {
			public boolean enabled = true;
			public float factor = 0.1f;
		}

		@NoArgsConstructor
		@Setter
		public static class Shadows {
			public boolean enabled = true;
			public int resolution = 4096;
			public float bias = 0.005f;
		}

		@NoArgsConstructor
		@Setter
		public static class VolumetricScattering {
			public boolean enabled = true;
			public int numSamples = 100;
			public float sampleDensity = 1.3f;
			public float decay = 0.99f;
			public float exposure = 0.0015f;
			public float maxBrightness = 100f;
		}

		@NoArgsConstructor
		@Setter
		public static class HDR {
			public boolean enabled = true;
			public float exposure = 0.6f;
		}

		@NoArgsConstructor
		@Setter
		public static class GammaCorrection {
			public boolean enabled = true;
			public float gamma = 2.2f;
		}
	}
}
