/*
 * 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;

import jetbrains.mps.lang.dataFlow.framework.instructions.*;

import java.util.*;

public class AnalyzerRunner<E> {
  protected Program myProgram;
  protected DataFlowAnalyzer<E> myAnalyzer;

  public AnalyzerRunner(Program program, DataFlowAnalyzer<E> analyzer) {
    myProgram = program;
    myAnalyzer = analyzer;
  }

  public AnalysisResult<E> analyze() {
    Map<ProgramState, E> stateValues = doAnalyze();
    Map<Instruction, E> result = new HashMap<Instruction, E>();
    for (Instruction i : myProgram.getInstructions()) {
      List<E> input = new ArrayList<E>();
      input.add(stateValues.get(new ProgramState(i, true)));
      input.add(stateValues.get(new ProgramState(i, false)));
      result.put(i, myAnalyzer.merge(myProgram, input));
    }
    return new AnalysisResult<E>(myProgram, myAnalyzer, stateValues, result);
  }

  private Map<ProgramState, E> doAnalyze() {
    Map<ProgramState, E> stateValues = new ProgramStateMap<E>(myProgram);

    for (ProgramState ps : myProgram.getStates()) {
      stateValues.put(ps, myAnalyzer.initial(myProgram));
    }

    AnalysisDirection direction = myAnalyzer.getDirection();

    Map<ProgramState, List<ProgramState>> dependencies = new ProgramStateMap<List<ProgramState>>(myProgram);
    Map<ProgramState, List<ProgramState>> dependents = new ProgramStateMap<List<ProgramState>>(myProgram);
    for (ProgramState ps : myProgram.getStates()) {
      dependencies.put(ps, direction.dependencies(ps));
      dependents.put(ps, direction.dependents(ps));
    }

    Queue<ProgramState> workList = new LinkedList<ProgramState>();
    for (Instruction i : myProgram.getInstructions()) {
      workList.add(new ProgramState(i, false));
      workList.add(new ProgramState(i, true));
    }

    while (!workList.isEmpty()) {
      ProgramState current = workList.remove();

      List<E> input = new ArrayList<E>();
      for (ProgramState s : dependencies.get(current)) {
        input.add(stateValues.get(s));
      }

      E oldValue = stateValues.get(current);
      E mergedValue = myAnalyzer.merge(myProgram, input);
      E newValue;
      if (myAnalyzer instanceof DataFlowAnalyzerBase) {
        newValue = ((DataFlowAnalyzerBase<E>) myAnalyzer).fun(mergedValue, current, Collections.unmodifiableMap(stateValues));
      } else {
        newValue = myAnalyzer.fun(mergedValue, current);
      }

      if (!newValue.equals(oldValue)) {
        stateValues.put(current, newValue);
        for (ProgramState s : dependents.get(current)) {
          workList.add(s);
        }
      }
    }

    return stateValues;
  }

  public Program getProgramCopy() {
    return myProgram;
  }
}
