package com.github.sommeri.less4j.core.compiler.stages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.sommeri.less4j.core.ast.Body;
import com.github.sommeri.less4j.core.ast.Declaration;
import com.github.sommeri.less4j.core.ast.Expression;
import com.github.sommeri.less4j.core.ast.ListExpression;
import com.github.sommeri.less4j.core.ast.ListExpressionOperator;
import com.github.sommeri.less4j.core.ast.ListExpressionOperator.Operator;
import com.github.sommeri.less4j.core.compiler.expressions.ExpressionManipulator;
import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree;
import com.github.sommeri.less4j.utils.ArraysUtils;
/**
* Preconditions:
* 1.) properties names must be interpolated,
* 2.) rulesets bodies must be fully solved.
*
*/
public class PropertiesMerger extends TreeDeclarationsVisitor {
private Map<String, MergedData> mergingProperties = new HashMap<String, MergedData>();
private ASTManipulator manipulator = new ASTManipulator();
private ExpressionManipulator expressionManipulator = new ExpressionManipulator();
public PropertiesMerger() {
}
protected void applyToDeclaration(Declaration declaration) {
if (declaration.isMerging())
addToPrevious(declaration);
}
private void addToPrevious(Declaration declaration) {
if (declaration.getExpression() == null)
return;
String key = toMergingPropertiesKey(declaration);
if (mergingProperties.containsKey(key)) {
MergedData mergedDeclaration = mergingProperties.get(key);
addToMergeData(mergedDeclaration, declaration.getExpression(), declaration.getMergeOperator());
manipulator.removeFromBody(declaration);
} else {
MergedData mergedData = createMergeData(declaration);
mergingProperties.put(key, mergedData);
}
}
private MergedData createMergeData(Declaration declaration) {
Expression expression = declaration.getExpression();
Expression important = null;
if (expressionManipulator.isImportant(expression)) {
important = expressionManipulator.cutRightmostListedExpression(expression);
}
MergedData mergedData = new MergedData(declaration, important);
mergedData.add(expression);
return mergedData;
}
private void addToMergeData(MergedData mergeData, Expression expression, Operator merge) {
if (mergeData.isImportant()) {
expressionManipulator.cutRightmostListedExpression(expression);
}
if (mergeData.getOperator() != merge) {
Expression mergeExpressions = mergeData.mergeExpressions();
mergeData.setOperator(merge);
mergeData.replaceExpressions(mergeExpressions);
}
mergeData.add(expression);
}
private String toMergingPropertiesKey(Declaration declaration) {
String cssPropertyName = declaration.getNameAsString();
boolean important = expressionManipulator.isImportant(declaration.getExpression());
return cssPropertyName + " " + important;
}
protected void enteringBody(Body node) {
mergingProperties = new HashMap<String, MergedData>();
}
protected void leavingBody(Body node) {
for (MergedData data : mergingProperties.values()) {
Declaration declaration = data.getDeclaration();
HiddenTokenAwareTree underlying = declaration.getUnderlyingStructure();
Expression mergedExpressions = data.mergeExpressions();
if (data.isImportant()) {
ListExpression list = bundleInSpaceSeparatedList(underlying, mergedExpressions);
list.addExpression(data.getImportance());
list.configureParentToAllChilds();
mergedExpressions = list;
}
declaration.setExpression(mergedExpressions);
mergedExpressions.setParent(declaration);
}
}
private ListExpression bundleInSpaceSeparatedList(HiddenTokenAwareTree underlying, Expression mergedExpressions) {
if (expressionManipulator.isSpaceSeparatedList(mergedExpressions))
return (ListExpression) mergedExpressions;
List<Expression> iExpressions = ArraysUtils.asList(mergedExpressions);
ListExpressionOperator space = new ListExpressionOperator(underlying, ListExpressionOperator.Operator.EMPTY_OPERATOR);
return new ListExpression(underlying, iExpressions, space);
}
}
class MergedData {
private final Declaration declaration;
private final Expression importance;
private Operator operator;
private List<Expression> mergedExpressions = new ArrayList<Expression>();
public MergedData(Declaration declaration, Expression importance) {
super();
this.declaration = declaration;
this.importance = importance;
this.operator = declaration.getMergeOperator();
}
public void replaceExpressions(Expression expression) {
mergedExpressions = new ArrayList<Expression>();
mergedExpressions.add(expression);
}
public Expression mergeExpressions() {
HiddenTokenAwareTree underlying = declaration.getUnderlyingStructure();
List<Expression> expressions = getMergedExpressions();
if (expressions.size() == 1)
return expressions.get(0);
ListExpressionOperator.Operator mergeOperator = getOperator();
ListExpression result = new ListExpression(underlying, expressions, new ListExpressionOperator(underlying, mergeOperator));
result.configureParentToAllChilds();
return result;
}
public boolean isImportant() {
return importance != null;
}
public void add(Expression expression) {
mergedExpressions.add(expression);
}
public Declaration getDeclaration() {
return declaration;
}
public Expression getImportance() {
return importance;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
public List<Expression> getMergedExpressions() {
return mergedExpressions;
}
}