/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.gui.workflow.parts;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.AbsoluteBendpoint;
import org.eclipse.draw2d.ConnectionLocator;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.Polyline;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.PolylineDecoration;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.CompoundSnapToHelper;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.SnapToGeometry;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.SnapToHelper;
import org.eclipse.gef.editparts.AbstractConnectionEditPart;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.ui.views.properties.IPropertySource;

import de.rcenvironment.core.component.workflow.execution.impl.WorkflowExecutionInformationImpl;
import de.rcenvironment.core.component.workflow.model.api.Connection;
import de.rcenvironment.core.component.workflow.model.api.Location;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription;
import de.rcenvironment.core.component.workflow.model.api.WorkflowNode;
import de.rcenvironment.core.gui.workflow.editor.commands.ConnectionBendpointEditPolicy;
import de.rcenvironment.core.gui.workflow.editor.commands.ConnectionDeletionPolicy;
import de.rcenvironment.core.gui.workflow.editor.properties.ComponentPropertySource;

/**
 * EditPart representing all connections between two workflow nodes.
 *
 * @author Heinrich Wendel
 * @author Oliver Seebach
 */
public class ConnectionPart extends AbstractConnectionEditPart implements PropertyChangeListener {

    private Label relationshipLabel;
    
    @Override
    public IFigure createFigure() {

        Polyline figure = (Polyline) super.createFigure();
        PolylineConnection connection = new PolylineConnection();
        connection.setPoints(figure.getPoints());
        ConnectionWrapper wrapper = (ConnectionWrapper) getModel();

        if (wrapper.getSourceArrow()) {
            connection.setSourceDecoration(new PolylineDecoration());
        }

        if (wrapper.getTargetArrow()) {
            connection.setTargetDecoration(new PolylineDecoration());
        }
        
        // skip self connections
        if (!wrapper.getSource().getIdentifierAsObject().equals(wrapper.getTarget().getIdentifierAsObject())) {
            ConnectionLocator connectionLocator = new ConnectionLocator(connection, ConnectionLocator.MIDDLE);
            
            connectionLocator.setRelativePosition(PositionConstants.NSEW);
            connectionLocator.setGap(5);
            relationshipLabel = new Label(String.valueOf(((ConnectionWrapper) getModel()).getNumberOfConnections()));
            // hide label per default
            relationshipLabel.setVisible(false);
            connection.add(relationshipLabel, connectionLocator);
        }

        return connection;
    }

    @Override
    public void activate() {
        super.activate();
        Object model = (((ScalableFreeformRootEditPart) getParent()).getContents()).getModel();
        WorkflowDescription workflowDescription = getWorkflowDescriptionFromModelObject(model);
        for (Connection connection : workflowDescription.getConnections()) {
            connection.addPropertyChangeListener(this);
        }
    }

    @Override
    public void deactivate() {
        super.deactivate();
        Object model = (((ScalableFreeformRootEditPart) getParent()).getContents()).getModel();
        WorkflowDescription workflowDescription = getWorkflowDescriptionFromModelObject(model);
        for (Connection connection : workflowDescription.getConnections()) {
            connection.removePropertyChangeListener(this);
        }
    }

    @Override
    protected void refreshVisuals() {
        EditPart parent = getParent();
        if (parent != null) {
            EditPartViewer viewer = parent.getViewer();
            EditPart contents = viewer.getContents();
            Object modelObject = contents.getModel();
            WorkflowDescription workflowDescription = getWorkflowDescriptionFromModelObject(modelObject);
            ConnectionWrapper connectionWrapper = (ConnectionWrapper) getModel();
            
            for (Connection connection : workflowDescription.getConnections()) {
                if ((connectionWrapper.getSource().getIdentifierAsObject().equals(connection.getSourceNode().getIdentifierAsObject())
                    && connectionWrapper.getTarget().getIdentifierAsObject().equals(connection.getTargetNode().getIdentifierAsObject()))
                    ) {
                    addBendpointsFromModelToGraphics(connection);
                    return;
                } 
            }
        }
    }
    
    @Override
    public Object getAdapter(@SuppressWarnings("rawtypes") Class type) {
        // Enable Snap to grid/geometry in wf editor for bendpoints..
        if (type == SnapToHelper.class) {
            List<SnapToHelper> helpers = new ArrayList<SnapToHelper>();
            if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED))) {
                helpers.add(new SnapToGeometry(this));
            }
            if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED))) {
                helpers.add(new SnapToGrid(this));
            }
            if (helpers.size() == 0) {
                return null;
            } else {
                return new CompoundSnapToHelper(helpers.toArray(new SnapToHelper[0]));
            }
        } else if (type == IPropertySource.class && getModel() instanceof WorkflowNode) {
            return new ComponentPropertySource(getViewer().getEditDomain().getCommandStack(), (WorkflowNode) getModel());
        }
        return super.getAdapter(type);
    }

    private void addBendpointsFromModelToGraphics(Connection matchingConnection) {
        org.eclipse.draw2d.Connection connection = getConnectionFigure();
        List<AbsoluteBendpoint> figureConstraint = new ArrayList<AbsoluteBendpoint>();
        for (Location location : matchingConnection.getBendpoints()) {
            figureConstraint.add(new AbsoluteBendpoint(new Point(location.x, location.y)));
        }
        connection.setRoutingConstraint(figureConstraint);
    }

    @Override
    protected void createEditPolicies() {
        installEditPolicy(EditPolicy.CONNECTION_ROLE, new ConnectionDeletionPolicy());
        installEditPolicy(EditPolicy.CONNECTION_BENDPOINTS_ROLE, new ConnectionBendpointEditPolicy());
    }

    /**
     * Hides connection label.
     */
    public void hideLabel() {
        if (relationshipLabel != null) {
            relationshipLabel.setVisible(false);
        }
    }

    /**
     * Show connection label.
     */
    public void showLabel() {
        if (relationshipLabel != null) {
            relationshipLabel.setVisible(true);
        }
    }


    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(Connection.PROPERTY_BENDPOINT)) {
            refreshVisuals();
        }
    }
    
    private WorkflowDescription getWorkflowDescriptionFromModelObject(Object modelObject) {
        WorkflowDescription workflowDescription = null;
        if (modelObject instanceof WorkflowDescription) {
            workflowDescription = (WorkflowDescription) modelObject;
        } else if (modelObject instanceof WorkflowExecutionInformationImpl) {
            workflowDescription = ((WorkflowExecutionInformationImpl) modelObject).getWorkflowDescription();
        }
        return workflowDescription;
    }

}
