// sass.hpp must go before all system headers to get the
// __EXTENSIONS__ fix on Solaris.
#include "sass.hpp"

#include "ast.hpp"

namespace Sass {

  // ##########################################################################
  // Returns the contents of a [SelectorList] that matches only 
  // elements that are matched by both [complex1] and [complex2].
  // If no such list can be produced, returns `null`.
  // ##########################################################################
  // ToDo: fine-tune API to avoid unnecessary wrapper allocations
  // ##########################################################################
  sass::vector<sass::vector<SelectorComponentObj>> unifyComplex(
    const sass::vector<sass::vector<SelectorComponentObj>>& complexes)
  {

    SASS_ASSERT(!complexes.empty(), "Can't unify empty list");
    if (complexes.size() == 1) return complexes;

    CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[unify]"));
    for (auto complex : complexes) {
      SelectorComponentObj base = complex.back();
      if (CompoundSelector * comp = base->getCompound()) {
        if (unifiedBase->empty()) {
          unifiedBase->concat(comp);
        }
        else {
          for (SimpleSelectorObj simple : comp->elements()) {
            unifiedBase = simple->unifyWith(unifiedBase);
            if (unifiedBase.isNull()) return {};
          }
        }
      }
      else {
        return {};
      }
    }

    sass::vector<sass::vector<SelectorComponentObj>> complexesWithoutBases;
    for (size_t i = 0; i < complexes.size(); i += 1) {
      sass::vector<SelectorComponentObj> sel = complexes[i];
      sel.pop_back(); // remove last item (base) from the list
      complexesWithoutBases.push_back(std::move(sel));
    }

    complexesWithoutBases.back().push_back(unifiedBase);

    return weave(complexesWithoutBases);

  }
  // EO unifyComplex

  // ##########################################################################
  // Returns a [CompoundSelector] that matches only elements
  // that are matched by both [compound1] and [compound2].
  // If no such selector can be produced, returns `null`.
  // ##########################################################################
  CompoundSelector* CompoundSelector::unifyWith(CompoundSelector* rhs)
  {
    if (empty()) return rhs;
    CompoundSelectorObj unified = SASS_MEMORY_COPY(rhs);
    for (const SimpleSelectorObj& sel : elements()) {
      unified = sel->unifyWith(unified);
      if (unified.isNull()) break;
    }
    return unified.detach();
  }
  // EO CompoundSelector::unifyWith(CompoundSelector*)

  // ##########################################################################
  // Returns the compoments of a [CompoundSelector] that matches only elements
  // matched by both this and [compound]. By default, this just returns a copy
  // of [compound] with this selector added to the end, or returns the original
  // array if this selector already exists in it. Returns `null` if unification
  // is impossible—for example, if there are multiple ID selectors.
  // ##########################################################################
  // This is implemented in `selector/simple.dart` as `SimpleSelector::unify`
  // ##########################################################################
  CompoundSelector* SimpleSelector::unifyWith(CompoundSelector* rhs)
  {

    if (rhs->length() == 1) {
      if (rhs->get(0)->is_universal()) {
        CompoundSelector* this_compound = SASS_MEMORY_NEW(CompoundSelector, pstate());
        this_compound->append(SASS_MEMORY_COPY(this));
        CompoundSelector* unified = rhs->get(0)->unifyWith(this_compound);
        if (unified == nullptr || unified != this_compound) delete this_compound;
        return unified;
      }
    }
    for (const SimpleSelectorObj& sel : rhs->elements()) {
      if (*this == *sel) {
        return rhs;
      }
    }

    CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, rhs->pstate());

    bool addedThis = false;
    for (auto simple : rhs->elements()) {
      // Make sure pseudo selectors always come last.
      if (!addedThis && simple->getPseudoSelector()) {
        result->append(this);
        addedThis = true;
      }
      result->append(simple);
    }

