/*
 * Copyright 2013 University of Chicago and Argonne National Laboratory
 *
 * 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 exm.stc.common.lang;

import java.util.ArrayList;
import java.util.List;

import exm.stc.common.lang.Types.StructType;
import exm.stc.common.lang.Types.StructType.StructField;
import exm.stc.common.lang.Types.Type;
import exm.stc.common.lang.Types.Typed;
import exm.stc.common.lang.Var.DefType;

public class RefCounting {
  public static enum RefCountType {
    READERS,
    WRITERS;
  }

  /**
   * Whether an input arg for an updateable var should be writable by
   * the callee
   * TODO: does this make sense from language design?
   */
  public static final boolean WRITABLE_UPDATEABLE_INARGS = true;

  /**
   * Returns true if type can carry a refcount.  Some particular
   * storage modes of the type might not have a refcount
   * @param type
   * @param rcType
   * @return
   */
  public static boolean mayHaveRefcount(Type type, RefCountType rcType) {
    if (rcType == RefCountType.READERS) {
      return mayHaveReadRefcount(type);
    } else {
      assert(rcType == RefCountType.WRITERS);
      return mayHaveWriteRefcount(type);
    }
  }

  public static boolean mayHaveReadRefcount(Type type) {
    if (Types.isPrimValue(type)) {
      return false;
    } else if (Types.isContainerLocal(type) ||
               Types.isStructLocal(type)) {
      return false;
    }
    return true;
  }

  public static boolean mayHaveWriteRefcount(Type type) {
    // Struct members may have write refcount 
    return Types.isArray(type) || Types.isPrimUpdateable(type) ||
           Types.isStruct(type) || Types.isBag(type);
  }

  /**
   * @param v
   * @return true if var has read refcount to be tracked/managed by
   *              STC-emitted code
   */
  public static boolean trackReadRefCount(Var v) {
    return trackReadRefCount(v.type(), v.defType());
  }
  
  private static boolean trackReadRefCount(Type type, DefType defType) {
    if (!mayHaveReadRefcount(type)) {
      return false;
    } else if (defType == DefType.GLOBAL_CONST) {
      return false;
    }
    return true;
  }
  
  /**
   * @param v
   * @return true if var has write refcount to be tracked/managed by
   *              STC-emitted code
   */
  public static boolean trackWriteRefCount(Var v) {
    return trackWriteRefCount(v.type(), v.defType());
  }
  
  private static boolean trackWriteRefCount(Type type, DefType defType) {
    if (!mayHaveWriteRefcount(type)) {
      return false;
    } else if (defType == DefType.GLOBAL_CONST) {
      return false;
    }
    return true;
  }
  
  /**
   * @param v
   * @return true if var has refcount to be tracked/managed by
   *              STC-emitted code
   */
  public static boolean trackRefCount(Var var, RefCountType rcType) {
    return trackRefCount(var.type(), var.defType(), rcType);
  }
  
  private static boolean trackRefCount(Type type, DefType defType,
                                      RefCountType rcType) {
    if (rcType == RefCountType.READERS) {
      return trackReadRefCount(type, defType);
    } else {
      assert(rcType == RefCountType.WRITERS);
      return trackWriteRefCount(type, defType);
    }
  }

  /**
   * Filter vars to include only variables where writers count is tracked
   * @param vars
   * @return
   */
  public static List<Var> filterTrackedWriteRefcount(List<Var> vars) {
    assert(vars != null);
    List<Var> res = new ArrayList<Var>();
    for (Var var: vars) {
      if (trackWriteRefCount(var)) {
        res.add(var);
      }
    }
    return res;
  }
  
  
  /**
   * @param wv
   * @return true if there is a distinction between closing and recursive
   *              closing for variable.  Note: this errs on the side of saying
   *              true.
   */
  public static boolean recursiveClosePossible(Typed typed) {
    if (Types.isPrimFuture(typed) || Types.isPrimUpdateable(typed)) {
      return false;
    } else if (Types.isContainer(typed)) {
      // If it's a reference to another variable, recursive close is possible
      return Types.isRef(Types.containerElemType(typed));
    } else if (Types.isRef(typed)) {
      return true;
    }
    // Err on side of saying true
    return true;
  }

  public static long baseRefCount(Var v, RefCountType rcType,
                                  boolean trackedCountOnly) {
    return baseRefCount(v.type(), v.defType(), rcType, trackedCountOnly);
  }
  
  /**
   * Return base reference count for type, i.e. what it's initialized to
   * by default 
   * @param rcType 
   * @param blockVar
   * @param trackedCountOnly return only refcounts tracked by STC
   *                (not auto-decremented on assign)
   * @return
   */
  private static long baseRefCount(Type type, DefType defType,
               RefCountType rcType, boolean trackedCountOnly) {
    assert(!Types.isPrimValue(type) && !Types.isContainerLocal(type) &&
           !Types.isStructLocal(type)) :
           "Invalid type for refcount: " + type.type();
    if (Types.isStruct(type)) {
      // Sum of field refcounts
      StructType structT = (StructType)type.type().getImplType();
      long count = 0;
      for (StructField field: structT.getFields()) {
        count += baseRefCount(field.getType(), defType, rcType,
                              trackedCountOnly);
      }
      return count;
    } else {
      // Data store variables generally have one refcount to start with
      // In some cases (e.g. decrement-on-assign), it's not tracked 
      if (trackedCountOnly) {
        if (trackRefCount(type, defType, rcType)) {
          return 1;
        } else {
          return 0;
        }
      }
      else
      {
        return 1;
      }
    }
  }
  
  public static long baseReadRefCount(Var var, boolean trackedCountOnly) {
    return baseRefCount(var.type(), var.defType(), RefCountType.READERS,
                        trackedCountOnly);
  }
  
  public static long baseWriteRefCount(Var var, boolean trackedCountOnly) {
    return baseRefCount(var.type(), var.defType(), RefCountType.WRITERS,
                        trackedCountOnly);
  }
}

@@ -5364,177 +5364,8 @@
 ) %7B%0A
-    assert(!Types.isPrimValue(type) && !Types.isContainerLocal(type) &&%0A           !Types.isStructLocal(type)) :%0A           %22Invalid type for refcount: %22 + type.type();%0A
     
@@ -5705,16 +5705,172 @@
 n count;
+ %0A    %7D else if (Types.isPrimValue(type) %7C%7C Types.isContainerLocal(type) %7C%7C%0A               Types.isStructLocal(type)) %7B%0A      // No refcount%0A      return 0;
 %0A    %7D e

Fix assertion error