/*
 * Copyright (c) 2002 - 2014 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.cfg.exc.intra;

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

import com.ibm.wala.cfg.exc.intra.NullPointerState.State;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.fixpoint.AbstractVariable;
/**
 * Encapsulates the state of all parameters of an invoked method
 * 
 * @author markus
 *
 */
public class ParameterState extends AbstractVariable<ParameterState> {
  /*
   * Inital state is UNKNOWN.
   * Lattice: BOTH < { NULL, NOT_NULL } < UNKNOWN 
   * 
   * public enum State { UNKNOWN, BOTH, NULL, NOT_NULL }; as defined in NullPointerState
   * 
   */
  
  public static final int NO_THIS_PTR = -1;
  
  // maps the parmeter's varNum --> State
  private final HashMap<Integer, State> params = new HashMap<>();
  
  public static ParameterState createDefault(IMethod m) {
    ParameterState p = new ParameterState();
    
    if (!m.isStatic()) {
      // set this pointer to NOT_NULL
      p.setState(0, State.NOT_NULL);
    }
    
    return p;
  }
  
  public ParameterState() {
  }
  
  /**
   * Constructor to make a {@code ParameteState} out of a regular {@code NullPointerState}.
   * 
   * @param state The {@code NullPointerState} to parse.
   * @param parameterNumbers The numbers of parameters in {@code state}
   */
  public ParameterState(NullPointerState state, int[] parameterNumbers) {
    //by convention the first ssa vars are the parameters
    for (int i=0; i < parameterNumbers.length; i++){
      params.put(i, state.getState(parameterNumbers[i]));
    }
  }

  public void setState(int varNum, State state) {
    State prev = params.get(varNum);
    if (prev != null) {
      switch (prev) {
      case UNKNOWN:
        if (state != State.UNKNOWN) {
          throw new IllegalArgumentException("Try to set " + prev + " to " + state);
        }
        break;
      case NULL:
        if (!(state == State.BOTH || state == State.NULL)) {
          throw new IllegalArgumentException("Try to set " + prev + " to " + state);
        }
        break;
      case NOT_NULL:
        if (!(state == State.BOTH || state == State.NOT_NULL)) {
          throw new IllegalArgumentException("Try to set " + prev + " to " + state);
        }
        break;
      default:
        throw new UnsupportedOperationException(String.format("unexpected previous state %s", prev));
      }
    }
    params.put(varNum, state);
  }
  
  public HashMap<Integer, State> getStates() {
    return this.params;
  }
  
  /**
   * Returns the state of an specified parameter.
   * 
   * @param varNum The SSA var num of the parameter
   * @return the state of the parameter defined with {@code varNum}
   */
  public State getState(int varNum) {
    State state = params.get(varNum);
    if (state == null) {
      throw new IllegalArgumentException("No mapping for variable " + varNum + "in ParameterState " + this.toString());
    }
    return state;
  }

  @Override
  public void copyState(ParameterState v) {
    throw new UnsupportedOperationException();
  }
  
  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder("<");
    Set<Entry<Integer, State>> paramsSet = params.entrySet();
        
    for (Entry<Integer, State> param : paramsSet){
      switch (param.getValue()) {
      case BOTH:
        buf.append('*');
        break;
      case NOT_NULL:
        buf.append('1');
        break;
      case NULL:
        buf.append('0');
        break;
      case UNKNOWN:
        buf.append('?');
        break;
      default:
        throw new IllegalStateException();
      }
    }
    buf.append('>');
    
    return buf.toString();
  }
}
