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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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.MemoryLine;
import com.ra4king.circuitsim.gui.Properties.Property;
import com.ra4king.circuitsim.gui.Properties.PropertyMemoryValidator;
import com.ra4king.circuitsim.simulator.CircuitState;
import com.ra4king.circuitsim.simulator.components.memory.ROM;

import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.util.Pair;

/**
 * @author Roi Atalla
 */
public class ROMPeer extends ComponentPeer<ROM> {
	public static void installComponent(ComponentManagerInterface manager) {
		manager.addComponent(new Pair<>("Memory", "ROM"),
		                     new Image(ROMPeer.class.getResourceAsStream("/resources/ROM.png")),
		                     new Properties());
	}
	
	private final Property<List<MemoryLine>> contentsProperty;
	
	public ROMPeer(Properties props, int x, int y) {
		super(x, y, 9, 5);
		
		Properties properties = new Properties();
		properties.ensureProperty(Properties.LABEL);
		properties.ensureProperty(Properties.LABEL_LOCATION);
		properties.ensureProperty(Properties.BITSIZE);
		properties.ensureProperty(Properties.ADDRESS_BITS);
		properties.mergeIfExists(props);
		
		int addressBits = properties.getValue(Properties.ADDRESS_BITS);
		int dataBits = properties.getValue(Properties.BITSIZE);
		
		contentsProperty = new Property<>("Contents", new PropertyMemoryValidator(addressBits, dataBits), null);
		String oldMemory;
		Property<?> oldContents = props.getProperty("Contents");
		if(oldContents == null) {
			oldMemory = "";
		} else if(oldContents.validator == null) {
			oldMemory = props.getValue("Contents");
		} else {
			oldMemory = oldContents.getStringValue();
		}
		properties.setValue(contentsProperty, contentsProperty.validator.parse(oldMemory));
		
		int[] memory = memoryToArray(properties.getValue(contentsProperty));
		ROM ram = new ROM(properties.getValue(Properties.LABEL), dataBits, addressBits, memory);
		
		List<PortConnection> connections = new ArrayList<>();
		connections.add(new PortConnection(this, ram.getPort(ROM.PORT_ADDRESS), "Address", 0, 2));
		connections.add(new PortConnection(this, ram.getPort(ROM.PORT_ENABLE), "Enable", 4, getHeight()));
		connections.add(new PortConnection(this, ram.getPort(ROM.PORT_DATA), "Data", getWidth(), 2));
		
		init(ram, properties, connections);
	}
	
	private static int[] memoryToArray(List<MemoryLine> lines) {
		if(lines == null) {
			return new int[0];
		}
		
		return lines.stream()
		            .flatMap(line -> line.values.stream())
		            .mapToInt(prop -> Integer.parseUnsignedInt(prop.get(), 16))
		            .toArray();
	}
	
	@Override
	public List<MenuItem> getContextMenuItems(CircuitManager circuit) {
		MenuItem menuItem = new MenuItem("Edit contents");
		menuItem.setOnAction(event -> {
			Property<List<MemoryLine>> property = getProperties().getProperty(contentsProperty.name);
			PropertyMemoryValidator memoryValidator = (PropertyMemoryValidator)property.validator;
			
			List<MemoryLine> lines = new ArrayList<>();
			
			circuit.getSimulatorWindow().getSimulator().runSync(() -> {
				int[] memory = getComponent().getMemory();
				lines.addAll(
					memoryValidator.parse(memory, (address, value) -> {
						memory[address] = value;
						
						int index = address / 16;
						MemoryLine line = property.value.get(index);
						line.values.get(address - index * 16).setValue(memoryValidator.parseValue(value));
						
						circuit.getCircuit().forEachState(state -> getComponent().valueChanged(state, null, 0));
					}));
			});
			
			memoryValidator.createAndShowMemoryWindow(circuit.getSimulatorWindow().getStage(), lines);
		});
		return Collections.singletonList(menuItem);
	}
	
	@Override
	public void paint(GraphicsContext graphics, CircuitState circuitState) {
		GuiUtils.drawName(graphics, this, getProperties().getValue(Properties.LABEL_LOCATION));
		
		graphics.setFill(Color.WHITE);
		GuiUtils.drawShape(graphics::fillRect, this);
		
		graphics.setStroke(Color.BLACK);
		GuiUtils.drawShape(graphics::strokeRect, this);

		String address = circuitState.getLastReceived(getComponent().getPort(ROM.PORT_ADDRESS)).toHexString();
		String value = circuitState.getLastPushed(getComponent().getPort(ROM.PORT_DATA)).toHexString();
		
		int x = getScreenX();
		int y = getScreenY();
		int width = getScreenWidth();
		int height = getScreenHeight();
		
		graphics.setFont(GuiUtils.getFont(11, true));

		String text = "ROM";
		Bounds bounds = GuiUtils.getBounds(graphics.getFont(), text);
		graphics.setFill(Color.BLACK);
		graphics.fillText(text,
		                  x + (width - bounds.getWidth()) * 0.5,
		                  y + (height + bounds.getHeight()) * 0.2);
		
		// Draw address
		text = "A: " + address;
		double addrY = y + bounds.getHeight() + 12;
		graphics.fillText(text, x + 13, addrY);
		
		// Draw data afterward
		bounds = GuiUtils.getBounds(graphics.getFont(), text);
		graphics.fillText("D: " + value, x + 13, addrY + bounds.getHeight());
		
		graphics.setFill(Color.GRAY);
		graphics.setFont(GuiUtils.getFont(10));
		graphics.fillText("A", x + 3, y + height * 0.5 - 1);
		graphics.fillText("D", x + width - 9, y + height * 0.5 - 1);
		graphics.fillText("en", x + width * 0.5 - 11.5, y + height - 3.5);
	}
}