/*
 * 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.context.typechecking;

import gnu.trove.THashSet;
import jetbrains.mps.errors.IErrorReporter;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus;
import jetbrains.mps.lang.typesystem.runtime.SubstituteType_Runtime;
import jetbrains.mps.newTypesystem.context.component.SimpleTypecheckingComponent;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.smodel.IOperationContext;
import jetbrains.mps.typesystem.inference.TypeSubstitution;
import jetbrains.mps.util.Cancellable;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * User: fyodor
 * Date: 11/12/12
 */
public abstract class BaseTypechecking<STATE extends State, COMP extends SimpleTypecheckingComponent> {

  protected final SNode myRootNode;
  private final STATE myState;
  private final COMP myTypecheckingComponent;

  public BaseTypechecking(SNode node, STATE state) {
    myRootNode = node;
    myState = state;
    myTypecheckingComponent = createTypecheckingComponent();
  }

  protected STATE getState() {
    return myState;
  }

  @SuppressWarnings("unchecked")
  protected COMP createTypecheckingComponent() {
    return (COMP) new SimpleTypecheckingComponent<STATE>(getState(), this);
  }

  public SNode getNode() {
    return myRootNode;
  }

  public COMP getTypecheckingComponent() {
    return myTypecheckingComponent;
  }

  protected SNode computeTypesForNode_special(SNode initialNode, Collection<SNode> givenAdditionalNodes) {
    SNode result = getTypecheckingComponent().computeTypesForNode_special(initialNode, givenAdditionalNodes);
    return result;
  }

  public SNode computeTypesForNodeDuringGeneration(SNode initialNode) {
    return computeTypesForNode_special(initialNode, Collections.<SNode>emptyList());
  }

  public SNode computeTypesForNodeDuringResolving(SNode initialNode) {
    return computeTypesForNode_special(initialNode, Collections.<SNode>emptyList());
  }

  public SNode computeTypesForNodeInferenceMode(SNode initialNode) {
    return computeTypesForNode_special(initialNode, Collections.<SNode>emptyList());
  }

  @NotNull
  public List<IErrorReporter> getErrors(SNode node) {
    Map<SNode, List<IErrorReporter>> nodesToErrorsMap = getTypecheckingComponent().getNodesToErrorsMap();

    List<IErrorReporter> result = new ArrayList<IErrorReporter>(4);
    List<IErrorReporter> iErrorReporters = nodesToErrorsMap.get(node);
    if (iErrorReporters != null) {
      result.addAll(iErrorReporters);
    }
    return result;
  }

  public void addNodeToFrontier(SNode node) {
    getTypecheckingComponent().addNodeToFrontier(node);
  }

  public void dispose() {
  }

  public void computeTypes(boolean refreshTypes) {
    getTypecheckingComponent().computeTypes(refreshTypes);
  }


  public void setCheckedTypesystem() {
    getTypecheckingComponent().setChecked();
  }

  public Set<Pair<SNode, List<IErrorReporter>>> getNodesWithErrors(boolean typesystemErrors) {
    Map<SNode, List<IErrorReporter>> nodesToErrorsMap = getTypecheckingComponent().getNodesToErrorsMap();
    Set<SNode> keySet = new THashSet<SNode>(nodesToErrorsMap.keySet());

    Set<Pair<SNode, List<IErrorReporter>>> result = new THashSet<Pair<SNode, List<IErrorReporter>>>(1);
    for (SNode key : keySet) {
      List<IErrorReporter> reporters = nodesToErrorsMap.get(key);
      if (!reporters.isEmpty()) {
        if (key.getContainingRoot() == null) {
          /*  LOG.warn("Type system reports error for node without containing root. Node: " + key);
                    for (IErrorReporter reporter : reporters) {
                      LOG.warn("This error was reported from: model: " + reporter.getRuleModel() + " id: " + reporter.getRuleId());
                    }     */
          continue;
        }
        result.add(new Pair<SNode, List<IErrorReporter>>(key, reporters));
      }
    }
    return result;
  }

  public boolean isChecked(boolean considerNonTypeSystemRules) {
    return getTypecheckingComponent().isChecked();
  }

  public void applyNonTypesystemRulesToRoot(TypeCheckingContext typeCheckingContext) {
    applyNonTypesystemRulesToRoot(typeCheckingContext, Cancellable.NEVER);
  }

  /**
   * Should return true iff the operation has succeeded and was not cancelled.
   */
  public abstract boolean applyNonTypesystemRulesToRoot(TypeCheckingContext typeCheckingContext, Cancellable c);

  /**
   * Returns the list of all node attributes with the attributedNode added as the last.
   * The rules applicable to earlier attributes can be amended by the rules applicable to attributes added later.
   * At some point a rule may declare to "supercede" the rules that follow, which then become obsolete.
   * This logic is in sync with the editor's policy for overriding editor cells using attributes.
   * @param attributedNode
   */
  public List<SNode> nodesToApplyRulesTo(SNode attributedNode) {
    if (attributedNode == null) return Collections.emptyList();

    ArrayList<SNode> nodesToTest = new ArrayList<SNode>(AttributeOperations.getAllAttributes(attributedNode));
    nodesToTest.add(attributedNode);

    return nodesToTest;
  }


}
