/**********************************************************************
 * Copyright (c) 2005, 2014 IBM Corporation, Ericsson
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Bernd Hufmann - Updated for TMF
 **********************************************************************/

package org.eclipse.tracecompass.tmf.ui.views.uml2sd.core;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IColor;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IGC;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.SDViewPref;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.util.TimeEventComparator;

/**
 * The Frame class is the base sequence diagram graph nodes container.<br>
 * For instance, only one frame can be drawn in the View.<br>
 * Lifelines, Messages and Stop which are supposed to represent a Sequence diagram are drawn in a Frame.<br>
 * Only the graph node added to their representing list will be drawn.
 *
 * The lifelines are appended along the X axsis when added in a frame.<br>
 * The syncMessages are ordered along the Y axsis depending on the event occurrence they are attached to.<br>
 *
 * @see org.eclipse.tracecompass.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details
 * @author sveyrier
 * @version 1.0
 */
public class Frame extends BasicFrame {

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------
    /**
     * The lifeline that is current highlighted.
     */
    private Lifeline fHighlightLifeline = null;
    /**
     * The value of the start event.
     */
    private int fStartEvent = 0;
    /**
     * The number of events in the frame.
     */
    private int fNbEvent = 0;
    /**
     * The color for highlighting.
     */
    private IColor fHighlightColor = null;
    /**
     * The list of time events of the corresponding execution occurrences.
     */
    private List<SDTimeEvent> fExecutionOccurrencesWithTime;
    /**
     * The Array of lifeline categories.
     */
    private LifelineCategories[] fLifelineCategories = null;

    // ------------------------------------------------------------------------
    // Methods
    // ------------------------------------------------------------------------

    /**
     * Returns a list of all lifelines known by this frame. Known lifelines are the only one which can be displayed on
     * screen.
     *
     * @return the lifelines list
     */
    protected List<GraphNode> getLifelines() {
        if (!hasChildren()) {
            return null;
        }
        return getNodeMap().get(Lifeline.LIFELINE_TAG);
    }

    /**
     * Returns the number of lifelines stored in the frame
     *
     * @return the number of lifelines
     */
    public int lifeLinesCount() {
        List<GraphNode> lifelines = getLifelines();
        if (lifelines != null) {
            return lifelines.size();
        }
        return 0;
    }

    /**
     * Returns the lifeline at the given index in the lifelines array
     *
     * @param index the position in the lifeline array
     * @return the lifeline or <code>null</code>
     */
    public Lifeline getLifeline(int index) {
        if ((getLifelines() != null) && (index >= 0) && (index < lifeLinesCount())) {
            return (Lifeline) getLifelines().get(index);
        }
        return null;
    }

    /**
     * Returns a list of syncMessages known by this frame. Known syncMessages are the only on which can be displayed on
     * screen
     *
     * @return the syncMessages list
     */
    protected List<GraphNode> getSyncMessages() {
        if (!hasChildren()) {
            return null;
        }
        return getNodeMap().get(SyncMessage.SYNC_MESS_TAG);
    }

    /**
     * Returns the number of syncMessages stored in the frame
     *
     * @return the number of syncMessage
     */
    public int syncMessageCount() {
        if (getSyncMessages() != null) {
            return getSyncMessages().size();
        }
        return 0;
    }

    /**
     * Returns the syncMessage at the given index in the syncMessages array
     *
     * @param index the position in the syncMessages array
     * @return the syncMessage or <code>null</code>
     */
    public SyncMessage getSyncMessage(int index) {
        if ((getSyncMessages() != null) && (index >= 0) && (index < getSyncMessages().size())) {
            return (SyncMessage) getSyncMessages().get(index);
        }
        return null;
    }

    /**
     * Returns a list of asyncMessages known by this frame. Known asyncMessages are the only on which can be displayed
     * on screen
     *
     * @return the asyncMessages list or <code>null</code>
     */
    protected List<GraphNode> getAsyncMessages() {
        if (!hasChildren()) {
            return null;
        }
        return getNodeMap().get(AsyncMessage.ASYNC_MESS_TAG);
    }

    /**
     * Returns the number of asyncMessage stored in the frame
     *
     * @return the number of asyncMessage
     */
    public int asyncMessageCount() {
        if (getAsyncMessages() != null) {
            return getAsyncMessages().size();
        }
        return 0;
    }

    /**
     * Returns the asyncMessage at the given index in the asyncMessage array
     *
     * @param index the position in the asyncMessage array
     * @return the asyncMessage or <code>null</code>
     */
    public AsyncMessage getAsyncMessage(int index) {
        if ((getAsyncMessages() != null) && (index >= 0) && (index < getAsyncMessages().size())) {
            return (AsyncMessage) getAsyncMessages().get(index);
        }
        return null;
    }

