/*
 * Copyright (c) 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.ipa.slicer;

import com.ibm.wala.dataflow.IFDS.ISupergraph;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.FilterIterator;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.graph.Graph;
import com.ibm.wala.util.intset.IntSet;
import java.util.Iterator;
import java.util.stream.Stream;

/** A wrapper around an SDG to make it look like a supergraph for tabulation. */
class SDGSupergraph implements ISupergraph<Statement, PDG<? extends InstanceKey>> {

  private final ISDG sdg;

  /** Do a backward slice? */
  private final boolean backward;

  public SDGSupergraph(ISDG sdg, boolean backward) {
    this.sdg = sdg;
    this.backward = backward;
  }

  @Override
  public Graph<PDG<? extends InstanceKey>> getProcedureGraph() {
    Assertions.UNREACHABLE();
    return null;
  }

  public Object[] getEntry() {
    Assertions.UNREACHABLE();
    return null;
  }

  @Override
  public byte classifyEdge(Statement src, Statement dest) {
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public Iterator<? extends Statement> getCallSites(
      Statement r, PDG<? extends InstanceKey> callee) {
    switch (r.getKind()) {
      case EXC_RET_CALLER:
        {
          ExceptionalReturnCaller n = (ExceptionalReturnCaller) r;
          SSAAbstractInvokeInstruction call = n.getInstruction();
          PDG<?> pdg = getProcOf(r);
          return pdg.getCallStatements(call).iterator();
        }
      case NORMAL_RET_CALLER:
        {
          NormalReturnCaller n = (NormalReturnCaller) r;
          SSAAbstractInvokeInstruction call = n.getInstruction();
          PDG<?> pdg = getProcOf(r);
          return pdg.getCallStatements(call).iterator();
        }
      case HEAP_RET_CALLER:
        {
          HeapStatement.HeapReturnCaller n = (HeapStatement.HeapReturnCaller) r;
          SSAAbstractInvokeInstruction call = n.getCall();
          PDG<?> pdg = getProcOf(r);
          return pdg.getCallStatements(call).iterator();
        }
      default:
        Assertions.UNREACHABLE(r.getKind().toString());
        return null;
    }
  }

  @Override
  public Iterator<? extends Statement> getCalledNodes(Statement call) {
    switch (call.getKind()) {
      case NORMAL:
        return new FilterIterator<>(getSuccNodes(call), this::isEntry);
      case PARAM_CALLER:
      case HEAP_PARAM_CALLER:
        return getSuccNodes(call);
      default:
        Assertions.UNREACHABLE(call.getKind().toString());
        return null;
    }
  }

  @Override
  public Statement[] getEntriesForProcedure(PDG<? extends InstanceKey> procedure) {
    Statement[] normal = procedure.getParamCalleeStatements();
    Statement[] result = new Statement[normal.length + 1];
    result[0] = new MethodEntryStatement(procedure.getCallGraphNode());
    System.arraycopy(normal, 0, result, 1, normal.length);
    return result;
  }

  @Override
  public Statement[] getExitsForProcedure(PDG<? extends InstanceKey> procedure) {
    Statement[] normal = procedure.getReturnStatements();
    Statement[] result = new Statement[normal.length + 1];
    result[0] = new MethodExitStatement(procedure.getCallGraphNode());
    System.arraycopy(normal, 0, result, 1, normal.length);
    return result;
  }

  @Override
  public Statement getLocalBlock(PDG<? extends InstanceKey> procedure, int i) {
    return procedure.getNode(i);
  }

  @Override
  public int getLocalBlockNumber(Statement n) {
    PDG<?> pdg = getProcOf(n);
    return pdg.getNumber(n);
  }

  @Override
  public Iterator<Statement> getNormalSuccessors(Statement call) {
    if (!backward) {
      return EmptyIterator.instance();
    } else {
      Assertions.UNREACHABLE();
      return null;
    }
  }

  @Override
  public int getNumberOfBlocks(PDG<? extends InstanceKey> procedure) {
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public PDG<? extends InstanceKey> getProcOf(Statement n) {
    CGNode node = n.getNode();
    PDG<? extends InstanceKey> result = sdg.getPDG(node);
    if (result == null) {
      Assertions.UNREACHABLE("panic: " + n + ' ' + node);
    }
    return result;
  }

  @Override
  public Iterator<? extends Statement> getReturnSites(
      Statement call, PDG<? extends InstanceKey> callee) {
    switch (call.getKind()) {
      case PARAM_CALLER:
        {
          ParamCaller n = (ParamCaller) call;
          SSAAbstractInvokeInstruction st = n.getInstruction();
          PDG<?> pdg = getProcOf(call);
          return pdg.getCallerReturnStatements(st).iterator();
        }
      case HEAP_PARAM_CALLER:
        {
          HeapStatement.HeapParamCaller n = (HeapStatement.HeapParamCaller) call;
          SSAAbstractInvokeInstruction st = n.getCall();
          PDG<?> pdg = getProcOf(call);
          return pdg.getCallerReturnStatements(st).iterator();
        }
      case NORMAL:
        {
          NormalStatement n = (NormalStatement) call;
          SSAAbstractInvokeInstruction st = (SSAAbstractInvokeInstruction) n.getInstruction();
          PDG<?> pdg = getProcOf(call);
          return pdg.getCallerReturnStatements(st).iterator();
        }
      default:
        Assertions.UNREACHABLE(call.getKind().toString());
        return null;
    }
  }

  @Override
  public boolean isCall(Statement n) {
    switch (n.getKind()) {
      case EXC_RET_CALLEE:
      case EXC_RET_CALLER:
      case HEAP_PARAM_CALLEE:
      case NORMAL_RET_CALLEE:
      case NORMAL_RET_CALLER:
      case PARAM_CALLEE:
      case PHI:
      case HEAP_RET_CALLEE:
      case HEAP_RET_CALLER:
      case METHOD_ENTRY:
      case METHOD_EXIT:
      case CATCH:
      case PI:
        return false;
      case HEAP_PARAM_CALLER:
      case PARAM_CALLER:
        return true;
      case NORMAL:
        if (sdg.getCOptions().isIgnoreInterproc()) {
          return false;
        } else {
          NormalStatement s = (NormalStatement) n;
          return s.getInstruction() instanceof SSAAbstractInvokeInstruction;
        }
      default:
        Assertions.UNREACHABLE(n.getKind() + " " + n);
        return false;
    }
  }

  @Override
  public boolean isEntry(Statement n) {
    switch (n.getKind()) {
      case PARAM_CALLEE:
      case HEAP_PARAM_CALLEE:
      case METHOD_ENTRY:
        return true;
      case PHI:
      case PI:
      case NORMAL_RET_CALLER:
      case PARAM_CALLER:
      case HEAP_RET_CALLER:
      case NORMAL:
      case EXC_RET_CALLEE:
      case EXC_RET_CALLER:
      case HEAP_PARAM_CALLER:
      case HEAP_RET_CALLEE:
      case NORMAL_RET_CALLEE:
      case CATCH:
        return false;
      default:
        Assertions.UNREACHABLE(n.toString());
        return false;
    }
  }

  @Override
  public boolean isExit(Statement n) {
    switch (n.getKind()) {
      case PARAM_CALLEE:
      case HEAP_PARAM_CALLEE:
      case HEAP_PARAM_CALLER:
      case PHI:
      case PI:
      case NORMAL_RET_CALLER:
      case PARAM_CALLER:
      case HEAP_RET_CALLER:
      case NORMAL:
      case EXC_RET_CALLER:
      case METHOD_ENTRY:
      case CATCH:
        return false;
      case HEAP_RET_CALLEE:
      case EXC_RET_CALLEE:
      case NORMAL_RET_CALLEE:
      case METHOD_EXIT:
        return true;
      default:
        Assertions.UNREACHABLE(n.toString());
        return false;
    }
  }

  @Override
  public boolean isReturn(Statement n) {
    switch (n.getKind()) {
      case EXC_RET_CALLER:
      case NORMAL_RET_CALLER:
      case HEAP_RET_CALLER:
        return true;
      case EXC_RET_CALLEE:
      case HEAP_PARAM_CALLEE:
      case HEAP_PARAM_CALLER:
      case HEAP_RET_CALLEE:
      case NORMAL:
      case NORMAL_RET_CALLEE:
      case PARAM_CALLEE:
      case PARAM_CALLER:
      case PHI:
      case PI:
      case METHOD_ENTRY:
      case CATCH:
        return false;
      default:
        Assertions.UNREACHABLE(n.getKind().toString());
        return false;
    }
  }

  @Override
  public void removeNodeAndEdges(Statement N) {
    Assertions.UNREACHABLE();
  }

  @Override
  public void addNode(Statement n) {
    Assertions.UNREACHABLE();
  }

  @Override
  public boolean containsNode(Statement N) {
    return sdg.containsNode(N);
  }

  @Override
  public int getNumberOfNodes() {
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public Iterator<Statement> iterator() {
    return sdg.iterator();
  }

  @Override
  public Stream<Statement> stream() {
    return sdg.stream();
  }

  @Override
  public void removeNode(Statement n) {
    Assertions.UNREACHABLE();
  }

  @Override
  public void addEdge(Statement src, Statement dst) {
    Assertions.UNREACHABLE();
  }

  @Override
  public int getPredNodeCount(Statement N) {
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public Iterator<Statement> getPredNodes(Statement N) {
    return sdg.getPredNodes(N);
  }

  @Override
  public int getSuccNodeCount(Statement N) {
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public Iterator<Statement> getSuccNodes(Statement N) {
    return sdg.getSuccNodes(N);
  }

  @Override
  public boolean hasEdge(Statement src, Statement dst) {
    return sdg.hasEdge(src, dst);
  }

  @Override
  public void removeAllIncidentEdges(Statement node) {
    Assertions.UNREACHABLE();
  }

  @Override
  public void removeEdge(Statement src, Statement dst) {
    Assertions.UNREACHABLE();
  }

  @Override
  public void removeIncomingEdges(Statement node) {
    Assertions.UNREACHABLE();
  }

  @Override
  public void removeOutgoingEdges(Statement node) {
    Assertions.UNREACHABLE();
  }

  @Override
  public int getMaxNumber() {
    return sdg.getMaxNumber();
  }

  @Override
  public Statement getNode(int number) {
    return sdg.getNode(number);
  }

  @Override
  public int getNumber(Statement N) {
    return sdg.getNumber(N);
  }

  @Override
  public Iterator<Statement> iterateNodes(IntSet s) {
    Assertions.UNREACHABLE();
    return null;
  }

  @Override
  public IntSet getPredNodeNumbers(Statement node) {
    return sdg.getPredNodeNumbers(node);
  }

  /** @see com.ibm.wala.util.graph.NumberedEdgeManager#getSuccNodeNumbers(java.lang.Object) */
  @Override
  public IntSet getSuccNodeNumbers(Statement node) {
    return sdg.getSuccNodeNumbers(node);
  }
}
