package com.xenoage.zong.core.music.util;
import com.xenoage.utils.collections.CList;
import com.xenoage.utils.collections.IList;
import com.xenoage.utils.iterators.ReverseIterator;
import com.xenoage.utils.math.Fraction;
import com.xenoage.zong.core.music.MusicElement;
import com.xenoage.zong.core.music.MusicElementType;
import lombok.Data;
import lombok.val;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.xenoage.utils.collections.CList.clist;
import static com.xenoage.utils.collections.CList.ilist;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.zong.core.music.util.BeatE.beatE;
import static com.xenoage.zong.core.music.util.Interval.Result.True;
/**
* This is a wrapper class to combine a list of objects with
* the beats they belong to.
*
* @author Andreas Wenger
*/
@Data public final class BeatEList<T>
implements Iterable<BeatE<T>> {
/** The list of elements, sorted in ascending beat order. */
private List<BeatE<T>> elements;
private static BeatEList<?> emptyList = null;
/**
* Returns a new, empty and mutable {@link BeatEList}.
*/
public static <T> BeatEList<T> beatEList() {
val ret = new BeatEList<T>();
ret.elements = new ArrayList<>(0); //start with a capacity of 0 to save memory
return ret;
}
/** Returns a shared empty, immutable list, that can be used instead of returning a new empty list or null. */
public static <T> BeatEList<T> emptyBeatEList() {
if (emptyList == null) {
emptyList = new BeatEList<MusicElement>();
emptyList.elements = ilist(); //immutable
}
return (BeatEList<T>) emptyList;
}
private BeatEList() {
}
private void checkNotNull(BeatE<? extends T> element) {
if (element.getElement() == null)
throw new IllegalArgumentException("Element must be assigned to a beat");
}
/**
* Gets the first element at the given beat, or null if there is none.
*/
public T get(Fraction beat) {
for (BeatE<T> e : elements) {
if (e.getBeat().equals(beat))
return e.getElement();
}
return null;
}
/**
* Gets the first element at the given beat with the given type, or null if there is none.
* Works only for {@link MusicElement} items.
*/
public T get(Fraction beat, MusicElementType type) {
for (BeatE<T> e : elements) {
if (e.getBeat().equals(beat) && type.is((MusicElement) e.getElement()))
return e.getElement();
}
return null;
}
/**
* Gets all elements at the given beat in a new list, or an empty list if there are none.
*/
public List<T> getAll(Fraction beat) {
List<T> ret = alist();
for (BeatE<T> e : elements) {
int compare = e.getBeat().compareTo(beat);
if (compare == 0)
ret.add(e.getElement());
else if (compare > 0)
break;
}
return ret;
}
/**
* Gets all used beats (each beat only one time).
*/
public List<Fraction> getBeats() {
List<Fraction> ret = alist(elements.size());
Fraction lastBeat = null;
for (BeatE<T> e : elements) {
if (lastBeat == null || false == lastBeat.equals(e.beat)) {
ret.add(e.beat);
lastBeat = e.beat;
}
}
return ret;
}
/**
* Adds the given positioned element.
* If there are already elements at this beat, it is added
* after the existing ones, but nothing is removed.
*/
public void add(BeatE<T> element) {
checkNotNull(element);
for (int i : range(elements)) {
if (element.getBeat().compareTo(elements.get(i).getBeat()) < 0) {
elements.add(i, element);
return;
}
}
elements.add(element);
}
/**
* Adds the given positioned element.
* If there are already elements at this beat, it is added
* after the existing ones, but nothing is removed.
*/
public void add(T element, Fraction beat) {
add(beatE(element, beat));
}
/**
* Adds the given positioned elements.
* If there are already elements at the respective beat, the given elements are added
* after the existing ones, but nothing is removed.
*/
public void addAll(BeatEList<? extends T> list) {
if (list != null)
for (BeatE<? extends T> e : list)
add(e.getElement(), e.getBeat());
}
/**
* Adds the given element at the given beat. If there is already
* a element, it is replaced by the given one and returned (otherwise null).
*/
public T set(BeatE<T> element) {
checkNotNull(element);
for (int i : range(elements)) {
BeatE<T> e = elements.get(i);
int compare = element.getBeat().compareTo(e.getBeat());
if (compare == 0) {
elements.set(i, element);
return e.getElement();
}
else if (compare < 0) {
elements.add(i, element);
return null;
}
}
elements.add(element);
return null;
}
/**
* Adds the given element at the given beat. If there is already
* a element, it is replaced by the given one and returned (otherwise null).
*/
public T set(T element, Fraction beat) {
return set(beatE(element, beat));
}
/**
* Removes the first occurrence of the given element.
* If found, it is returned, otherwise null.
*/
public T remove(T element) {
for (int i : range(elements)) {
if (elements.get(i).getElement() == element) {
return elements.remove(i).getElement();
}
}
return null;
}
/**
* Removes and returns the first element at the given beat (if there is any).
* If not found, null is returned.
*/
public T remove(Fraction beat) {
T e = get(beat);
if (e != null) {
remove(e);
return e;
}
return null;
}
/**
* Gets the first element, or null if the list is empty.
*/
public BeatE<T> getFirst() {
if (elements.size() > 0)
return elements.get(0);
else
return null;
}
/**
* Gets the last element, or null if the list is empty.
*/
public BeatE<T> getLast() {
if (elements.size() > 0)
return elements.get(elements.size() - 1);
else
return null;
}
/**
* Returns the last element at or before the given beat.
* If there is none, null is returned.
*/
public BeatE<T> getLastBefore(Interval endpoint, Fraction beat) {
for (BeatE<T> e : ReverseIterator.reverseIt(elements)) {
if (endpoint.isInInterval(e.getBeat(), beat) == True)
return e;
}
return null;
}
/**
* Gets the number of elements in this list.
*/
public int size() {
return elements.size();
}
@Override public Iterator<BeatE<T>> iterator() {
return elements.iterator();
}
/**
* Gets all data elements.
*/
public IList<T> getDataElements() {
CList<T> ret = clist();
for (BeatE<T> element : elements)
ret.add(element.getElement());
return ret.close();
}
/**
* Gets an {@link Iterable} to iterate in reverse order, from highest to lowest beat.
*/
public Iterable<BeatE<T>> reverseIt() {
return ReverseIterator.reverseIt(elements);
}
/**
* Returns a new {@link BeatEList} with only the elements which appear in the
* given interval relative to the given beat.
*/
public BeatEList<T> filter(Interval interval, Fraction beat) {
BeatEList<T> ret = beatEList();
for (val e : elements)
if (interval.isInInterval(e.beat, beat) == True)
ret.add(e);
return ret;
}
/**
* Creates a mutable copy of this list, that can be further modified.
*/
public BeatEList<T> clone() {
val ret = new BeatEList<T>();
ret.elements = alist(elements);
return ret;
}
}