package com.ra4king.circuitsim.gui.peers.wiring;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.ra4king.circuitsim.gui.CircuitManager;
import com.ra4king.circuitsim.gui.ComponentManager.ComponentManagerInterface;
import com.ra4king.circuitsim.gui.ComponentPeer;
import com.ra4king.circuitsim.gui.Connection.PortConnection;
import com.ra4king.circuitsim.gui.GuiUtils;
import com.ra4king.circuitsim.gui.Properties;
import com.ra4king.circuitsim.gui.Properties.Direction;
import com.ra4king.circuitsim.gui.Properties.Property;
import com.ra4king.circuitsim.simulator.CircuitState;
import com.ra4king.circuitsim.simulator.Port;
import com.ra4king.circuitsim.simulator.WireValue;
import com.ra4king.circuitsim.simulator.WireValue.State;
import com.ra4king.circuitsim.simulator.components.wiring.Pin;

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.util.Pair;

/**
 * @author Roi Atalla
 */
public class PinPeer extends ComponentPeer<Pin> {
	public static final Property<Boolean> IS_INPUT = new Property<>("Is input?", Properties.YESNO_VALIDATOR, true);
	
	public static void installComponent(ComponentManagerInterface manager) {
		manager.addComponent(new Pair<>("Wiring", "Input Pin"),
		                     new Image(PinPeer.class.getResourceAsStream("/resources/InputPin.png")),
		                     new Properties(new Property<>(IS_INPUT, true)));
		
		manager.addComponent(new Pair<>("Wiring", "Output Pin"),
		                     new Image(PinPeer.class.getResourceAsStream("/resources/OutputPin.png")),
		                     new Properties(new Property<>(IS_INPUT, false),
		                                    new Property<>(Properties.DIRECTION, Direction.WEST)));
	}
	
	public PinPeer(Properties props, int x, int y) {
		super(x, y, 0, 0);
		
		Object value = props.getValueOrDefault(IS_INPUT, false);
		boolean isInput;
		if(value instanceof String) {
			isInput = Boolean.parseBoolean((String)value);
		} else {
			isInput = (Boolean)value;
		}
		
		Properties properties = new Properties();
		properties.ensureProperty(Properties.LABEL);
		properties.ensureProperty(
				new Property<>(Properties.LABEL_LOCATION, isInput ? Direction.WEST : Direction.EAST));
		properties.ensureProperty(Properties.DIRECTION);
		properties.ensureProperty(Properties.BITSIZE);
		properties.ensureProperty(IS_INPUT);
		properties.mergeIfExists(props);
		
		Pin pin = new Pin(properties.getValue(Properties.LABEL),
		                  properties.getValue(Properties.BITSIZE),
		                  properties.getValue(IS_INPUT));
		setWidth(Math.max(2, Math.min(8, pin.getBitSize())));
		setHeight((int)Math.round((1 + (pin.getBitSize() - 1) / 8) * 1.5));
		
		List<PortConnection> connections = new ArrayList<>();
		switch(properties.getValue(Properties.DIRECTION)) {
			case EAST:
				connections.add(new PortConnection(this, pin.getPort(0), getWidth(), getHeight() / 2));
				break;
			case WEST:
				connections.add(new PortConnection(this, pin.getPort(0), 0, getHeight() / 2));
				break;
			case NORTH:
				connections.add(new PortConnection(this, pin.getPort(0), getWidth() / 2, 0));
				break;
			case SOUTH:
				connections.add(new PortConnection(this, pin.getPort(0), getWidth() / 2, getHeight()));
				break;
		}
		
		init(pin, properties, connections);
	}
	
	public boolean isInput() {
		return getComponent().isInput();
	}
	
