package com.github.czyzby.lml.parser.impl.tag.macro;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.lml.parser.LmlParser;
import com.github.czyzby.lml.parser.tag.LmlTag;
/** As opposed to a regular for-each macro, this iterates over passed arrays as a nested for loop rather than all at
* once. This: <blockquote>
*
* <pre>
* <:nested value0=array0 value1=array1>
* <!-- do something, index: {nested:index}, values: {value0}, {value1} -->
* </:nested>
* </pre>
*
* </blockquote>...is "equivalent" to this Java syntax: <blockquote>
*
* <pre>
* int index = 0;
* for (Type0 value0 : array0) {
* for (Type1 value1 : array1) {
* // do something
* index++;
* }
* }
* </pre>
*
* </blockquote>
*
* This macro should be preferred over two actually nested for-each macros when you need access to a global iteration
* index. This is also arguably faster to parse. For example: <blockquote>
*
* <pre>
* <:nested who=he;she;it what=is;was;does>
* Rule {nested:index}: {who} {what}.
* </:nested>
* </pre>
*
* </blockquote>...prints:
* <ul>
* <li>Rule 0: he is.
* <li>Rule 1: he was.
* <li>Rule 2: he does.
* <li>Rule 3: she is.
* <li>Rule 4: she was.
* <li>Rule 5: she does.
* <li>Rule 6: it is.
* <li>Rule 7: it was.
* <li>Rule 8: it does.
* </ul>
* Total runs amount is equal to multiplied sizes of passed arrays.
* <p>
* When using default DTD settings, "element" is the only recognized macro attribute. As this does not allow you to
* create nested loops or iterate over multiple arrays at once, you might want to modify DTD files manually.
*
* @author MJ */
public class NestedForEachLmlMacroTag extends AbstractLoopLmlMacroTag {
private final IntArray indexes;
private final Array<String> argumentNames;
private final Array<String[]> values;
private int currentIndex;
public NestedForEachLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) {
super(parser, parentTag, rawTagData);
final int argumentsAmount = GdxArrays.sizeOf(getAttributes());
if (argumentsAmount <= 0) {
parser.throwErrorIfStrict("Nested for each macro needs array attributes to iterate over.");
indexes = null;
argumentNames = null;
values = null;
} else {
indexes = new IntArray(argumentsAmount);
argumentNames = GdxArrays.newArray(argumentsAmount);
values = GdxArrays.newArray(argumentsAmount);
fillArrays();
}
}
private void fillArrays() {
for (final Entry<String, String> attribute : getNamedAttributes()) {
indexes.add(0);
argumentNames.add(attribute.key);
final String[] array = getParser().parseArray(attribute.value, getActor());
values.add(array);
}
}
@Override
protected boolean supportsNamedAttributes() {
return true;
}
@Override
protected boolean hasNext() {
if (GdxArrays.isEmpty(indexes)) {
return false;
}
return indexes.first() < values.first().length;
}
@Override
protected int getIndex() {
return currentIndex;
}
@Override
protected void next(final ObjectMap<String, String> arguments) {
for (int arrayId = 0, length = indexes.size; arrayId < length; arrayId++) {
arguments.put(argumentNames.get(arrayId), values.get(arrayId)[indexes.get(arrayId)]);
}
incrementIndex(indexes.size - 1);
currentIndex++;
}
/** @param arrayId index of the array argument to increment. */
private void incrementIndex(final int arrayId) {
indexes.set(arrayId, indexes.get(arrayId) + 1);
if (arrayId > 0 && indexes.get(arrayId) == values.get(arrayId).length) {
indexes.set(arrayId, 0);
incrementIndex(arrayId - 1);
}
}
@Override
public String[] getExpectedAttributes() {
return new String[] { ELEMENT_ATTRIBUTE };
}
}