    /**
     * Returns a list of syncMessages return known by this frame. Known syncMessages return are the only on which can be
     * displayed on screen
     *
     * @return the syncMessages return list or <code>null</code>
     */
    protected List<GraphNode> getSyncMessagesReturn() {
        if (!hasChildren()) {
            return null;
        }
        return getNodeMap().get(SyncMessageReturn.SYNC_MESS_RET_TAG);
    }

    /**
     * Returns the number of syncMessageReturn stored in the frame
     *
     * @return the number of syncMessageReturn
     */
    public int syncMessageReturnCount() {
        if (getSyncMessagesReturn() != null) {
            return getSyncMessagesReturn().size();
        }
        return 0;
    }

    /**
     * Returns the syncMessageReturn at the given index in the syncMessageReturn array
     *
     * @param index the position in the syncMessageReturn array
     * @return the syncMessageReturn or <code>null</code>
     */
    public SyncMessageReturn getSyncMessageReturn(int index) {
        if ((getSyncMessagesReturn() != null) && (index >= 0) && (index < getSyncMessagesReturn().size())) {
            return (SyncMessageReturn) getSyncMessagesReturn().get(index);
        }
        return null;
    }

    /**
     * Returns a list of asyncMessageRetun known by this frame. Known asyncMessageRetun are the only on which can be
     * displayed on screen
     *
     * @return the asyncMessageRetun list or <code>null</code>
     */
    protected List<GraphNode> getAsyncMessagesReturn() {
        if (!hasChildren()) {
            return null;
        }
        return getNodeMap().get(AsyncMessageReturn.ASYNC_MESS_RET_TAG);
    }

    /**
     * Returns the number of asyncMessageReturn stored in the frame
     *
     * @return the number of asyncMessageReturn
     */
    public int asyncMessageReturnCount() {
        if (getAsyncMessagesReturn() != null) {
            return getAsyncMessagesReturn().size();
        }
        return 0;
    }

    /**
     * Returns the asyncMessageReturn at the given index in the asyncMessageReturn array
     *
     * @param index the position in the asyncMessageReturn array
     * @return the asyncMessageReturn or <code>null</code>
     */
    public AsyncMessageReturn getAsyncMessageReturn(int index) {
        if ((getAsyncMessagesReturn() != null) && (index >= 0) && (index < getAsyncMessagesReturn().size())) {
            return (AsyncMessageReturn) getAsyncMessagesReturn().get(index);
        }
        return null;
    }

    /**
     * Adds a lifeline to the frame lifelines list. The lifeline X drawing order depends on the lifeline addition order
     * into the frame lifelines list.
     *
     * @param lifeline the lifeline to add
     */
    public void addLifeLine(Lifeline lifeline) {
        setComputeMinMax(true);
        if (lifeline == null) {
            return;
        }
        // set the lifeline parent frame
        lifeline.setFrame(this);
        // Increate the frame lifeline counter
        // and set the lifeline drawing order
        lifeline.setIndex(getNewHorizontalIndex());
        if (lifeline.hasTimeInfo()) {
            setHasTimeInfo(true);
        }
        // add the lifeline to the lifelines list
        addNode(lifeline);
    }

    /**
     * Returns the first visible lifeline drawn in the view
     *
     * @return the first visible lifeline index
     */
    public int getFirstVisibleLifeline() {
        if (!hasChildren()) {
            return 0;
        }
        Integer ret = getIndexes().get(Lifeline.LIFELINE_TAG);
        return (ret == null ? 0 : ret.intValue());
    }

    /**
     * Returns the first visible synchronous message drawn in the view
     *
     * @return the first visible synchronous message index
     */
    public int getFirstVisibleSyncMessage() {
        if (!hasChildren()) {
            return 0;
        }
        Integer ret = getIndexes().get(SyncMessage.SYNC_MESS_TAG);
        return (ret == null ? 0 : ret.intValue());
    }

    /**
     * Returns the first visible synchronous message return drawn in the view
     *
     * @return the first visible synchronous message return index
     */
    public int getFirstVisibleSyncMessageReturn() {
        if (!hasChildren()) {
            return 0;
        }
        Integer ret = getIndexes().get(SyncMessageReturn.SYNC_MESS_RET_TAG);
        return (ret == null ? 0 : ret.intValue());
    }

    /**
     * Returns the first visible synchronous message drawn in the view
     *
     * @return the first visible synchronous message index
     */
    public int getFirstVisibleAsyncMessage() {
        if (!hasChildren()) {
            return 0;
        }
        Integer ret = getIndexes().get(AsyncMessage.ASYNC_MESS_TAG);
        return (ret == null ? 0 : ret.intValue());
    }

    /**
     * Returns the first visible synchronous message return drawn in the view
     *
     * @return the first visible synchronous message return index
     */
    public int getFirstVisibleAsyncMessageReturn() {
        if (!hasChildren()) {
            return 0;
        }
        Integer ret = getIndexes().get(AsyncMessageReturn.ASYNC_MESS_RET_TAG);
        return (ret == null ? 0 : ret.intValue());
    }

