/*
 * Copyright 2008 Google Inc.
 *
 * 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 com.google.gwt.dev.jjs.impl.gflow.constants;

import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JValueLiteral;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.impl.gflow.AssumptionMap;
import com.google.gwt.dev.jjs.impl.gflow.AssumptionUtil;
import com.google.gwt.dev.jjs.impl.gflow.FlowFunction;
import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgConditionalNode;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgReadWriteNode;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgVisitor;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgWriteNode;
import com.google.gwt.dev.jjs.impl.gflow.constants.ConstantsAssumption.Updater;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;

import java.util.ArrayList;

/**
 * Flow function for ConstantsAnalysis.
 */
public class ConstantsFlowFunction implements
    FlowFunction<CfgNode<?>, CfgEdge, Cfg, ConstantsAssumption> {
  @Override
  public void interpret(CfgNode<?> node,
      final Cfg graph, AssumptionMap<CfgEdge, ConstantsAssumption> assumptionMap) {
    ConstantsAssumption in = AssumptionUtil.join(graph.getInEdges(node), assumptionMap);

    final int outSize = graph.getOutEdges(node).size();
    final ArrayList<ConstantsAssumption> result =
      new ArrayList<ConstantsAssumption>(outSize);

    final Updater assumption = new Updater(in);
    node.accept(new CfgVisitor() {
      @Override
      public void visitConditionalNode(CfgConditionalNode<?> x) {
        JExpression condition = x.getCondition();

        Updater thenAssumptions = assumption.copy();
        Updater elseAssumptions = assumption.copy();

        Preconditions.checkNotNull(condition, "Null condition in %s", x);
        AssumptionDeducer.deduceAssumption(condition, JBooleanLiteral.TRUE,
            thenAssumptions);
        AssumptionDeducer.deduceAssumption(condition, JBooleanLiteral.FALSE,
            elseAssumptions);

        for (CfgEdge e : graph.getOutEdges(x)) {
          if (CfgConditionalNode.THEN.equals(e.getRole())) {
            result.add(thenAssumptions.unwrap());
          } else if (CfgConditionalNode.ELSE.equals(e.getRole())) {
            result.add(elseAssumptions.unwrap());
          } else {
            result.add(assumption.unwrap());
          }
        }
      }

      @Override
      public void visitNode(CfgNode<?> node) {
        // We can't deduce any assumptions from the node. Just copy incoming
        // assumptions further.
        for (int i = 0; i < graph.getOutEdges(node).size(); ++i) {
          result.add(assumption.unwrap());
        }
      }

      @Override
      public void visitReadWriteNode(CfgReadWriteNode node) {
        processWrite(assumption, node.getTargetVariable(), node.getValue());
        super.visitReadWriteNode(node);
      }

      @Override
      public void visitWriteNode(CfgWriteNode node) {
        processWrite(assumption, node.getTargetVariable(), node.getValue());
        super.visitWriteNode(node);
      }

      private void processWrite(final Updater assumption,
          JVariable var, JExpression expression) {
        if (var == null) {
          return;
        }

        if (var instanceof JParameter || var instanceof JLocal) {
          if (expression != null) {
            JValueLiteral valueLiteral =
              ExpressionEvaluator.evaluate(expression, assumption.unwrap());
            if (valueLiteral != null &&
                (valueLiteral.getType() == var.getType() ||
                 valueLiteral instanceof JNullLiteral)) {
              assumption.set(var, valueLiteral);
            } else {
              // Don't bother to try to get conversions right.
              assumption.set(var, null);
            }
          } else {
            assumption.set(var, null);
          }
        }
      }
    });

    AssumptionUtil.setAssumptions(graph.getOutEdges(node), result, assumptionMap);
  }
}
