package pipe.views;

import pipe.controllers.AbstractConnectableController;
import pipe.controllers.PetriNetController;
import pipe.handlers.LabelHandler;
import uk.ac.imperial.pipe.models.petrinet.Connectable;

import javax.swing.*;
import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Shape;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;

/**
 * Abstract connectable view for connectable models
 * @param <T> Connectable model
 */
public abstract class ConnectableView<T extends Connectable> extends AbstractPetriNetViewComponent<T>
        implements Cloneable, Serializable {
    /**
     * Name label for the connectable item
     */
    protected TextLabel textLabel;

    private final AbstractConnectableController<T> componentController;

    /**
     * Shape of the place on the Petri net
     */
    protected Shape shape;

    /**
     * Constructor
     * @param id
     * @param model
     * @param controller
     * @param parent
     * @param shape
     */
    ConnectableView(String id, T model, PetriNetController controller, AbstractConnectableController<T> componentController, Container parent, Shape shape) {
        super(id, model, controller, parent);
        this.componentController = componentController;
        this.shape = shape;
        setLocation(model.getX(), model.getY());

        int x = (int) (model.getX() + model.getNameXOffset());
        int y = (int) (model.getX() + model.getNameXOffset());
        textLabel = new TextLabel(model.getId(), x, y);
        addChangeListener();
        updateBounds();
    }

    /**
     * Adds x, y and id change listeners to the underlying model which redraw the connectable when changed
     */
    private void addChangeListener() {
        model.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
                String name = propertyChangeEvent.getPropertyName();
                if (name.equals(Connectable.X_CHANGE_MESSAGE) || name.equals(Connectable.Y_CHANGE_MESSAGE)) {
                    updateBounds();
                    updateLabelLocation();
                } else if (name.equals(Connectable.NAME_X_OFFSET_CHANGE_MESSAGE) || name.equals(
                        Connectable.NAME_Y_OFFSET_CHANGE_MESSAGE)) {
                    updateLabelLocation();
                } else if (name.equals(Connectable.ID_CHANGE_MESSAGE) || name.equals(Connectable.NAME_CHANGE_MESSAGE)) {
                    //TODO: NAMELABEL SHOULD LISTEN?
                    String newName = (String) propertyChangeEvent.getNewValue();
                    textLabel.setText(newName);
                    textLabel.repaint();
                }
            }

        });
    }

    /**
     * Updates label position according to the Connectable location
     */
    private final void updateLabelLocation() {
        textLabel.setPosition(model.getX() + model.getNameXOffset(), model.getY() + model.getNameYOffset());
    }

    /**
     * Changes the displayed bounds of the object relative to its x,y width and height
     *
     * Implemented because the canvas has no layout manager
     *
     */
    protected final void updateBounds() {
        Rectangle bounds = shape.getBounds();
        Rectangle newBounds = new Rectangle((int)(model.getCentre().getX() + bounds.getX()), (int)(model.getCentre().getY() + bounds.getY()), (int) bounds.getWidth() + getComponentDrawOffset(), (int)bounds.getHeight() + getComponentDrawOffset()) ;
        setBounds(newBounds);
//        setBounds(model.getX(), model.getY(), model.getWidth() + getComponentDrawOffset(), model.getHeight() + getComponentDrawOffset());

        //TODO: THIS IS A DIRTY HACK IN ORDER TO GET DRAGGIGN WHEN ZOOMED WORKING
        Component root = SwingUtilities.getRoot(this);
        if (root != null) {
            root.repaint();
        }
    }

    /**
     * Performs a connectable specific delete by removing the name label from the parent
     */
    @Override
    public final void componentSpecificDelete() {
        if (getParent() != null) {
            getParent().remove(textLabel);
        }
    }

    /**
     * Adds the name label to the container
     * @param container to add label to 
     */
    protected final void addLabelToContainer(Container container) {
        container.add(textLabel);
        textLabel.setPosition(model.getX() + model.getNameXOffset(), model.getY() + model.getNameYOffset());
        LabelHandler<T> labelHandler = new LabelHandler<>(textLabel, this, componentController);
        textLabel.addMouseListener(labelHandler);
        textLabel.addMouseMotionListener(labelHandler);
        textLabel.addMouseWheelListener(labelHandler);
    }

}