    /**
     * Returns the list of execution occurrences.
     *
     * @return the list of execution occurrences
     */
    public List<SDTimeEvent> getExecutionOccurrencesWithTime() {
        return fExecutionOccurrencesWithTime;
    }

    /**
     * Inserts a lifeline after a given lifeline.
     *
     * @param toInsert A lifeline to insert
     * @param after A lifelife the toInsert-lifeline will be inserted after.
     */
    public void insertLifelineAfter(Lifeline toInsert, Lifeline after) {
        if ((toInsert == null)) {
            return;
        }
        if (toInsert == after) {
            return;
        }
        int insertPoint = 0;
        if (after != null) {
            insertPoint = after.getIndex();
        }
        int removePoint = toInsert.getIndex() - 1;
        if (removePoint >= insertPoint) {
            getLifelines().remove(removePoint);
        }
        getLifelines().add(insertPoint, toInsert);
        if (removePoint < insertPoint) {
            getLifelines().remove(removePoint);
        }

        if (removePoint >= insertPoint) {
            toInsert.setIndex(insertPoint + 1);
        } else {
            toInsert.setIndex(insertPoint - 1);
        }

        insertPoint++;
        if (removePoint >= insertPoint) {
            for (int i = insertPoint; i < getLifelines().size(); i++) {
                getLifeline(i).setIndex(i + 1);
            }
        } else {
            for (int i = 0; i < insertPoint && i < getLifelines().size(); i++) {
                getLifeline(i).setIndex(i + 1);
            }
        }
    }

    /**
     * Inserts a lifeline before a given lifeline.
     *
     * @param toInsert
     *            A lifeline to insert
     * @param before
     *            A lifeline the toInsert-lifeline will be inserted before.
     */
    public void insertLifelineBefore(Lifeline toInsert, Lifeline before) {
        if ((toInsert == null)) {
            return;
        }
        if (toInsert == before) {
            return;
        }
        int insertPoint = 0;
        if (before != null) {
            insertPoint = before.getIndex() - 1;
        }
        int removePoint = toInsert.getIndex() - 1;
        if (removePoint >= insertPoint) {
            getLifelines().remove(removePoint);
        }
        getLifelines().add(insertPoint, toInsert);
        if (removePoint < insertPoint) {
            getLifelines().remove(removePoint);
        }

        if (removePoint >= insertPoint) {
            toInsert.setIndex(insertPoint + 1);
        } else {
            toInsert.setIndex(insertPoint - 1);
        }

        insertPoint++;
        if (removePoint >= insertPoint) {
            for (int i = insertPoint; i < getLifelines().size(); i++) {
                getLifeline(i).setIndex(i + 1);
            }
        } else {
            for (int i = 0; i < insertPoint && i < getLifelines().size(); i++) {
                getLifeline(i).setIndex(i + 1);
            }
        }
    }

    /**
     * Gets the closer life line to the given x-coordinate.
     *
     * @param x A x coordinate
     * @return the closer lifeline
     */
    public Lifeline getCloserLifeline(int x) {
        int index = (x - Metrics.FRAME_H_MARGIN + Metrics.LIFELINE_H_MAGIN) / Metrics.swimmingLaneWidth() - 1;
        if (index < 0) {
            index = 0;
        }
        if (index >= getLifelines().size()) {
            index = getLifelines().size() - 1;
        }
        Lifeline node1, node2, node3;
        int dist1, dist2, dist3;
        node1 = node2 = node3 = getLifeline(index);
        dist1 = dist2 = dist3 = Math.abs(node1.getX() + node1.getWidth() / 2 - x);
        if (index > 0) {
            node2 = getLifeline(index - 1);
            dist2 = Math.abs(node2.getX() + node2.getWidth() / 2 - x);
        }
        if (index < getLifelines().size() - 1) {
            node3 = getLifeline(index + 1);
            dist3 = Math.abs(node3.getX() + node3.getWidth() / 2 - x);
        }
        if (dist1 <= dist2 && dist1 <= dist3) {
            return node1;
        } else if (dist2 <= dist1 && dist2 <= dist3) {
            return node2;
        }
        return node3;
    }

    /**
     * Re-orders the given list of lifelines.
     *
     * @param list A list of lifelines to reorder.
     */
    public void reorder(List<?> list) {
        for (int i = 0; i < list.size(); i++) {
            Object o = list.get(i);
            if (o instanceof Lifeline[]) {
                Lifeline temp[] = (Lifeline[]) o;
                if (temp.length == 2) {
                    if (temp[1] == null) {
                        insertLifelineAfter(temp[0], getLifeline(lifeLinesCount() - 1));
                    } else {
                        insertLifelineBefore(temp[0], temp[1]);
                    }
                }
            }
        }
    }

