/*
 * Copyright 2011 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.vaadin.sass.selector;

import org.w3c.css.sac.CombinatorCondition;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.ConditionFactory;
import org.w3c.css.sac.ConditionalSelector;
import org.w3c.css.sac.DescendantSelector;
import org.w3c.css.sac.ElementSelector;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SelectorFactory;
import org.w3c.css.sac.SelectorList;
import org.w3c.css.sac.SiblingSelector;
import org.w3c.css.sac.SimpleSelector;
import org.w3c.flute.parser.selectors.AndConditionImpl;
import org.w3c.flute.parser.selectors.AttributeConditionImpl;
import org.w3c.flute.parser.selectors.ChildSelectorImpl;
import org.w3c.flute.parser.selectors.ClassConditionImpl;
import org.w3c.flute.parser.selectors.ConditionFactoryImpl;
import org.w3c.flute.parser.selectors.DirectAdjacentSelectorImpl;
import org.w3c.flute.parser.selectors.ElementSelectorImpl;
import org.w3c.flute.parser.selectors.IdConditionImpl;
import org.w3c.flute.parser.selectors.PseudoClassConditionImpl;
import org.w3c.flute.parser.selectors.PseudoElementSelectorImpl;
import org.w3c.flute.parser.selectors.SelectorFactoryImpl;

import com.vaadin.sass.parser.SelectorListImpl;

public class SelectorUtil {

    public static String toString(CompositeSelector compositeSelector) {
        StringBuilder builder = new StringBuilder();
        if (compositeSelector != null) {
            if (compositeSelector.getFirst() != null) {
                builder.append(toString(compositeSelector.getFirst())).append(
                        " ");
            }
            if (compositeSelector.getSecond() != null) {
                builder.append(toString(compositeSelector.getSecond()));
            }
        }
        return builder.toString();
    }

    public static String toString(SelectorList selectorList) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < selectorList.getLength(); i++) {
            String selectorString = toString(selectorList.item(i));
            stringBuilder.append(selectorString);
            if (selectorList.getLength() > i + 1) {
                stringBuilder.append(", ");
            }
        }
        return stringBuilder.toString();
    }

    public static String toString(Selector selector) {
        if (selector == null) {
            return "";
        }
        if (selector.getSelectorType() == Selector.SAC_CONDITIONAL_SELECTOR) {
            StringBuilder stringBuilder = new StringBuilder();
            ConditionalSelector conditionalSelector = (ConditionalSelector) selector;
            String simpleSelectorString = toString(conditionalSelector
                    .getSimpleSelector());
            if (simpleSelectorString != null) {
                stringBuilder.append(simpleSelectorString);
            }
            String conditionString = getConditionString(conditionalSelector
                    .getCondition());
            stringBuilder.append(conditionString);
            return stringBuilder.toString();
        } else if (selector.getSelectorType() == Selector.SAC_DESCENDANT_SELECTOR) {
            return getDecendantSelectorString((DescendantSelector) selector,
                    " ");
        } else if (selector.getSelectorType() == Selector.SAC_CHILD_SELECTOR) {
            DescendantSelector childSelector = (DescendantSelector) selector;
            String seperator = " > ";
            if (childSelector.getSimpleSelector() instanceof PseudoElementSelectorImpl) {
                seperator = "::";
            }
            return getDecendantSelectorString((DescendantSelector) selector,
                    seperator);
        } else if (selector.getSelectorType() == Selector.SAC_ELEMENT_NODE_SELECTOR) {
            ElementSelectorImpl elementSelector = (ElementSelectorImpl) selector;
            return elementSelector.getLocalName() == null ? ""
                    : elementSelector.getLocalName();
        } else if (selector.getSelectorType() == Selector.SAC_DIRECT_ADJACENT_SELECTOR) {
            DirectAdjacentSelectorImpl directAdjacentSelector = (DirectAdjacentSelectorImpl) selector;
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder
                    .append(toString(directAdjacentSelector.getSelector()));
            stringBuilder.append(" + ");
            stringBuilder.append(toString(directAdjacentSelector
                    .getSiblingSelector()));
            return stringBuilder.toString();
        } else if (selector.getSelectorType() == Selector.SAC_PSEUDO_ELEMENT_SELECTOR) {
            PseudoElementSelectorImpl pseudoElementSelectorImpl = (PseudoElementSelectorImpl) selector;
            return pseudoElementSelectorImpl.getLocalName();
        } else if (selector.getSelectorType() == CompositeSelector.SCSS_COMPOSITE_SELECTOR) {
            return toString((CompositeSelector) selector);
        } else {
            System.out.println("SU !Unknown selector type, type: "
                    + selector.getSelectorType() + ", " + selector.toString());
        }
        return "";
    }

    private static String getDecendantSelectorString(
            DescendantSelector selector, String separator) {
        StringBuilder stringBuilder = new StringBuilder();
        String ancestor = toString(selector.getAncestorSelector());
        String simpleSelector = toString(selector.getSimpleSelector());
        stringBuilder.append(ancestor);
        stringBuilder.append(separator);
        stringBuilder.append(simpleSelector);
        return stringBuilder.toString();
    }

    private static String getConditionString(Condition condition) {
        short conditionType = condition.getConditionType();
        if (conditionType == Condition.SAC_CLASS_CONDITION) {
            ClassConditionImpl classCondition = (ClassConditionImpl) condition;
            return "." + classCondition.getValue();
        } else if (conditionType == Condition.SAC_ID_CONDITION) {
            IdConditionImpl idCondition = (IdConditionImpl) condition;
            return "#" + idCondition.getValue();
        } else if (conditionType == Condition.SAC_AND_CONDITION) {
            AndConditionImpl andCondition = (AndConditionImpl) condition;
            return getConditionString(andCondition.getFirstCondition())
                    + getConditionString(andCondition.getSecondCondition());
        } else if (conditionType == Condition.SAC_ATTRIBUTE_CONDITION) {
            AttributeConditionImpl attributeCondition = (AttributeConditionImpl) condition;
            StringBuilder string = new StringBuilder();
            string.append('[');
            string.append(attributeCondition.getLocalName());
            String value = attributeCondition.getValue();
            if ("true".equals(value) || "false".equals(value)) {
                string.append("=").append(value).append(']');
            } else {
                string.append("=\"");
                string.append(attributeCondition.getValue());
                string.append("\"]");
            }
            return string.toString();
        } else if (conditionType == Condition.SAC_PSEUDO_CLASS_CONDITION) {
            PseudoClassConditionImpl pseudoClassCondition = (PseudoClassConditionImpl) condition;
            return ":" + pseudoClassCondition.getValue();
        } else {
            System.out.println("CU !condition type not identified, type: "
                    + conditionType + ", " + condition.toString());
            return "";
        }
    }

    public static boolean hasParentSelector(SelectorList selectorList) {
        String selectorString = toString(selectorList);
        return selectorString.contains("&");
    }

    public static SelectorList createNewSelectorListFromAnOldOneWithSomPartReplaced(
            SelectorList oldList, String toBeReplacedSelectorName,
            SelectorList candidateSelectorList) throws Exception {
        if (candidateSelectorList.getLength() != 1) {
            throw new Exception("Candidate selector should not be a list");
        }
        if (!(candidateSelectorList.item(0) instanceof SimpleSelector)) {
            throw new Exception(
                    "Candidate selector should only be a SimpleSelector");
        }
        SelectorListImpl newSelectorList = new SelectorListImpl();
        SimpleSelector candidateSelector = (SimpleSelector) candidateSelectorList
                .item(0);
        for (int i = 0; i < oldList.getLength(); i++) {
            Selector selector = oldList.item(i);
            newSelectorList.addSelector(createSelectorWithSomePartReplaced(
                    selector, toBeReplacedSelectorName, candidateSelector));
        }
        return newSelectorList;
    }

    private static Selector createSelectorWithSomePartReplaced(
            Selector selector, String toBeReplacedSelectorName,
            SimpleSelector candidateSelector) {
        if (!toString(selector).contains(toBeReplacedSelectorName)) {
            return selector;
        }
        SelectorFactory factory = new SelectorFactoryImpl();
        if (selector instanceof SimpleSelector) {
            return createSimpleSelectorWithSomePartReplaced(
                    (SimpleSelector) selector, toBeReplacedSelectorName,
                    candidateSelector);
        } else if (selector instanceof DescendantSelector) {
            DescendantSelector descendantSelector = (DescendantSelector) selector;
            Selector ancestor = descendantSelector.getAncestorSelector();
            SimpleSelector simpleSelector = descendantSelector
                    .getSimpleSelector();
            return factory.createDescendantSelector(
                    createSelectorWithSomePartReplaced(ancestor,
                            toBeReplacedSelectorName, candidateSelector),
                    createSimpleSelectorWithSomePartReplaced(simpleSelector,
                            toBeReplacedSelectorName, candidateSelector));
        } else if (selector instanceof DirectAdjacentSelectorImpl) {
            SiblingSelector siblingSelector = (SiblingSelector) selector;
            Selector ancestor = siblingSelector.getSelector();
            SimpleSelector simpleSelector = siblingSelector
                    .getSiblingSelector();
            return factory.createDirectAdjacentSelector(
                    Selector.SAC_DIRECT_ADJACENT_SELECTOR, ancestor,
                    simpleSelector);
        } else if (selector instanceof CompositeSelector) {
            CompositeSelector compositeSelector = (CompositeSelector) selector;
            Selector first = compositeSelector.getFirst();
            Selector second = compositeSelector.getSecond();
            return new CompositeSelector(createSelectorWithSomePartReplaced(
                    first, toBeReplacedSelectorName, candidateSelector),
                    createSelectorWithSomePartReplaced(second,
                            toBeReplacedSelectorName, candidateSelector));
        }
        return null;
    }

    private static SimpleSelector createSimpleSelectorWithSomePartReplaced(
            SimpleSelector simpleSelector, String toBeReplacedSelectorName,
            SimpleSelector candidateSelector) {
        if (simpleSelector == null
                || !toString(simpleSelector).contains(toBeReplacedSelectorName)) {
            return simpleSelector;
        }
        if (simpleSelector instanceof ElementSelector
                && candidateSelector instanceof ElementSelector) {
            return candidateSelector;
        }
        if (simpleSelector instanceof ConditionalSelector) {
            return createConditionSelectorWithSomePartReplaced(
                    (ConditionalSelector) simpleSelector,
                    toBeReplacedSelectorName, candidateSelector);
        }
        return simpleSelector;
    }

    private static ConditionalSelector createConditionSelectorWithSomePartReplaced(
            ConditionalSelector oldConditionSelector,
            String toBeReplacedSelectorName, SimpleSelector candidateSelector) {
        if (oldConditionSelector == null
                || !toString(oldConditionSelector).contains(
                        toBeReplacedSelectorName)) {
            return oldConditionSelector;
        }
        SelectorFactory selectorFactory = new SelectorFactoryImpl();
        if (candidateSelector instanceof ElementSelector) {
            return selectorFactory.createConditionalSelector(candidateSelector,
                    oldConditionSelector.getCondition());
        }
        if (candidateSelector instanceof ConditionalSelector) {
            // TODO some cases not covered.
            ConditionalSelector candidateConditionSelector = (ConditionalSelector) candidateSelector;
            Condition newCondition = createConditionWithSomePartReplaced(
                    oldConditionSelector.getCondition(),
                    toBeReplacedSelectorName,
                    candidateConditionSelector.getCondition());
            return selectorFactory.createConditionalSelector(
                    oldConditionSelector.getSimpleSelector(), newCondition);
        }
        return oldConditionSelector;
    }

    private static Condition createConditionWithSomePartReplaced(
            Condition oldCondition, String toBeReplaced, Condition candidate) {
        if (oldCondition == null
                || !getConditionString(oldCondition).contains(toBeReplaced)) {
            return oldCondition;
        }
        if (oldCondition.getConditionType() == Condition.SAC_AND_CONDITION) {
            ConditionFactory conditionFactory = new ConditionFactoryImpl();
            CombinatorCondition oldCombinatorCondition = (CombinatorCondition) oldCondition;
            Condition newFirstCondition = createConditionWithSomePartReplaced(
                    oldCombinatorCondition.getFirstCondition(), toBeReplaced,
                    candidate);
            Condition newSecondCondition = createConditionWithSomePartReplaced(
                    oldCombinatorCondition.getSecondCondition(), toBeReplaced,
                    candidate);
            return conditionFactory.createAndCondition(newFirstCondition,
                    newSecondCondition);
        } else {
            return candidate;
        }
    }

    public static boolean equals(Selector one, Selector another) {
        return one == null ? another == null : toString(one).equals(
                toString(another));
    }

    public static Selector createSelectorAndreplaceSelectorVariableWithValue(
            Selector selector, String variable, String value) throws Exception {

        SelectorFactoryImpl factory = new SelectorFactoryImpl();

        ElementSelector es = factory.createElementSelector(
                null,
                ((ElementSelector) selector).getLocalName().replaceAll(
                        variable, value));

        if (selector instanceof ConditionalSelector) {
            return factory.createConditionalSelector(es,
                    ((ConditionalSelector) selector).getCondition());
        } else if (selector instanceof DescendantSelector) {
            return factory.createDescendantSelector(es,
                    ((DescendantSelector) selector).getSimpleSelector());
        } else if (selector instanceof ChildSelectorImpl) {
            return factory.createChildSelector(es,
                    ((DescendantSelector) selector).getSimpleSelector());
        } else {
            throw new Exception("Invalid selector type");
        }
    }
}
