/*
 * Copyright 2003-2012 JetBrains s.r.o.
 *
 * 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 jetbrains.mps.newTypesystem.rules;

import jetbrains.mps.languageScope.LanguageScope;
import jetbrains.mps.smodel.NodeReadAccessCasterInEditor;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.Pair;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * User: fyodor
 * Date: 8/28/12
 */
public abstract class SingleTermRules<K> {

  private ConcurrentHashMap<Object, Set<K>> myCachedRules = new ConcurrentHashMap<Object, Set<K>>();

  public Set<K> lookupRules(SNode term) {
    final LanguageScope langScope = LanguageScope.getCurrent();

    final SAbstractConcept concept = term.getConcept();
    final Object compoundKey = new Pair<Object, SAbstractConcept>(langScope, concept);

    Set<K> cachedRules = myCachedRules.get(compoundKey);
    if (cachedRules != null) {
      return cachedRules;
    }
    return NodeReadAccessCasterInEditor.runReadTransparentAction(new Computable<Set<K>>() {
      @Override
      public Set<K> compute() {
        Set<K> computedRules = computeRules(concept, langScope);
        myCachedRules.put(compoundKey, computedRules);

        return computedRules;
      }
    });
  }

  public void purgeCache() {
    myCachedRules.clear();
  }

  private Set<K> computeRules(SAbstractConcept concept, LanguageScope langScope) {
    LinkedHashSet<K> result = new LinkedHashSet<K>();

    LinkedList<SAbstractConcept> queue = new LinkedList<SAbstractConcept>();
    queue.add(concept);

    while (!queue.isEmpty()) {
      SAbstractConcept nextConceptFQName = queue.remove();
      boolean overriding = false;
      for (K applicableRule : allForConcept(nextConceptFQName, langScope)) {
        overriding |= isOverriding(applicableRule);
        result.add(applicableRule);
      }
      if (!overriding) {
        queue.addAll(getParents(nextConceptFQName));
      }
    }

    return Collections.unmodifiableSet(result);
  }

  abstract protected List<SAbstractConcept> getParents(SAbstractConcept nextConcept);

  abstract protected Iterable<K> allForConcept(SAbstractConcept concept, LanguageScope scope);

  abstract protected boolean isOverriding(K rule);

}
