package com.danitheskunk.skunkworks;

import com.danitheskunk.skunkworks.audio.AudioEngine;
import com.danitheskunk.skunkworks.audio.ISample;
import com.danitheskunk.skunkworks.audio.nodes.Mixer;
import com.danitheskunk.skunkworks.audio.nodes.Node;
import com.danitheskunk.skunkworks.audio.nodes.SamplePlayer;
import com.danitheskunk.skunkworks.gfx.IRenderContext;
import com.danitheskunk.skunkworks.gfx.threedee.IRenderContext3D;
import com.danitheskunk.skunkworks.gfx.ITexture;
import com.danitheskunk.skunkworks.gfx.font.IFont;
import com.danitheskunk.skunkworks.nodes.NodeRoot;
import org.lwjgl.glfw.GLFW;

/**
 * Main class to extend to write a game in Skunkworks
 * <p>
 * Extend this class and override {@link BaseGame#update update},
 * {@link BaseGame#render render}, {@link BaseGame#renderPre renderPre},
 * and/or {@link BaseGame#render3D render3D} to add functionality. Then instance
 * and call {@link BaseGame#run run}.
 * <p>
 * Automatic timing (by default 60fps), resizing(with various scalers), 2d
 * and 3d rendering, and audio playback.
 */
@SuppressWarnings("SameParameterValue")
public abstract class BaseGame {
	protected final AudioEngine audioEngine;
	protected final IFont debugFont;
	protected final Engine engine;
	protected final Mixer mixer;
	protected final NodeRoot rootNode;
	protected final SamplePlayer samplePlayer;
	protected final IWindow window;

	/**
	 * Create with given window size and title
	 *
	 * @param windowSize the size of the game's window
	 * @param windowTitle the title of the game's window
	 */
	public BaseGame(Vec2i windowSize, String windowTitle) {
		audioEngine = new AudioEngine(48000, 256, 8);
		mixer = new Mixer(audioEngine, 2);
		samplePlayer = new SamplePlayer(audioEngine);
		Node.connect(samplePlayer, 0, mixer, 0);
		audioEngine.setNode(mixer);

		engine = new Engine();
		window = engine.openWindow(windowSize, windowTitle);
		//todo: load from .jar
		debugFont = window.loadFontTileset("fonts/ega-8x14.png");
		rootNode = new NodeRoot();
	}

	/**
	 * Load sample from path
	 *
	 * @param path file path to the sample
	 * @return the loaded sample
	 */
	protected ISample loadSample(String path) {
		return audioEngine.loadSample(path);
	}

	/**
	 * Load image from path and prepare as gpu texture
	 *
	 * @param path file path to the image file
	 * @return the loaded texture
	 */
	protected ITexture loadTexture(String path) {
		return window.loadTexture(path);
	}

	/**
	 * Play sample once
	 *
	 * @param sample the sample to be played
	 */
	protected void playSample(ISample sample) {
		samplePlayer.play(sample);
	}

	/**
	 * Play sample once or looped
	 *
	 * @param sample the sample to be played
	 * @param looping loops forever if true, otherwise play once
	 */
	protected void playSample(ISample sample, boolean looping) {
		samplePlayer.play(sample, looping);
	}

	/**
	 * Render Callback
	 * <p>
	 * This method will be called after {@link BaseGame#render3D render3D},
	 * when the engine is ready for 2d rendering. Override this method to
	 * render 2d things on screen.
	 *
	 * @param rc the render context with all the render functions
	 */
	protected void render(IRenderContext rc) {
		//to be overriden by child class
	}

	/**
	 * 3D Render Callback
	 * <p>
	 * This method will be called when the engine is ready for 3d rendering.
	 * Override this method to render 3d things on screen.
	 *
	 * @param rc3d the render context with all the render functions
	 */
	protected void render3D(IRenderContext3D rc3d) {
		//to be overriden by child class
	}

	/**
	 * Early Render Callback
	 * <p>
	 * This method will be called before {@link BaseGame#render3D render3D},
	 * when the engine is ready for 2d rendering. Override this method to
	 * render 2d things on screen underneath 3d content.
	 *
	 * @param rc the render context with all the render functions
	 */
	protected void renderPre(IRenderContext rc) {
		//to be overriden by child class
	}

	/**
	 * Runs the game
	 * <p>
	 * This will transfer the control to Skunkworks until the game window
	 * closes.
	 */
	public void run() {
		double lastTime, currentTime, delta;

		lastTime = GLFW.glfwGetTime();
		while(!window.shouldClose()) {
			currentTime = GLFW.glfwGetTime();
			delta = currentTime - lastTime;
			lastTime = currentTime;
			engine.tick();
			window.tick();
			//todo: frame rate control
			rootNode.tick();
			update(1.0 / 60.0);
			var rc3d = window.renderStart3D();
			render3D(rc3d);
			window.renderFinish3D(rc3d);
			var rc = window.renderStart();
			renderPre(rc);
			rootNode.render(rc);
			render(rc);
			window.renderFinish(rc);

			window.runScaler();
			audioEngine.refill();
		}
	}

	/**
	 * Update Callback
	 * <p>
	 * This method will be called periodically, either in a fixed(by default)
	 * or variable period. Can be changed by setting {@link Timestep}
	 *
	 * @param delta time that passed since last update in seconds
	 */
	protected void update(double delta) {

	}
}
