package it.cavallium.warppi.gui.expression.blocks;

import it.cavallium.warppi.device.display.DisplayOutputDevice;
import it.cavallium.warppi.gui.GraphicalElement;
import it.cavallium.warppi.gui.expression.Caret;
import it.cavallium.warppi.gui.expression.ExtraMenu;
import it.cavallium.warppi.gui.expression.InputContext;
import it.cavallium.warppi.gui.graphicengine.Renderer;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.parser.features.interfaces.Feature;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

public abstract class Block implements TreeBlock, BlockOrContainer, GraphicalElement {

	protected boolean small;
	protected int width;
	protected int height;
	protected int line;
	protected BlockContainer parent;
	
	public Block() {
		
	}
	
	/**
	 * Copy
	 * @param b
	 */
	public Block(BlockContainer parent, Block b) {
		this.small = b.small;
		this.width = b.width;
		this.height = b.height;
		this.line = b.line;
		this.parent = parent;
	}

	/**
	 *
	 * @param r
	 *            Graphic Renderer class.
	 * @param x
	 *            Position relative to the window.
	 * @param y
	 *            Position relative to the window.
	 */
	public abstract void draw(DisplayOutputDevice ge, Renderer r, int x, int y, Caret caret);

	public abstract boolean appendBlock(Caret caret, Block newBlock);
	
	/**
	 * Used only to get inner blocks when deleting the parent block.
	 * @return every block of every inner container, or null if empty
	 */
	public abstract ObjectArrayList<Block> getInnerBlocks();

	public abstract ObjectArrayList<BlockContainer> getInnerContainers();
	
	@Override
	public abstract void recomputeDimensions();

	@Override
	public int getWidth() {
		return width;
	}

	@Override
	public int getHeight() {
		return height;
	}

	@Override
	public int getLine() {
		return line;
	}

	public boolean isSmall() {
		return small;
	}

	public abstract void setSmall(boolean small);

	public ExtraMenu<?> getExtraMenu() {
		return null;
	}

	public abstract Feature toFeature(MathContext context) throws Error;

	@Override
	public BlockContainer getParentContainer() {
		return parent;
	}

	@Override
	public BlockContainer getParent() {
		return parent;
	}

	@Override
	public BlockOrContainer getAt(int index) {
		var ic = getInnerContainers();
		return ic.get(index);
	}

	@Override
	public int getIndex() {
		return parent != null ? parent.getContentUnsafe().indexOf(this) : -1;
	}

	@Override
	public boolean hasParent() {
		return parent != null;
	}

	public void setParent(final BlockContainer parent) {
		this.parent = parent;
	}
	
	public abstract Block clone(BlockContainer parent, InputContext ic);

	public BlockReference<?> getReference() {
		if (this.hasParent()) {
			return this.parent.getReference(this);
		} else {
			return null;
		}
	}

	public BlockPosition getLastInnerPosition() {
		var innerContainers = getInnerContainers();
		if (innerContainers != null && !innerContainers.isEmpty()) {
			var lastContainer = innerContainers.get(innerContainers.size() - 1);
			var content = lastContainer.getContent();
			if (content.isEmpty()) {
				return new BlockPosition(lastContainer);
			} else {
				return new BlockPosition(content.get(content.size() - 1));
			}
		} else {
			return null;
		}
	}

	public BlockPosition getFirstInnerPosition() {
		var innerContainers = getInnerContainers();
		if (innerContainers != null && !innerContainers.isEmpty()) {
			return new BlockPosition(innerContainers.get(0));
		} else {
			return null;
		}
	}
}
