package squidpony.squidgrid.gui.gdx;

import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport;

import squidpony.IColorCenter;

/**
 * A SquidLib-aware partial implementation of {@link ScreenAdapter}. This is a
 * very general implementation (on which I rely in my game), please do not
 * change that too radically.
 * 
 * <p>
 * By implementing {@link #getNext()}, you specify how to switch between screens
 * (for example: splash screen -> (main menu screen <-> game screen)). To build
 * your {@link SquidPanel}, you should use the protected methods that this class
 * provides. In this way, you won't have to worry about the screen size and
 * resizing.
 * </p>
 * 
 * <p>
 * Moving from a screen to another is either triggered by libgdx (when it calls
 * {@link #resize(int, int)} and {@link #dispose()}) or by you (you can call
 * {@link #dispose()} directly). In both cases, it'll make
 * {@link SquidApplicationAdapter}'s
 * {@link com.badlogic.gdx.ApplicationAdapter#render()} method call
 * {@link #getNext()}, hereby triggering screen change.
 * </p>
 * 
 * <p>
 * There really is now way around this class being abstract. Very often, the
 * result of {@link #getNext()} cannot be precomputed. For example after a game
 * screen, either you'll go to the win screen or the lose screen. And the
 * latter cannot be precomputed when building the game screen :-(
 * </p>
 * 
 * @author smelC
 * 
 * @param <T>
 *            The type of color
 */
public abstract class AbstractSquidScreen<T extends Color> extends ScreenAdapter {

	/**
	 * The current size manager. It is always up-to-date w.r.t. to the actual
	 * screen's size, except when a call to {@link #resize(int, int)} has been
	 * done by libgdx, and {@link #getNext()} wasn't called yet.
	 */
	protected ScreenSizeManager sizeManager;

	protected final IColorCenter<T> colorCenter;
	protected final IPanelBuilder ipb;

	/**
	 * It is up to subclassers to initialize this field. Beware that is disposed
	 * if non-null in {@link #dispose()}. Usually it is assigned at construction
	 * time or in {@link #render(float)}.
	 */
	protected Stage stage;

	protected boolean disposed = false;
	protected boolean resized = false;

	private ShapeRenderer renderer = null;

	/**
	 * Here's what the content of {@code ssi} must be:
	 * 
	 * <ol>
	 * <li>A size manager that is correct w.r.t. to the current screen size. It
	 * is usually built by inspecting the current screen size (see
	 * {@link com.badlogic.gdx.Gdx#graphics}) and a cell size you want.
	 * 
	 * <p>
	 * The screen's size is not computed automatically by this constructor,
	 * because usually you can build a single instance of
	 * {@link ScreenSizeManager} at startup, and pass it along all
	 * {@link AbstractSquidScreen}. The instance will only change when there's
	 * some resizing (i.e. when ligdx calls
	 * {@link ScreenAdapter#resize(int, int)}).
	 * </p>
	 * </li>
	 * <li>The color center to use.</li>
	 * <li>How to build fresh {@link SquidPanel}.</li>
	 * </ol>
	 */
	public AbstractSquidScreen(SquidScreenInput<T> ssi) {
		this.sizeManager = ssi.ssm;
		this.colorCenter = ssi.icc;
		this.ipb = ssi.ipb;
	}

	@Override
	public void dispose() {
		if (!disposed) {
			/* This should likely make getNext()'s behavior change */
			disposed = true;

			/*
			 * Either we're moving to a new screen, in which case this avoids
			 * glitches if the new screen does not paint everything; or we're
			 * leaving in which case we don't care.
			 */
			clearScreen();

			if (renderer != null)
				renderer.dispose();

			if (stage != null) {
				stage.dispose();
				/* It should not be reused, hence: */
				stage = null;
			}
		}
	}

	@Override
	public void resize(int width, int height) {
		/* In some implementations, this make getNext()'s behavior change */
		this.resized |= true;
		this.sizeManager = sizeManager.changeScreenSize(width, height);
		if (disposeAtResize())
			dispose();
	}

