/*
 * Copyright 2013 The Error Prone Authors.
 *
 * 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.errorprone.refaster;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.errorprone.refaster.Unifier.unifications;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableBiMap;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.TreeVisitor;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBinary;

/**
 * {@link UTree} version of {@link BinaryTree}.
 *
 * @author lowasser@google.com (Louis Wasserman)
 */
@AutoValue
abstract class UBinary extends UExpression implements BinaryTree {
  static final ImmutableBiMap<Kind, JCTree.Tag> OP_CODES =
      new ImmutableBiMap.Builder<Kind, JCTree.Tag>()
          .put(Kind.PLUS, JCTree.Tag.PLUS)
          .put(Kind.MINUS, JCTree.Tag.MINUS)
          .put(Kind.MULTIPLY, JCTree.Tag.MUL)
          .put(Kind.DIVIDE, JCTree.Tag.DIV)
          .put(Kind.REMAINDER, JCTree.Tag.MOD)
          .put(Kind.LEFT_SHIFT, JCTree.Tag.SL)
          .put(Kind.RIGHT_SHIFT, JCTree.Tag.SR)
          .put(Kind.UNSIGNED_RIGHT_SHIFT, JCTree.Tag.USR)
          .put(Kind.OR, JCTree.Tag.BITOR)
          .put(Kind.AND, JCTree.Tag.BITAND)
          .put(Kind.XOR, JCTree.Tag.BITXOR)
          .put(Kind.CONDITIONAL_AND, JCTree.Tag.AND)
          .put(Kind.CONDITIONAL_OR, JCTree.Tag.OR)
          .put(Kind.LESS_THAN, JCTree.Tag.LT)
          .put(Kind.LESS_THAN_EQUAL, JCTree.Tag.LE)
          .put(Kind.GREATER_THAN, JCTree.Tag.GT)
          .put(Kind.GREATER_THAN_EQUAL, JCTree.Tag.GE)
          .put(Kind.EQUAL_TO, JCTree.Tag.EQ)
          .put(Kind.NOT_EQUAL_TO, JCTree.Tag.NE)
          .buildOrThrow();

  static final ImmutableBiMap<Kind, Kind> NEGATION =
      new ImmutableBiMap.Builder<Kind, Kind>()
          .put(Kind.LESS_THAN, Kind.GREATER_THAN_EQUAL)
          .put(Kind.LESS_THAN_EQUAL, Kind.GREATER_THAN)
          .put(Kind.GREATER_THAN, Kind.LESS_THAN_EQUAL)
          .put(Kind.GREATER_THAN_EQUAL, Kind.LESS_THAN)
          .put(Kind.EQUAL_TO, Kind.NOT_EQUAL_TO)
          .put(Kind.NOT_EQUAL_TO, Kind.EQUAL_TO)
          .buildOrThrow();

  static final ImmutableBiMap<Kind, Kind> DEMORGAN =
      new ImmutableBiMap.Builder<Kind, Kind>()
          .put(Kind.CONDITIONAL_AND, Kind.CONDITIONAL_OR)
          .put(Kind.CONDITIONAL_OR, Kind.CONDITIONAL_AND)
          .put(Kind.AND, Kind.OR)
          .put(Kind.OR, Kind.AND)
          .buildOrThrow();

  public static UBinary create(Kind binaryOp, UExpression lhs, UExpression rhs) {
    checkArgument(
        OP_CODES.containsKey(binaryOp), "%s is not a supported binary operation", binaryOp);
    return new AutoValue_UBinary(binaryOp, lhs, rhs);
  }

  @Override
  public abstract Kind getKind();

  @Override
  public abstract UExpression getLeftOperand();

  @Override
  public abstract UExpression getRightOperand();

  @Override
  public Choice<Unifier> visitBinary(BinaryTree binary, Unifier unifier) {
    return Choice.condition(getKind().equals(binary.getKind()), unifier)
        .flatMap(unifications(getLeftOperand(), binary.getLeftOperand()))
        .flatMap(unifications(getRightOperand(), binary.getRightOperand()));
  }

  @Override
  public <R, D> R accept(TreeVisitor<R, D> visitor, D data) {
    return visitor.visitBinary(this, data);
  }

  @Override
  public JCBinary inline(Inliner inliner) throws CouldNotResolveImportException {
    return inliner
        .maker()
        .Binary(
            OP_CODES.get(getKind()),
            getLeftOperand().inline(inliner),
            getRightOperand().inline(inliner));
  }

  @Override
  public UExpression negate() {
    if (NEGATION.containsKey(getKind())) {
      return UBinary.create(NEGATION.get(getKind()), getLeftOperand(), getRightOperand());
    } else if (DEMORGAN.containsKey(getKind())) {
      return UBinary.create(
          DEMORGAN.get(getKind()), getLeftOperand().negate(), getRightOperand().negate());
    } else {
      return super.negate();
    }
  }
}