    if (!addedThis) {
      result->append(this);
    }
    return result.detach();

  }
  // EO SimpleSelector::unifyWith(CompoundSelector*)

  // ##########################################################################
  // This is implemented in `selector/type.dart` as `PseudoSelector::unify`
  // ##########################################################################
  CompoundSelector* TypeSelector::unifyWith(CompoundSelector* rhs)
  {
    if (rhs->empty()) {
      rhs->append(this);
      return rhs;
    }
    TypeSelector* type = Cast<TypeSelector>(rhs->at(0));
    if (type != nullptr) {
      SimpleSelector* unified = unifyWith(type);
      if (unified == nullptr) {
        return nullptr;
      }
      rhs->elements()[0] = unified;
    }
    else if (!is_universal() || (has_ns_ && ns_ != "*")) {
      rhs->insert(rhs->begin(), this);
    }
    return rhs;
  }

  // ##########################################################################
  // This is implemented in `selector/id.dart` as `PseudoSelector::unify`
  // ##########################################################################
  CompoundSelector* IDSelector::unifyWith(CompoundSelector* rhs)
  {
    for (const SimpleSelector* sel : rhs->elements()) {
      if (const IDSelector* id_sel = Cast<IDSelector>(sel)) {
        if (id_sel->name() != name()) return nullptr;
      }
    }
    return SimpleSelector::unifyWith(rhs);
  }

  // ##########################################################################
  // This is implemented in `selector/pseudo.dart` as `PseudoSelector::unify`
  // ##########################################################################
  CompoundSelector* PseudoSelector::unifyWith(CompoundSelector* compound)
  {

    if (compound->length() == 1 && compound->first()->is_universal()) {
      // std::cerr << "implement universal pseudo\n";
    }

    for (const SimpleSelectorObj& sel : compound->elements()) {
      if (*this == *sel) {
        return compound;
      }
    }

    CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, compound->pstate());

    bool addedThis = false;
    for (auto simple : compound->elements()) {
      // Make sure pseudo selectors always come last.
      if (PseudoSelectorObj pseudo = simple->getPseudoSelector()) {
        if (pseudo->isElement()) {
          // A given compound selector may only contain one pseudo element. If
          // [compound] has a different one than [this], unification fails.
          if (isElement()) {
            return {};
          }
          // Otherwise, this is a pseudo selector and
          // should come before pseduo elements.
          result->append(this);
          addedThis = true;
        }
      }
      result->append(simple);
    }

    if (!addedThis) {
      result->append(this);
    }

    return result.detach();

  }
  // EO PseudoSelector::unifyWith(CompoundSelector*

  // ##########################################################################
  // This is implemented in `extend/functions.dart` as `unifyUniversalAndElement`
  // Returns a [SimpleSelector] that matches only elements that are matched by
  // both [selector1] and [selector2], which must both be either [UniversalSelector]s
  // or [TypeSelector]s. If no such selector can be produced, returns `null`.
  // Note: libsass handles universal selector directly within the type selector
  // ##########################################################################
  SimpleSelector* TypeSelector::unifyWith(const SimpleSelector* rhs)
  {
    bool rhs_ns = false;
    if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) {
      if (!is_universal_ns()) {
        return nullptr;
      }
      rhs_ns = true;
    }
    bool rhs_name = false;
    if (!(name_ == rhs->name() || rhs->is_universal())) {
      if (!(is_universal())) {
        return nullptr;
      }
      rhs_name = true;
    }
    if (rhs_ns) {
      ns(rhs->ns());
      has_ns(rhs->has_ns());
    }
    if (rhs_name) name(rhs->name());
    return this;
  }
  // EO TypeSelector::unifyWith(const SimpleSelector*)

  // ##########################################################################
  // Unify two complex selectors. Internally calls `unifyComplex`
  // and then wraps the result in newly create ComplexSelectors.
  // ##########################################################################
  SelectorList* ComplexSelector::unifyWith(ComplexSelector* rhs)
  {
    SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate());
    sass::vector<sass::vector<SelectorComponentObj>> rv =
       unifyComplex({ elements(), rhs->elements() });
    for (sass::vector<SelectorComponentObj> items : rv) {
      ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate());
      sel->elements() = std::move(items);
      list->append(sel);
    }
    return list.detach();
  }
  // EO ComplexSelector::unifyWith(ComplexSelector*)

  // ##########################################################################
  // only called from the sass function `selector-unify`
  // ##########################################################################
  SelectorList* SelectorList::unifyWith(SelectorList* rhs)
  {
    SelectorList* slist = SASS_MEMORY_NEW(SelectorList, pstate());
    // Unify all of children with RHS's children,
    // storing the results in `unified_complex_selectors`
    for (ComplexSelectorObj& seq1 : elements()) {
      for (ComplexSelectorObj& seq2 : rhs->elements()) {
        if (SelectorListObj unified = seq1->unifyWith(seq2)) {
          std::move(unified->begin(), unified->end(),
            std::inserter(slist->elements(), slist->end()));
        }
      }
    }
    return slist;
  }
  // EO SelectorList::unifyWith(SelectorList*)

  // ##########################################################################
  // ##########################################################################

}
