/*
 * Copyright 2003-2011 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.lang.dataFlow.framework.instructions;


import jetbrains.mps.lang.dataFlow.framework.Program;
import jetbrains.mps.lang.dataFlow.framework.ProgramState;
import jetbrains.mps.lang.dataFlow.framework.Program.TryFinallyInfo;

import java.util.*;

public abstract class Instruction {
  protected Program myProgram;
  protected Object mySource;

  protected String myRuleReference;
  protected final Set<Instruction> myJumps = new HashSet<Instruction>();
  protected final Map<Object, Object> myUserObjects = new HashMap<Object, Object>();
  protected TryFinallyInfo myBlockInfo;
  protected int myIndex;

  public Instruction() {
  }

  public Instruction(String ruleNodeReference) {
    myRuleReference = ruleNodeReference;
  }

  public Program getProgram() {
    return myProgram;
  }

  public void setProgram(Program p) {
    myProgram = p;
  }

  public Object getSource() {
    return mySource;
  }

  public void setSource(Object source) {
    mySource = source;
  }

  public boolean isStart() {
    return getIndex() == 0;
  }

  public void addJump(Instruction instruction) {
    myJumps.add(instruction);
  }

  public void removeJump(Instruction instruction) {
    myJumps.remove(instruction);
  }

  public Set<Instruction> getJumps() {
    return myJumps;
  }

  public void buildCaches() {
    TryFinallyInfo bestMatch = null;
    for (TryFinallyInfo info : getProgram().getBlockInfos()) {
      if (myIndex > info.getTry().getIndex() && myIndex < info.getEndTry().getIndex()) {
        bestMatch = info;
      }
    }
    myBlockInfo = bestMatch;
  }

  public TryFinallyInfo getEnclosingBlock() {
    return myBlockInfo;
  }

  public Set<Instruction> succ() {
    Set<Instruction> result = new HashSet<Instruction>();
    for (ProgramState ps : new ProgramState(this, false).succ()) {
      result.add(ps.getInstruction());
    }
    for (ProgramState ps : new ProgramState(this, true).succ()) {
      result.add(ps.getInstruction());
    }
    return result;
  }

  public Set<Instruction> pred() {
    Set<Instruction> result = new HashSet<Instruction>();
    for (ProgramState ps : new ProgramState(this, false).pred()) {
      result.add(ps.getInstruction());
    }
    for (ProgramState ps : new ProgramState(this, true).pred()) {
      result.add(ps.getInstruction());
    }
    return result;
  }

  public List<ProgramState> succ(ProgramState s) {
    List<ProgramState> result = new ArrayList<ProgramState>();
    result.add(new ProgramState(getProgram().get(getIndex() + 1), s.isReturnMode()));
    return result;
  }

  public List<ProgramState> pred(ProgramState s) {
    List<ProgramState> result = new ArrayList<ProgramState>();
    if (this != getProgram().getStart()) {
      Instruction prev = getProgram().get(getIndex() - 1);
      if (!(prev instanceof RetInstruction) &&
          !(prev instanceof JumpInstruction) &&
          !(prev instanceof EndTryInstruction && s.isReturnMode())) {
        result.add(new ProgramState(prev, s.isReturnMode()));
      }
    }

    for (Instruction jump : myJumps) {
      result.add(new ProgramState(jump, s.isReturnMode()));
    }
    return result;
  }

  public int getIndex() {
    return myIndex;
  }

  public void setIndex(int index) {
    myIndex = index;
  }

  public boolean isBefore(Instruction i) {
    return getIndex() < i.getIndex();
  }

  public boolean isAfter(Instruction i) {
    return getIndex() > i.getIndex();
  }

  public abstract String commandPresentation();

  public void putUserObject(Object key, Object value) {
    myUserObjects.put(key, value);
  }

  public Object getUserObject(Object key) {
    return myUserObjects.get(key);
  }

  public String toString() {
    return getIndex() + ": " + commandPresentation();
  }

  public String getRuleReference() {
    return myRuleReference;
  }

  public void setRuleReference(String ruleReference) {
    myRuleReference = ruleReference;
  }
}
