package com.baselet.element.sequence_aio.facet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import com.baselet.element.sequence_aio.facet.LifelineSpanningTickSpanningOccurrence.ContainerPadding;
/**
* Only supports ContainerPaddings which don't overlap at start, i.e. if the start tick is the same the lifeline
* interval must not intersect.
*
*/
public class VerticalDrawingInfoImpl implements VerticalDrawingInfo {
private final double startingHeadTopY;
private final double startingHeadHeight;
private final double tickVerticalPadding;
private final double defaultTickHeight;
private final double[] accumulativeAddiontalHeightOffsets;
private final double[] topPadding;
private final double[] bottomPadding;
/** contains the bottom padding of the enclosing containers which end at the same tick */
private final Map<Container, Double> containerBottomPadding;
private final double startingTickTopY;
/**
* @param startingHeadTopY
* @param startingHeadHeight
* @param defaultTickHeight
* @param tickVerticalPadding <code>tickVerticalPadding = getVerticalEnd(tick)- getVerticalStart(tick+1)</code>
* @param addiontalHeights for every tick how much height should be added to this tick, therefore length at least
* lastTick + 1 so that lastTick is a valid index
* @param allPaddings
*/
public VerticalDrawingInfoImpl(double startingHeadTopY, double startingHeadHeight, double defaultTickHeight,
double tickVerticalPadding, double[] addiontalHeights, Collection<ContainerPadding> allPaddings) {
this.startingHeadTopY = startingHeadTopY;
this.startingHeadHeight = startingHeadHeight;
this.tickVerticalPadding = tickVerticalPadding;
this.defaultTickHeight = defaultTickHeight;
topPadding = new double[addiontalHeights.length + 1];
bottomPadding = new double[addiontalHeights.length + 1];
containerBottomPadding = new HashMap<Container, Double>((int) (allPaddings.size() / 0.7));
accumulativeAddiontalHeightOffsets = new double[addiontalHeights.length + 1];
processPaddings(addiontalHeights, allPaddings);
startingTickTopY = startingHeadTopY + startingHeadHeight + tickVerticalPadding;
}
private void processPaddings(double[] addiontalHeights, Collection<ContainerPadding> allPaddings) {
Map<Integer, List<ContainerPadding>> endMap = new HashMap<Integer, List<ContainerPadding>>();
Map<Integer, List<ContainerPadding>> startMap = new HashMap<Integer, List<ContainerPadding>>();
for (ContainerPadding cp : allPaddings) {
if (!startMap.containsKey(cp.getContainer().getStartTick())) {
startMap.put(cp.getContainer().getStartTick(), new LinkedList<ContainerPadding>());
}
startMap.get(cp.getContainer().getStartTick()).add(cp);
if (!endMap.containsKey(cp.getContainer().getEndTick())) {
endMap.put(cp.getContainer().getEndTick(), new LinkedList<ContainerPadding>());
}
endMap.get(cp.getContainer().getEndTick()).add(cp);
}
for (Map.Entry<Integer, List<ContainerPadding>> e : startMap.entrySet()) {
for (ContainerPadding cp : e.getValue()) {
topPadding[e.getKey()] = Math.max(topPadding[e.getKey()], cp.getTopPadding());
}
}
calculateBottomPaddings(endMap);
double sum = 0;
for (int i = 0; i < addiontalHeights.length; i++) {
sum += addiontalHeights[i] + topPadding[i] + bottomPadding[i];
accumulativeAddiontalHeightOffsets[i + 1] = sum;
}
}
private void calculateBottomPaddings(Map<Integer, List<ContainerPadding>> endMap) {
for (Map.Entry<Integer, List<ContainerPadding>> e : endMap.entrySet()) {
while (!e.getValue().isEmpty()) {
List<ContainerPadding> cpList = new LinkedList<ContainerPadding>();
cpList.add(e.getValue().remove(0));
int startLl = cpList.get(0).getContainer().getFirstLifeline().getIndex();
int endLl = cpList.get(0).getContainer().getLastLifeline().getIndex();
// add all lifelines to the list which have intersecting lifelines
ListIterator<ContainerPadding> cpIter = e.getValue().listIterator();
while (cpIter.hasNext()) {
ContainerPadding cp = cpIter.next();
if (isIntersecting(startLl, endLl, cp.getContainer())) {
cpList.add(cp);
cpIter.remove();
startLl = Math.min(startLl, cp.getContainer().getFirstLifeline().getIndex());
endLl = Math.max(endLl, cp.getContainer().getLastLifeline().getIndex());
}
}
Collections.sort(cpList, ContainerPadding.getContainerStartTickLifelineAscComparator());
double padding = 0;
for (ContainerPadding cp : cpList) {
containerBottomPadding.put(cp.getContainer(), padding);
padding += cp.getBottomPadding();
}
bottomPadding[e.getKey()] = Math.max(bottomPadding[e.getKey()], padding);
}
}
}
private boolean contains(int low, int high, int value) {
return low <= value && value <= high;
}
private boolean isIntersecting(int startLl, int endLl, Container container) {
boolean isIntersecting = contains(startLl, endLl, container.getFirstLifeline().getIndex());
isIntersecting = isIntersecting || contains(startLl, endLl, container.getLastLifeline().getIndex());
isIntersecting = isIntersecting || contains(container.getLastLifeline().getIndex(),
container.getLastLifeline().getIndex(), startLl);
return isIntersecting;
}
@Override
public double getVerticalStart(int tick) {
return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + accumulativeAddiontalHeightOffsets[tick]
+ topPadding[tick];
}
@Override
public double getVerticalEnd(int tick) {
return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + defaultTickHeight
+ accumulativeAddiontalHeightOffsets[tick + 1] - bottomPadding[tick];
}
@Override
public double getTickHeight(int tick) {
return getVerticalEnd(tick) - getVerticalStart(tick);
}
@Override
public double getTickVerticalPadding() {
return tickVerticalPadding;
}
@Override
public double getVerticalHeadStart() {
return startingHeadTopY;
}
@Override
public double getVerticalHeadEnd() {
return getVerticalHeadStart() + startingHeadHeight;
}
@Override
public double getHeadHeight() {
return startingHeadHeight;
}
@Override
public double getVerticalCenter(int tick) {
return getVerticalStart(tick) / 2 + getVerticalEnd(tick) / 2;
}
@Override
public double getVerticalStart(Container container) {
// since the starting points aren't allowed to overlapp we only need to calculate the start without the topPadding
int tick = container.getStartTick();
return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + accumulativeAddiontalHeightOffsets[tick];
}
@Override
public double getVerticalEnd(Container container) {
// this is tricky because the end ticks can overlap
int tick = container.getEndTick();
return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + defaultTickHeight
+ accumulativeAddiontalHeightOffsets[tick + 1] - containerBottomPadding.get(container);
}
}