	@Override
	public void mousePressed(CircuitManager manager, CircuitState state, double x, double y) {
		if(!isInput()) {
			return;
		}
		
		if(state != manager.getCircuit().getTopLevelState()) {
			Alert alert = new Alert(AlertType.CONFIRMATION);
			alert.initOwner(manager.getSimulatorWindow().getStage());
			alert.initModality(Modality.WINDOW_MODAL);
			alert.setTitle("Switch to top-level state?");
			alert.setHeaderText("Switch to top-level state?");
			alert.setContentText("Cannot modify state of a subcircuit. Switch to top-level state?");
			Optional<ButtonType> buttonType = alert.showAndWait();
			if(buttonType.isPresent() && buttonType.get() == ButtonType.OK) {
				state = manager.getCircuit().getTopLevelState();
				manager.getCircuitBoard().setCurrentState(state);
			} else {
				return;
			}
		}
		
		Pin pin = getComponent();
		
		WireValue value = state.getLastPushed(pin.getPort(Pin.PORT));
		if(pin.getBitSize() == 1) {
			pin.setValue(state,
			             new WireValue(1, value.getBit(0) == State.ONE ? State.ZERO : State.ONE));
		} else {
			double bitWidth = getScreenWidth() / Math.min(8.0, pin.getBitSize());
			double bitHeight = getScreenHeight() / ((pin.getBitSize() - 1) / 8 + 1.0);
			
			int bitCol = (int)(x / bitWidth);
			int bitRow = (int)(y / bitHeight);
			
			int bit = pin.getBitSize() - 1 - (bitCol + bitRow * 8);
			if(bit >= 0 && bit < pin.getBitSize()) {
				WireValue newValue = new WireValue(value);
				newValue.setBit(bit, value.getBit(bit) == State.ONE ? State.ZERO : State.ONE);
				pin.setValue(state, newValue);
			}
		}
	}
	
	@Override
	public boolean keyPressed(CircuitManager manager, CircuitState state, KeyCode keyCode, String text) {
		if(!isInput()) {
			return false;
		}
		
		switch(keyCode) {
			case NUMPAD0:
			case NUMPAD1:
			case DIGIT0:
			case DIGIT1:
				int value = text.charAt(0) - '0';
				
				WireValue currentValue = new WireValue(state.getLastPushed(getComponent().getPort(Pin.PORT)));
				
				for(int i = currentValue.getBitSize() - 1; i > 0; i--) {
					currentValue.setBit(i, currentValue.getBit(i - 1));
				}
				
				currentValue.setBit(0, value == 1 ? State.ONE : State.ZERO);
				getComponent().setValue(state, currentValue);
				break;
		}
		
		return false;
	}
	
	@Override
	public void paint(GraphicsContext graphics, CircuitState circuitState) {
		GuiUtils.drawName(graphics, this, getProperties().getValue(Properties.LABEL_LOCATION));
		
		graphics.setFont(GuiUtils.getFont(16, true));
		Port port = getComponent().getPort(Pin.PORT);
		WireValue value = isInput() ? circuitState.getLastPushed(port)
		                            : circuitState.getLastReceived(port);
		if(circuitState.isShortCircuited(port.getLink())) {
			graphics.setFill(Color.RED);
		} else {
			if(value.getBitSize() == 1) {
				GuiUtils.setBitColor(graphics, value.getBit(0));
			} else {
				graphics.setFill(Color.WHITE);
			}
		}
		graphics.setStroke(Color.BLACK);
		
		if(isInput()) {
			GuiUtils.drawShape(graphics::fillRect, this);
			GuiUtils.drawShape(graphics::strokeRect, this);
		} else {
			graphics.fillRoundRect(getScreenX(), getScreenY(), getScreenWidth(), getScreenHeight(), 20, 20);
			graphics.strokeRoundRect(getScreenX(), getScreenY(), getScreenWidth(), getScreenHeight(), 20, 20);
		}
		
		graphics.setFill(port.getLink().getBitSize() > 1 ? Color.BLACK : Color.WHITE);
		GuiUtils.drawValue(graphics, value.toString(), getScreenX(), getScreenY(), getScreenWidth());
	}
}