package com.github.sommeri.less4j.core.compiler.selectors;
import java.util.ArrayList;
import java.util.List;
import com.github.sommeri.less4j.core.ast.ASTCssNode;
import com.github.sommeri.less4j.core.ast.Selector;
import com.github.sommeri.less4j.core.ast.SelectorPart;
import com.github.sommeri.less4j.core.problems.BugHappened;
import com.github.sommeri.less4j.utils.ArraysUtils;
import com.github.sommeri.less4j.utils.ListsComparator;
import com.github.sommeri.less4j.utils.ListsComparator.MatchMarker;
public class SelectorsComparatorForExtend {
private SelectorsComparatorUtils utils = new SelectorsComparatorUtils();
private ListsComparator listsComparator = new ListsComparator();
private ElementSubsequentComparator elementSubsequentComparator;
private SimpleSelectorComparator simpleSelectorComparator;
private SelectorPartComparator selectorPartsComparator;
public SelectorsComparatorForExtend(GeneralComparatorForExtend generalComparator) {
this.elementSubsequentComparator = new ElementSubsequentComparator(generalComparator, utils);
this.simpleSelectorComparator = new SimpleSelectorComparator(elementSubsequentComparator, utils);
this.selectorPartsComparator = new SelectorPartComparator(simpleSelectorComparator);
}
public boolean equals(Selector first, Selector second) {
List<SelectorPart> firstParts = first.getParts();
List<SelectorPart> secondParts = second.getParts();
return listsComparator.equals(firstParts, secondParts, selectorPartsComparator);
}
public boolean contains(Selector lookFor, Selector inSelector) {
List<SelectorPart> lookForParts = lookFor.getParts();
List<SelectorPart> inSelectorParts = inSelector.getParts();
return containsInList(lookForParts, inSelectorParts) || containsEmbedded(lookForParts, inSelectorParts);
}
public Selector replace(Selector lookFor, Selector inSelector, Selector replaceBy) {
List<SelectorPart> lookForParts = lookFor.getParts();
List<SelectorPart> inSelectorParts = ArraysUtils.deeplyClonedList(inSelector.getParts());
Selector result = new Selector(replaceBy.getUnderlyingStructure(), inSelectorParts);
replaceInList(lookForParts, result, replaceBy.getParts());
replaceEmbedded(lookForParts, result, replaceBy.getParts());
return result;
}
private boolean containsInList(List<SelectorPart> lookForParts, List<SelectorPart> inSelectorParts) {
boolean contains = listsComparator.contains(lookForParts, inSelectorParts, selectorPartsComparator);
return contains;
}
private void replaceInList(List<SelectorPart> lookForParts, Selector inSelector, List<SelectorPart> originalReplaceBy) {
List<SelectorPart> inSelectorParts = inSelector.getParts();
SelectorPartsListBuilder builder = new SelectorPartsListBuilder();
List<MatchMarker<SelectorPart>> matches = listsComparator.findMatches(lookForParts, inSelectorParts, selectorPartsComparator);
if (matches.isEmpty() || originalReplaceBy == null || originalReplaceBy.isEmpty())
return ;
SelectorPart lastRemainder = null;
MatchMarker<SelectorPart> previousMatch = null;
for (MatchMarker<SelectorPart> currentMatch : matches) {
SelectorPart firstMatch = currentMatch.getFirst();
SelectorPart lastMatch = currentMatch.getLast();
if (!inSelectorParts.contains(firstMatch) && (previousMatch==null || lastRemainder==null || previousMatch.getLast()!=currentMatch.getFirst())) { //particularly ugly condition
previousMatch = currentMatch;
continue ;
}
boolean prefixNeeded = true;
if (!inSelectorParts.contains(firstMatch) && lastRemainder!=null) { //particularly ugly code
firstMatch = ArraysUtils.last(builder.getParts());
prefixNeeded = false;
}
previousMatch = currentMatch;
List<SelectorPart> replaceBy = ArraysUtils.deeplyClonedList(originalReplaceBy);
if (firstMatch == lastMatch) {
if (lookForParts.size() != 1)
throw new BugHappened("Impossible state happened.", lookForParts.isEmpty() ? null : lookForParts.get(0));
List<SelectorPart> replaceInside = replaceInsidePart(lookForParts.get(0), lastMatch, replaceBy);
ArraysUtils.replace(lastMatch, inSelectorParts, replaceInside);
} else {
if (prefixNeeded)
builder.addUpTo(inSelectorParts, firstMatch);
ArraysUtils.chopFirst(inSelectorParts); // now we are chopping firstMatch
SelectorPart firstRemainder = selectorPartsComparator.cutSuffix(lookForParts.get(0), firstMatch);
if (firstRemainder != null) {
if (prefixNeeded)
builder.add(firstRemainder);
builder.directlyAttach(replaceBy);
} else {
builder.addAll(replaceBy);
}
removeFromParent(ArraysUtils.chopUpTo(inSelectorParts, lastMatch));
ArraysUtils.chopFirst(inSelectorParts); // now we are chopping lastMatch
lastRemainder = selectorPartsComparator.cutPrefix(ArraysUtils.last(lookForParts), lastMatch);
builder.directlyAttachNonNull(lastRemainder);
}
}
builder.addAll(inSelectorParts);
inSelector.setParts(builder.getParts());
inSelector.configureParentToAllChilds();
}
private List<SelectorPart> replaceInsidePart(SelectorPart lookFor, SelectorPart inside, List<SelectorPart> replaceBy) {
SelectorPart[] split = selectorPartsComparator.splitOn(lookFor, inside);
SelectorPartsListBuilder builder = new SelectorPartsListBuilder();
if (split.length > 0) {
builder.directlyAttachNonNull(split[0]);
for (int i = 1; i < split.length; i++) {
builder.directlyAttach(ArraysUtils.deeplyClonedList(replaceBy));
builder.directlyAttachNonNull(split[i]);
}
}
return builder.getParts();
}
private void removeFromParent(List<SelectorPart> removeThese) {
for (SelectorPart elementSubsequent : removeThese) {
elementSubsequent.setParent(null);
}
removeThese.clear();
}
private boolean containsEmbedded(List<SelectorPart> lookFor, List<SelectorPart> inSelectors) {
for (SelectorPart inside : inSelectors) {
if (containsEmbedded(lookFor, inside))
return true;
}
return false;
}
private boolean containsEmbedded(List<SelectorPart> lookFor, ASTCssNode inside) {
for (ASTCssNode kid : inside.getChilds()) {
if (containsEmbedded(lookFor, kid))
return true;
switch (kid.getType()) {
case SELECTOR:
Selector kidSelector = (Selector) kid;
if (containsInList(lookFor, kidSelector.getParts()))
return true;
default:
break;
}
}
return false;
}
private void replaceEmbedded(List<SelectorPart> lookFor, ASTCssNode inside, List<SelectorPart> originalReplaceBy) {
for (ASTCssNode kid : inside.getChilds()) {
replaceEmbedded(lookFor, kid, originalReplaceBy);
switch (kid.getType()) {
case SELECTOR:
Selector kidSelector = (Selector) kid;
replaceInList(lookFor, kidSelector, originalReplaceBy);
default:
break;
}
}
}
}
class SelectorPartsListBuilder {
private SelectorsManipulator manipulator = new SelectorsManipulator();
private List<SelectorPart> newInSelectorParts = new ArrayList<SelectorPart>();
public SelectorPartsListBuilder() {
}
public List<SelectorPart> getParts() {
return newInSelectorParts;
}
public void directlyAttachNonNull(SelectorPart part) {
if (part != null)
directlyAttach(part);
}
public void directlyAttach(SelectorPart part) {
if (newInSelectorParts.isEmpty()) {
add(part);
} else {
SelectorPart tail = ArraysUtils.last(newInSelectorParts);
manipulator.directlyJoinParts(tail, part);
}
}
public void directlyAttach(List<SelectorPart> list) {
if (!newInSelectorParts.isEmpty()) {
SelectorPart tail = ArraysUtils.last(newInSelectorParts);
SelectorPart firstReplaceBy = list.remove(0);
manipulator.directlyJoinParts(tail, firstReplaceBy);
}
addAll(list);
}
public void addAll(List<SelectorPart> list) {
newInSelectorParts.addAll(list);
}
public void addNonNull(SelectorPart part) {
if (part != null)
add(part);
}
public void add(SelectorPart part) {
newInSelectorParts.add(part);
}
public void addUpTo(List<SelectorPart> inSelectorParts, SelectorPart firstMatch) {
newInSelectorParts.addAll(ArraysUtils.chopUpTo(inSelectorParts, firstMatch));
}
@Override
public String toString() {
return newInSelectorParts.toString();
}
}