package com.baselet.element.sequence_aio.facet;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.baselet.control.basics.geom.DimensionDouble;
import com.baselet.control.basics.geom.PointDouble;
import com.baselet.control.enums.AlignHorizontal;
import com.baselet.control.enums.AlignVertical;
import com.baselet.diagram.draw.DrawHandler;
import com.baselet.diagram.draw.TextSplitter;
import com.baselet.element.sequence_aio.facet.LifelineSpanningTickSpanningOccurrence.ContainerPadding;
public class SequenceDiagram {
private static final double LIFELINE_X_PADDING = 40;
private static final double LIFELINE_Y_PADDING = 20;
private static final double LIFELINE_MIN_WIDTH = 100;
private static final double DESCRIPTION_V_PADDING = 10;
private static final double DESCRIPTION_H_PADDING = 10;
private static final double TICK_HEIGHT = 20;
private static final double TICK_Y_PADDING = 6;// should be odd and the nothing should draw at the center!
private String[] titleLines;
private String[] descLines;
// options
private Lifeline[] lifelines;
private final Collection<LifelineSpanningTickSpanningOccurrence> spanningLifelineOccurrences;
private int lastTick;
public SequenceDiagram() {
titleLines = new String[] { "" };
descLines = new String[] { "" };
lifelines = new Lifeline[0];
spanningLifelineOccurrences = new LinkedList<LifelineSpanningTickSpanningOccurrence>();
}
/**
* @param title lines which are separated by a \n
*/
public void setTitle(String title) {
titleLines = title.split("\n");
}
// /**
// * @param titleLines an array of lines which build the title (each element must not contain a \n)
// */
// public void setTitle(String[] titleLines) {
// this.titleLines = titleLines;
// }
/**
* @param text description lines which are separated by a \n
*/
public void setText(String text) {
descLines = text.split("\n");
}
// /**
// * @param textLines an array of lines which build the description (each element must not contain a \n)
// */
// public void setText(String[] textLines) {
// descLines = textLines;
// }
public int getLastTick() {
return lastTick;
}
public void setLastTick(int lastTick) {
this.lastTick = lastTick;
}
/**
*
* @param headText
* @param headType
* @param createdOnStart if false the lifeline will be created by the first message sent to this lifeline
* @param execSpecFromStart
*/
public Lifeline addLiveline(String headText, Lifeline.LifelineHeadType headType, boolean createdOnStart, boolean execSpecFromStart) {
lifelines = Arrays.copyOf(lifelines, lifelines.length + 1);
lifelines[lifelines.length - 1] = new Lifeline(headText, lifelines.length - 1, headType, createdOnStart, execSpecFromStart);
return lifelines[lifelines.length - 1];
}
public void addLifelineSpanningTickSpanningOccurrence(LifelineSpanningTickSpanningOccurrence occurrence) {
spanningLifelineOccurrences.add(occurrence);
}
/**
*
* @return how many lifelines the diagram has
*/
public int getLifelineCount() {
return lifelines.length;
}
public List<Lifeline> getLifelines() {
return Arrays.asList(lifelines);
}
public Lifeline[] getLifelinesArray() {
return Arrays.copyOf(lifelines, lifelines.length);
}
public DimensionDouble draw(DrawHandler drawHandler) {
DoubleConverter identity = new DoubleConverter() {
@Override
public double convert(double value) {
return value;
}
};
return draw(drawHandler, identity, identity);
}
public DimensionDouble draw(DrawHandler drawHandler, DoubleConverter widthConverter, DoubleConverter heightConverter) {
HorizontalDrawingInfo horizontalDrawingInfo;
VerticalDrawingInfo verticalInfo;
DrawingInfo drawingInfo;
// calculate the minimum width of the lifelines and the diagram; get all paddings and create the horizontal drawing info
double lifelineWidth = Math.max(getLifelineWidth(drawHandler), LIFELINE_MIN_WIDTH);
double diagramMinWidth = Math.max(LIFELINE_MIN_WIDTH,
TextSplitter.getTextMinWidth(descLines, drawHandler) + DESCRIPTION_H_PADDING * 2);
diagramMinWidth = Math.max(diagramMinWidth, PentagonDrawingHelper.getMinimumWidth(drawHandler, titleLines));
Collection<ContainerPadding> allPaddings = new LinkedList<ContainerPadding>();
for (LifelineSpanningTickSpanningOccurrence lstso : spanningLifelineOccurrences) {
if (lstso.getPaddingInformation() != null) {
allPaddings.add(lstso.getPaddingInformation());
}
}
horizontalDrawingInfo = new HorizontalDrawingInfoImpl(0, diagramMinWidth, widthConverter, lifelineWidth,
LIFELINE_X_PADDING, lifelines.length, lastTick, allPaddings);
double diagramWidth = horizontalDrawingInfo.getDiagramWidth();
// calculate and draw the header, then draw top border
double headerHeight = 0;
if (titleLines.length > 1 || titleLines.length == 1 && !titleLines[0].isEmpty()) {
headerHeight = PentagonDrawingHelper.draw(drawHandler, titleLines, diagramWidth, new PointDouble(0, 0)).y;
}
drawHandler.drawLine(0, 0, diagramWidth, 0);
// draw description
double descHeight = TextSplitter.getSplitStringHeight(descLines, diagramWidth - DESCRIPTION_H_PADDING * 2, drawHandler);
TextSplitter.drawText(drawHandler, descLines, DESCRIPTION_H_PADDING, headerHeight + DESCRIPTION_V_PADDING,
diagramWidth - DESCRIPTION_H_PADDING * 2, descHeight, AlignHorizontal.LEFT, AlignVertical.CENTER);
double lifelineHeadTop = headerHeight + descHeight + DESCRIPTION_H_PADDING * 2 + LIFELINE_Y_PADDING;
double lifelineHeadHeight = getLifelineHeadHeight(drawHandler, horizontalDrawingInfo);
verticalInfo = new VerticalDrawingInfoImpl(lifelineHeadTop, lifelineHeadHeight, TICK_HEIGHT, TICK_Y_PADDING,
calculateAddiontalHeights(drawHandler, horizontalDrawingInfo), allPaddings);
drawingInfo = new DrawingInfoImpl(horizontalDrawingInfo, verticalInfo, getLifelineCount());
// first draw the occurrences which affect more than one lifeline which stores the interrupted areas in the
// corresponding LifelineDrawingInfo. This info is then passed to the lifeline so it can be drawn
if (lifelines.length > 0) {
for (LifelineSpanningTickSpanningOccurrence llstso : spanningLifelineOccurrences) {
llstso.draw(drawHandler, drawingInfo);
}
for (Lifeline ll : lifelines) {
ll.draw(drawHandler, drawingInfo.getDrawingInfo(ll), lastTick);
}
}
// draw left,right and bottom border
double bottomY = heightConverter.convert(verticalInfo.getVerticalEnd(lastTick) + LIFELINE_Y_PADDING);
drawHandler.drawLine(0, bottomY, diagramWidth, bottomY);
drawHandler.drawLine(0, 0, 0, bottomY);
drawHandler.drawLine(diagramWidth, 0, diagramWidth, bottomY);
return new DimensionDouble(diagramWidth, bottomY);
}
private double getLifelineWidth(DrawHandler drawHandler) {
double maxMinWidth = 0;
for (Lifeline ll : lifelines) {
maxMinWidth = Math.max(maxMinWidth, ll.getMinWidth(drawHandler));
}
for (LifelineSpanningTickSpanningOccurrence llstso : spanningLifelineOccurrences) {
int llCount = llstso.getLastLifeline().getIndex() - llstso.getFirstLifeline().getIndex() + 1;
maxMinWidth = Math.max(maxMinWidth,
(llstso.getOverallMinWidth(drawHandler, LIFELINE_X_PADDING) - LIFELINE_X_PADDING * (llCount - 1)) / llCount);
}
return maxMinWidth;
}
private double[] calculateAddiontalHeights(DrawHandler drawHandler, HorizontalDrawingInfo hDrawingInfo) {
double[] addiontalHeight = new double[lastTick + 1];
for (Lifeline ll : lifelines) {
for (Map.Entry<Integer, Double> e : ll.getAdditionalYHeights(drawHandler, hDrawingInfo.getHDrawingInfo(ll),
TICK_HEIGHT).entrySet()) {
addiontalHeight[e.getKey()] = Math.max(addiontalHeight[e.getKey()], e.getValue());
}
}
for (LifelineSpanningTickSpanningOccurrence llstso : spanningLifelineOccurrences) {
for (Map.Entry<Integer, Double> e : llstso.getEveryAdditionalYHeight(drawHandler, hDrawingInfo,
TICK_HEIGHT).entrySet()) {
addiontalHeight[e.getKey()] = Math.max(addiontalHeight[e.getKey()], e.getValue());
}
}
return addiontalHeight;
}
private double getLifelineHeadHeight(DrawHandler drawHandler, HorizontalDrawingInfo hDrawingInfo) {
double maxHeight = 0;
for (Lifeline ll : lifelines) {
if (ll.isCreatedOnStart()) {
maxHeight = Math.max(maxHeight, ll.getHeadMinHeight(drawHandler, hDrawingInfo.getHDrawingInfo(ll).getWidth()));
}
}
return maxHeight;
}
public static interface DoubleConverter {
public double convert(double value);
}
}