    /**
     * Resets the time compression information.
     */
    public void resetTimeCompression() {
        fHighlightLifeline = null;
        this.fStartEvent = 0;
        this.fNbEvent = 0;
        fHighlightColor = null;
    }

    @Override
    protected void computeMinMax() {
        List<SDTimeEvent> timeArray = buildTimeArray();
        if ((timeArray == null) || timeArray.isEmpty()) {
            return;
        }
        for (int i = 0; i < timeArray.size() - 1; i++) {
            SDTimeEvent m1 = timeArray.get(i);
            SDTimeEvent m2 = timeArray.get(i + 1);
            if (SDViewPref.getInstance().excludeExternalTime() && ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage))) {
                BaseMessage mes1 = (BaseMessage) m1.getGraphNode();
                BaseMessage mes2 = (BaseMessage) m2.getGraphNode();
                if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) {
                    continue;
                }
            }

            updateMinMax(m1, m2);
        }
    }

    /**
     * Find the two graph nodes that are closest to this date, one just earlier, second just later. If date is before
     * any graph node, bounds[0] is null and bounds[1] is the earliest. If date is after any graph node, bounds[1] is
     * null and bounds[0] is the latest.
     *
     * @param dateToFind date to be found
     * @param bounds a two items array that will receive bounds if found
     * @return true if both bounds not null
     */
    public boolean findDateBounds(ITmfTimestamp dateToFind, ITimeRange bounds[]) {
        if (hasTimeInfo()) {
            List<SDTimeEvent> timeArray = buildTimeArray();

            if ((timeArray == null) || timeArray.isEmpty()) {
                return false;
            }

            bounds[0] = null;
            bounds[1] = null;
            for (int i = 0; i < timeArray.size(); i++) {
                SDTimeEvent m = timeArray.get(i);
                if (m.getTime().compareTo(dateToFind) > 0) {
                    bounds[1] = m.getGraphNode();
                    if (i > 0) {
                        bounds[0] = timeArray.get(i - 1).getGraphNode();
                        return true;
                    }
                    return false;
                }
            }
            bounds[0] = timeArray.get(timeArray.size() - 1).getGraphNode();
        }
        return false;
    }

    /**
     * Highlights the time compression.
     *
     * @param lifeline A lifeline to highlight
     * @param startEvent A start event number
     * @param nbEvent A number of events
     * @param color A color for highlighting
     */
    public void highlightTimeCompression(Lifeline lifeline, int startEvent, int nbEvent, IColor color) {
        fHighlightLifeline = lifeline;
        this.fStartEvent = startEvent;
        this.fNbEvent = nbEvent;
        fHighlightColor = color;
    }

    /**
     * Set the lifeline categories which will be use during the lifelines creation
     *
     * @see Lifeline#setCategory(int)
     * @param categories the lifeline categories array
     */
    public void setLifelineCategories(LifelineCategories[] categories) {
        fLifelineCategories = Arrays.copyOf(categories, categories.length);
    }

    /**
     * Returns the lifeline categories array set for the this frame
     *
     * @return the lifeline categories array or null if not set
     */
    public LifelineCategories[] getLifelineCategories() {
        return  Arrays.copyOf(fLifelineCategories, fLifelineCategories.length);
    }

    /**
     * Adds a message to the Frame message list. Four kinds of syncMessages can be added:<br>
     * - synchronous syncMessages<br>
     * - synchronous syncMessages return<br>
     * - asynchronous syncMessages<br>
     * - asynchronous syncMessages return<br>
     * For drawing performance reason, it is recommended to add synchronous syncMessages in the same order they should
     * appear along the Y axis in the Frame.
     *
     * @param message the message to add
     */
    public void addMessage(BaseMessage message) {
        addNode(message);
    }

    @Override
    public void draw(IGC context) {
        drawFrame(context);
        if (!hasChildren()) {
            return;
        }

        if (fHighlightLifeline != null) {
            IColor backupColor = context.getBackground();
            context.setBackground(SDViewPref.getInstance().getTimeCompressionSelectionColor());
            int gy = fHighlightLifeline.getY() + fHighlightLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fStartEvent;
            context.fillRectangle(Metrics.FRAME_H_MARGIN + 1, gy, fHighlightLifeline.getX() + Metrics.getLifelineWidth() / 2 - Metrics.FRAME_H_MARGIN, (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fNbEvent);
            context.setBackground(backupColor);
        }
        super.draw(context, false);
        int lifelineArryStep = 1;
        if (Metrics.swimmingLaneWidth() * context.getZoom() < Metrics.LIFELINE_SIGNIFICANT_HSPACING) {
            lifelineArryStep = Math.round(Metrics.LIFELINE_SIGNIFICANT_HSPACING / (Metrics.swimmingLaneWidth() * context.getZoom()));
        }
        if (getIndexes().size() == 0) {
            return;
        }
        int lifeLineDrawIndex = checkNotNull(getIndexes().get(Lifeline.LIFELINE_TAG)).intValue();
        List<GraphNode> nodeList = checkNotNull(getNodeMap().get(Lifeline.LIFELINE_TAG));
        for (int i = lifeLineDrawIndex; i < nodeList.size(); i = i + lifelineArryStep) {
            Lifeline toDraw = (Lifeline) nodeList.get(i);
            if (toDraw.getX() - Metrics.LIFELINE_SPACING / 2 > context.getContentsX() + context.getVisibleWidth()) {
                break;
            }
            toDraw.drawName(context);

            if (fHighlightLifeline != null) {
                if (toDraw == fHighlightLifeline) {
                    toDraw.highlightExecOccurrenceRegion(context, fStartEvent, fNbEvent, fHighlightColor);
                } else if (toDraw.getIndex() < fHighlightLifeline.getIndex()) {
                    int acIndex = toDraw.getExecOccurrenceDrawIndex();
                    // acIndex = first visible execution occurrence
                    // for drawing speed reason with only search on the visible subset
                    if (toDraw.getExecutions() != null) {
                        for (int index = acIndex; index < toDraw.getExecutions().size(); index++) {
                            BasicExecutionOccurrence exec = (BasicExecutionOccurrence) toDraw.getExecutions().get(index);
                            int tempEvent = fStartEvent;
                            for (int j = 0; j < fNbEvent; j++) {
                                if (((tempEvent >= exec.getStartOccurrence()) && (tempEvent <= exec.getEndOccurrence()) && (tempEvent + 1 >= exec.getStartOccurrence()) && (tempEvent + 1 <= exec.getEndOccurrence()))) {
                                    toDraw.highlightExecOccurrenceRegion(context, tempEvent, 1, SDViewPref.getInstance().getTimeCompressionSelectionColor());
                                }
                                tempEvent = tempEvent + 1;
                            }
                            // if we are outside the visible area we stop right now
                            // This works because execution occurrences are ordered along the Y axis
                            if (exec.getY() > getY()) {
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    protected List<SDTimeEvent> buildTimeArray() {

        if (!hasChildren()) {
            return new ArrayList<>();
        }

        List<SDTimeEvent> timeArray = super.buildTimeArray();
        fExecutionOccurrencesWithTime = null;
        if (getLifelines() != null) {
            List<GraphNode> nodeList = checkNotNull(getNodeMap().get(Lifeline.LIFELINE_TAG));
            for (int i = 0; i < nodeList.size(); i++) {
                Lifeline lifeline = (Lifeline) nodeList.get(i);
                if (lifeline.hasTimeInfo() && lifeline.getExecutions() != null) {
                    for (Iterator<GraphNode> j = lifeline.getExecutions().iterator(); j.hasNext();) {
                        GraphNode o = j.next();
                        if (o instanceof ExecutionOccurrence) {
                            ExecutionOccurrence eo = (ExecutionOccurrence) o;
                            if (eo.hasTimeInfo()) {
                                int event = eo.getStartOccurrence();
                                ITmfTimestamp time = eo.getStartTime();
                                SDTimeEvent f = new SDTimeEvent(time, event, eo);
                                timeArray.add(f);
                                if (fExecutionOccurrencesWithTime == null) {
                                    fExecutionOccurrencesWithTime = new ArrayList<>();
                                }
                                fExecutionOccurrencesWithTime.add(f);
                                event = eo.getEndOccurrence();
                                time = eo.getEndTime();
                                f = new SDTimeEvent(time, event, eo);
                                timeArray.add(f);
                                fExecutionOccurrencesWithTime.add(f);
                            }
                        }
                    }
                }
            }
        }

        if (fExecutionOccurrencesWithTime != null) {
            SDTimeEvent[] temp = fExecutionOccurrencesWithTime.toArray(new SDTimeEvent[fExecutionOccurrencesWithTime.size()]);
            Arrays.sort(temp, new TimeEventComparator());
            fExecutionOccurrencesWithTime = Arrays.asList(temp);
        }
        SDTimeEvent[] temp = timeArray.toArray(new SDTimeEvent[timeArray.size()]);
        Arrays.sort(temp, new TimeEventComparator());
        timeArray = Arrays.asList(temp);
        return timeArray;
    }

    /**
     * Get the closer leaving message.
     *
     * @param lifeline A lifeline reference
     * @param message A message reference
     * @param list A list of graph nodes
     * @param smallerEvent A smaller event flag
     * @return the closer leaving message.
     */
    protected GraphNode getCloserLeavingMessage(Lifeline lifeline, BaseMessage message, List<GraphNode> list, boolean smallerEvent) {
        if (list == null) {
            return null;
        }

        if (!smallerEvent) {
            int event = 0;
            if (message != null) {
                event = message.getEventOccurrence();
            }
            for (int i = 0; i < list.size(); i++) {
                GraphNode node = list.get(i);
                if (node instanceof SyncMessage) {
                    SyncMessage syncNode = (SyncMessage) node;
                    if ((syncNode.getEventOccurrence() > event) && (syncNode.getStartLifeline() == lifeline) && !syncNode.isSameAs(message)) {
                        return node;
                    }
                } else if (node instanceof AsyncMessage) {
                    AsyncMessage asyncNode = (AsyncMessage) node;
                    if ((asyncNode.getStartOccurrence() > event) && (asyncNode.getStartLifeline() == lifeline) && !asyncNode.isSameAs(message)) {
                        return node;
                    }
                }
            }
        } else {
            int event = getMaxEventOccurrence();
            if (message != null) {
                if (message instanceof AsyncMessage) {
                    event = ((AsyncMessage) message).getStartOccurrence();
                } else {
                    event = message.getEventOccurrence();
                }
            }
            for (int i = list.size() - 1; i >= 0; i--) {
                GraphNode node = list.get(i);
                if (node instanceof SyncMessage) {
                    SyncMessage syncNode = (SyncMessage) node;
                    if ((syncNode.getEventOccurrence() < event) && (syncNode.getStartLifeline() == lifeline) && !syncNode.isSameAs(message)) {
                        return node;
                    }
                } else if (node instanceof AsyncMessage) {
                    AsyncMessage asyncNode = (AsyncMessage) node;
                    if ((asyncNode.getStartOccurrence() < event) && (asyncNode.getStartLifeline() == lifeline) && !asyncNode.isSameAs(message)) {
                        return node;
                    }
                }
            }
        }
        return null;
    }


    /**
     * Get the closer entering message.
     *
     * @param lifeline A lifeline reference
     * @param message A message reference
     * @param list A list of graph nodes
     * @param smallerEvent A smaller event flag
     * @return the closer entering message.
     */
    protected GraphNode getCloserEnteringMessage(Lifeline lifeline, BaseMessage message, List<GraphNode> list, boolean smallerEvent) {
        if (list == null) {
            return null;
        }
        if (!smallerEvent) {
            int event = 0;
            if (message != null) {
                event = message.getEventOccurrence();
            }
            for (int i = 0; i < list.size(); i++) {
                GraphNode node = list.get(i);
                if (node instanceof SyncMessage) {
                    SyncMessage syncNode = (SyncMessage) node;
                    if ((syncNode.getEventOccurrence() > event) && (syncNode.getEndLifeline() == lifeline) && !syncNode.isSameAs(message)) {
                        return node;
                    }
                } else if (node instanceof AsyncMessage) {
                    AsyncMessage asyncNode = (AsyncMessage) node;
                    if ((asyncNode.getStartOccurrence() > event) && (asyncNode.getEndLifeline() == lifeline) && !asyncNode.isSameAs(message)) {
                        return node;
                    }
                }
            }
        } else {
            int event = getMaxEventOccurrence();
            if (message != null) {
                if (message instanceof AsyncMessage) {
                    event = ((AsyncMessage) message).getStartOccurrence();
                } else {
                    event = message.getEventOccurrence();
                }
            }
            for (int i = list.size() - 1; i >= 0; i--) {
                GraphNode node = list.get(i);
                if (node instanceof SyncMessage) {
                    SyncMessage syncNode = (SyncMessage) node;
                    if ((syncNode.getEventOccurrence() < event) && (syncNode.getEndLifeline() == lifeline) && !syncNode.isSameAs(message)) {
                        return node;
                    }
                } else if (node instanceof AsyncMessage) {
                    AsyncMessage asyncNode = (AsyncMessage) node;
                    if ((asyncNode.getStartOccurrence() < event) && (asyncNode.getEndLifeline() == lifeline) && !asyncNode.isSameAs(message)) {
                        return node;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get distance of given event from given graph node.
     *
     * @param node A graph node reference.
     * @param event A event number to check.
     * @return distance of event from graph node.
     */
    protected int distanceFromEvent(GraphNode node, int event) {
        int distance = 0;
        if (node instanceof SyncMessage) {
            distance = ((SyncMessage) node).getEventOccurrence() - event;
        } else if (node instanceof AsyncMessage) {
            int start = ((AsyncMessage) node).getStartOccurrence();
            int end = ((AsyncMessage) node).getEndOccurrence();
            if ((start - event) < (end - event)) {
                distance = start - event;
            } else {
                distance = end - event;
            }
        }
        return Math.abs(distance);
    }

    /**
     * Get node from 2 given nodes that is close to event.
     *
     * @param node1 A first graph node
     * @param node2 A second graph node
     * @param event A event to check.
     * @return graph node that is closer or <code>null</code>
     */
    protected GraphNode getCloserToEvent(GraphNode node1, GraphNode node2, int event) {
        if ((node1 != null) && (node2 != null)) {
            if (distanceFromEvent(node1, event) < distanceFromEvent(node2, event)) {
                return node1;
            }
            return node2;
        } else if (node1 != null) {
            return node1;
        } else if (node2 != null) {
            return node2;
        }
        return null;
    }

    /**
     * Get called message based on given start message.
     *
     * @param startMessage A start message to check.
     * @return called message (graph node) or <code>null</code>
     */
    public GraphNode getCalledMessage(BaseMessage startMessage) {
        int event = 0;
        GraphNode result = null;
        Lifeline lifeline = null;
        if (startMessage != null) {
            event = startMessage.getEventOccurrence();
            lifeline = startMessage.getEndLifeline();
            if (lifeline == null) {
                lifeline = startMessage.getStartLifeline();
            }
        }
        if (lifeline == null) {
            return null;
        }
        GraphNode message = getCloserLeavingMessage(lifeline, startMessage, getSyncMessages(), false);
        GraphNode messageReturn = getCloserLeavingMessage(lifeline, startMessage, getSyncMessagesReturn(), false);
        result = getCloserToEvent(message, messageReturn, event);
        message = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessages(), false);
        result = getCloserToEvent(result, message, event);
        messageReturn = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessagesReturn(), false);
        result = getCloserToEvent(result, messageReturn, event);
        return result;
    }

    /**
     * Get caller message based on given start message.
     *
     * @param startMessage A start message to check.
     * @return called message (graph node) or <code>null</code>
     */
    public GraphNode getCallerMessage(BaseMessage startMessage) {
        int event = getMaxEventOccurrence();
        GraphNode result = null;
        Lifeline lifeline = null;
        if (startMessage != null) {
            event = startMessage.getEventOccurrence();
            lifeline = startMessage.getStartLifeline();
            if (lifeline == null) {
                lifeline = startMessage.getEndLifeline();
            }
        }
        if (lifeline == null) {
            return null;
        }
        GraphNode message = getCloserEnteringMessage(lifeline, startMessage, getSyncMessages(), true);
        GraphNode messageReturn = getCloserEnteringMessage(lifeline, startMessage, getSyncMessagesReturn(), true);
        result = getCloserToEvent(message, messageReturn, event);
        message = getCloserEnteringMessage(lifeline, startMessage, getAsyncMessages(), true);
        result = getCloserToEvent(result, message, event);
        messageReturn = getCloserEnteringMessage(lifeline, startMessage, getAsyncMessagesReturn(), true);
        result = getCloserToEvent(result, messageReturn, event);
        return result;
    }

    /**
     * Get next lifeline based on given message.
     *
     * @param lifeline A lifeline reference
     * @param startMessage A start message to check
     * @return next lifeline or <code>null</code>
     */
    public GraphNode getNextLifelineMessage(Lifeline lifeline, BaseMessage startMessage) {
        int event = 0;
        if (startMessage != null) {
            event = startMessage.getEventOccurrence();
        }
        if (lifeline == null) {
            return null;
        }
        GraphNode message = getCloserLeavingMessage(lifeline, startMessage, getSyncMessages(), false);
        GraphNode messageReturn = getCloserLeavingMessage(lifeline, startMessage, getSyncMessagesReturn(), false);
        GraphNode result = getCloserToEvent(message, messageReturn, event);
        message = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessages(), false);
        result = getCloserToEvent(result, message, event);
        messageReturn = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessagesReturn(), false);
        result = getCloserToEvent(result, messageReturn, event);
        return result;
    }

    /**
     * Get previous lifeline based on given message.
     *
     * @param lifeline A lifeline reference
     * @param startMessage A start message to check.
     * @return previous lifeline or <code>null</code>
     */
    public GraphNode getPrevLifelineMessage(Lifeline lifeline, BaseMessage startMessage) {
        int event = getMaxEventOccurrence();
        if (startMessage != null) {
            if (startMessage instanceof AsyncMessage) {
                event = ((AsyncMessage) startMessage).getStartOccurrence();
            } else {
                event = startMessage.getEventOccurrence();
            }
        }
        if (lifeline == null) {
            return null;
        }
        GraphNode message = getCloserLeavingMessage(lifeline, startMessage, getSyncMessages(), true);
        GraphNode messageReturn = getCloserLeavingMessage(lifeline, startMessage, getSyncMessagesReturn(), true);
        GraphNode result = getCloserToEvent(message, messageReturn, event);
        message = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessages(), true);
        result = getCloserToEvent(result, message, event);
        messageReturn = getCloserLeavingMessage(lifeline, startMessage, getAsyncMessagesReturn(), true);
        result = getCloserToEvent(result, messageReturn, event);
        return result;
    }

    /**
     * Get the first execution occurrence.
     *
     * @param lifeline A lifeline reference
     * @return the first execution occurrence of lifeline or <code>null</code>.
     */
    public BasicExecutionOccurrence getFirstExecution(Lifeline lifeline) {
        if (lifeline == null) {
            return null;
        }
        List<GraphNode> list = lifeline.getExecutions();

        if ((list == null) || (list.isEmpty())) {
            return null;
        }

        BasicExecutionOccurrence result = (BasicExecutionOccurrence) list.get(0);
        for (int i = 0; i < list.size(); i++) {
            BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i);
            if ((e.getStartOccurrence() < result.getEndOccurrence())) {
                result = e;
            }
        }
        return result;
    }

    /**
     * Get the previous execution occurrence relative to a given execution occurrence.
     *
     * @param exec A execution occurrence reference.
     * @return the previous execution occurrence of lifeline or <code>null</code>.
     */
    public BasicExecutionOccurrence getPrevExecOccurrence(BasicExecutionOccurrence exec) {
        if (exec == null) {
            return null;
        }
        Lifeline lifeline = exec.getLifeline();
        if (lifeline == null) {
            return null;
        }
        List<GraphNode> list = lifeline.getExecutions();
        if (list == null) {
            return null;
        }
        BasicExecutionOccurrence result = null;
        for (int i = 0; i < list.size(); i++) {
            BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i);
            if ((e.getStartOccurrence() < exec.getStartOccurrence()) && (result == null)) {
                result = e;
            }
            if ((e.getStartOccurrence() < exec.getStartOccurrence()) && (result != null) && (e.getStartOccurrence() >= result.getEndOccurrence())) {
                result = e;
            }
        }
        return result;
    }

    /**
     * Get the next execution occurrence relative to a given execution occurrence.
     *
     * @param exec A execution occurrence reference.
     * @return the next execution occurrence of lifeline or <code>null</code>.
     */
    public BasicExecutionOccurrence getNextExecOccurrence(BasicExecutionOccurrence exec) {
        if (exec == null) {
            return null;
        }
        Lifeline lifeline = exec.getLifeline();
        if (lifeline == null) {
            return null;
        }
        List<GraphNode> list = lifeline.getExecutions();
        if (list == null) {
            return null;
        }
        BasicExecutionOccurrence result = null;
        for (int i = 0; i < list.size(); i++) {
            BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i);
            if ((e.getStartOccurrence() > exec.getStartOccurrence()) && (result == null)) {
                result = e;
            }
            if ((e.getStartOccurrence() > exec.getStartOccurrence()) && (result != null) && (e.getStartOccurrence() <= result.getEndOccurrence())) {
                result = e;
            }
        }
        return result;
    }

    /**
     * Get the last execution occurrence.
     *
     * @param lifeline A lifeline reference.
     * @return the last execution occurrence of lifeline or <code>null</code>.
     */
    public BasicExecutionOccurrence getLastExecOccurrence(Lifeline lifeline) {
        if (lifeline == null) {
            return null;
        }
        List<GraphNode> list = lifeline.getExecutions();
        if (list == null) {
            return null;
        }
        BasicExecutionOccurrence result = null;
        for (int i = 0; i < list.size(); i++) {
            BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i);
            if (result == null) {
                result = e;
            }
            if (e.getStartOccurrence() > result.getEndOccurrence()) {
                result = e;
            }
        }
        return result;
    }

    /**
     * @return highlighted life line if set else null.
     */
    protected Lifeline getHighlightLifeline() {
        return fHighlightLifeline;
    }

    /**
     * @return the start event value.
     */
    protected int getStartEvent() {
        return fStartEvent;
    }

    /**
     * Returns the number of events
     *
     * @return the number of events
     */
    protected int getNumberOfEvents() {
        return fNbEvent;
    }

    /**
     * Returns the highlight color.
     *
     * @return the highlight color
     */
    protected IColor getHighlightColor() {
        return fHighlightColor;
    }

    /**
     * Set the highlighted life line.
     *
     * @param lifeline
     *          The highlighted life line if set else null
     */
    protected void setHighlightLifeline(Lifeline lifeline) {
        fHighlightLifeline = lifeline;
    }

    /**
     * Sets the start event value
     *
     * @param startEvent
     *           the start event value.
     */
    protected void setStartEvent(int startEvent) {
        fStartEvent = startEvent;
    }

    /**
     * Sets the number of events
     *
     * @param nbEvents
     *          The number of events
     */
    protected void setNumberOfEvents(int nbEvents) {
        fNbEvent = nbEvents;
    }

    /**
     * Sets the highlight color.
     *
     * @param color
     *          the highlight color
     */
    protected void setHighlightColor(IColor color) {
        fHighlightColor = color;
    }

    /**
     * sets the list of execution occurrences.
     *
     * @param occurences
     *          the list of execution occurrences
     */
    protected void setExecutionOccurrencesWithTime(List<SDTimeEvent> occurences) {
        fExecutionOccurrencesWithTime = occurences;
    }
}