/*
 * 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.ide.typesystem.trace;

import jetbrains.mps.newTypesystem.context.IncrementalTypecheckingContext;
import jetbrains.mps.newTypesystem.operation.AbstractOperation;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.typesystem.inference.util.ConcurrentSubtypingCache;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.typesystem.inference.ITypeContextOwner;
import jetbrains.mps.typesystem.inference.ITypechecking;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.typesystem.inference.TypeContextManager;
import jetbrains.mps.typesystem.inference.util.SubtypingCache;

import java.util.List;

/**
* Created with IntelliJ IDEA.
* User: fyodor
* Date: 11/28/12
* Time: 2:07 PM
* To change this template use File | Settings | File Templates.
*/
public class TypecheckingContextTracker implements ITypeContextOwner {

  private final TypeCheckingContext myTypecheckingContext;
  private final SNode myRootNode;
  private TypeCheckingContext myCurrentContext;
  private AbstractOperation myOperation;
  private AbstractOperation myOldOperation;
  private State myCurrentState;
  private State myStateCopy;
  private boolean generationMode = false;

  public TypecheckingContextTracker(SNode rootNode) {
    myRootNode = rootNode;
    myTypecheckingContext = initContext();

    myOperation = myTypecheckingContext.getOperation();
    myStateCopy = new State(myTypecheckingContext, myTypecheckingContext.getState().getOperation());
    myCurrentState = myStateCopy;
    myCurrentContext = myTypecheckingContext;
  }

  private TypeCheckingContext initContext() {
    final TypeCheckingContext context = TypeContextManager.getInstance().acquireTypecheckingContext(myRootNode, this);
    TypeContextManager.getInstance().runTypeCheckingAction(this, myRootNode, new ITypechecking.Action() {
      @Override
      public void run(TypeCheckingContext context) {
        context.checkRootInTraceMode(true);
      }
    });
    return context;
  }

  @Override
  public TypeCheckingContext createTypecheckingContext(SNode sNode, TypeContextManager typeContextManager) {
    return typeContextManager.createTracingTypeCheckingContext(sNode);
  }

  @Override
  public boolean reuseTypecheckingContext() {
    return true;
  }

  @Override
  public SubtypingCache createSubtypingCache() {
    return new ConcurrentSubtypingCache();
  }

  public void setGenerationMode(boolean generationMode, SNode selectedNode) {
    if (generationMode == this.generationMode) {
      return;
    }
    this.generationMode = generationMode;
    myOldOperation = null;
    if (false && this.generationMode) {
      IncrementalTypecheckingContext context = (IncrementalTypecheckingContext) TypeContextManager.getInstance().createTypeCheckingContext(selectedNode);
      context.getTypeInGenerationMode(selectedNode);

      myOperation = context.getOperation();
      myCurrentContext = context;
      myCurrentState = context.getState();
    } else {
      myOperation = myTypecheckingContext.getOperation();
      myCurrentContext = myTypecheckingContext;
      myCurrentState = myStateCopy;
    }
  }

  public State resetCurrentState(AbstractOperation fromDiff) {
    AbstractOperation rootDiff = myCurrentContext.getOperation();
    if (myOldOperation == null) {
      myCurrentState.clear(false);
      myCurrentState.executeOperationsBeforeAnchor(rootDiff, fromDiff);
    } else {
      myCurrentState.updateState(myOldOperation, fromDiff);
    }
    return myCurrentState;
  }

  public State updateCurrentState(AbstractOperation fromDiff, AbstractOperation toDiff) {
    myCurrentState.updateState(fromDiff, toDiff);
    myOldOperation = toDiff;
    return myCurrentState;
  }

  public State updateCurrentState(AbstractOperation difference) {
    AbstractOperation nextDiff = findUltimateConsequence(difference);
    if (nextDiff != null && nextDiff != difference) {
      myCurrentState.updateState(difference, nextDiff);
      myOldOperation = nextDiff;
      return myCurrentState;
    } else {
      myOldOperation = difference;
    }
    return null;
  }

  private AbstractOperation findUltimateConsequence(AbstractOperation op) {
    if (op == null) {
      return null;
    }
    AbstractOperation result = op;
    List<AbstractOperation> consequences = result.getConsequences();
    while (consequences != null && consequences.size() > 0) {
      result = consequences.get(consequences.size() - 1);
      consequences = result.getConsequences();
    }
    return result;
  }

  public State getStateCopy() {
    return myStateCopy;
  }

  public State getCurrentState() {
    return myCurrentState;
  }

  public AbstractOperation getOperation () {
    return myOperation;
  }

  public void checkRoot(boolean refresh) {
    myTypecheckingContext.checkRootInTraceMode(refresh);
  }

  public void dispose() {
    TypeContextManager.getInstance().releaseTypecheckingContext(myRootNode, this);
  }
}