	/**
	 * Implementations of this method should likely inspect {@link #disposed}
	 * and {@link #resized}. When {@link #resized} holds, this method typically
	 * returns an instance that is a variation of {@code this}, where the font
	 * size has been changed (to get the new size, use {@link #sizeManager}; it
	 * has been updated already). When {@link #disposed} holds, the usual
	 * behavior is for this method to return null to quit the whole application
	 * (that's the assumption that
	 * {@link squidpony.squidgrid.gui.gdx.SquidApplicationAdapter} does) or to
	 * return another screen to move forward (for example when switching from
	 * the main/splash screen to the game's screen).
	 * 
	 * <p>
	 * This method is normally called when the user is moving to the next screen
	 * (so that's your game logic) or when {@link #isDisposed()} holds or when
	 * {@link #hasPendingResize()} holds.
	 * </p>
	 * 
	 * @return The screen to use after this one, or {@code null} if the
	 *         application is quitting.
	 */
	public abstract /* @Nullable */ AbstractSquidScreen<T> getNext();

	/**
	 * @return Whether libgdx called {@link #dispose()} on {@code this}.
	 */
	public boolean isDisposed() {
		return disposed;
	}

	/**
	 * @return Whether libgdx called {@link #resize(int, int)} on {@code this}.
	 */
	public boolean hasPendingResize() {
		return resized;
	}

	/**
	 * Ideally, you should always go through this method to create a
	 * {@link SquidPanel}.
	 * 
	 * @return How this class builds {@link SquidPanel}.
	 */
	public IPanelBuilder getPanelBuilder() {
		return ipb;
	}

	/**
	 * @return The content required to build another {@link AbstractSquidScreen}
	 *         from {@code this}'s content.
	 */
	public SquidScreenInput<T> toSquidScreenInput() {
		return new SquidScreenInput<>(sizeManager, colorCenter, ipb);
	}

	/**
	 * @param desiredCellSize
	 * @return A screen wide squid panel, margins-aware, and with its position
	 *         set.
	 */
	protected final SquidPanel buildScreenWideSquidPanel(int desiredCellSize) {
		return ipb.buildScreenWide(sizeManager.screenWidth, sizeManager.screenHeight, desiredCellSize, null);
	}

	/**
	 * @return A screen wide squid panel, margins-aware, and with its position
	 *         set. It uses the current cell size.
	 */
	protected final SquidPanel buildScreenWideSquidPanel() {
		final SquidPanel result = buildSquidPanel(sizeManager.wCells, sizeManager.hCells);
		result.setPosition(sizeManager.leftMargin, sizeManager.botMargin);
		return result;
	}

	/**
	 * @param width
	 * @param height
	 * @return A panel of size {@code (width, height)} that uses the default
	 *         cell width/cell height. Its position isn't set.
	 */
	protected final SquidPanel buildSquidPanel(int width, int height) {
		return buildSquidPanel(width, height, sizeManager.cellWidth, sizeManager.cellHeight);
	}

	/**
	 * @param width
	 * @param height
	 * @param cellWidth
	 * @param cellHeight
	 * @return A panel of size {@code (width, height)} that has {@code cellSize}
	 *         . Its position isn't set.
	 */
	protected final SquidPanel buildSquidPanel(int width, int height, int cellWidth, int cellHeight) {
		return ipb.buildByCells(width, height, cellWidth, cellHeight, null);
	}

	/* Default implementation, feel free to override */
	protected Stage buildStage() {
		return new Stage(new ScreenViewport());
	}

	/**
	 * @return Whether this screen should be thrown away when a resize event
	 *         occurs.
	 */
	/* You should return false if you handle resizing on your own */
	protected boolean disposeAtResize() {
		return true;
	}

	/**
	 * @return The color to use to repaint the screen entirely in
	 *         {@link #clearScreen()} (used in this class when moving from a
	 *         {@link AbstractSquidScreen} to another).
	 */
	protected T getClearingColor() {
		return colorCenter.getBlack();
	}

	protected void clearScreen() {
		final T c = getClearingColor();
		if (renderer == null)
			renderer = new ShapeRenderer();
		UIUtil.drawRectangle(renderer, 0, 0, sizeManager.screenWidth, sizeManager.screenHeight,
				ShapeType.Filled, c);
	}

	/**
	 * A dumb container, to avoid having too many parameters to
	 * {@link AbstractSquidScreen}'s constructor.
	 * 
	 * @author smelC
	 * 
	 * @param <T>
	 */
	public static class SquidScreenInput<T> {

		public final ScreenSizeManager ssm;
		public final IColorCenter<T> icc;
		public final IPanelBuilder ipb;

		public SquidScreenInput(ScreenSizeManager ssm, IColorCenter<T> icc, IPanelBuilder ipb) {
			this.ssm = ssm;
			this.icc = icc;
			this.ipb = ipb;
		}

	}
}
