/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.mm.mmtk.gcspy;

import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;
import org.mmtk.plan.Plan;
import org.mmtk.vm.VM;

import org.jikesrvm.runtime.Magic;
import static org.jikesrvm.runtime.SysCall.sysCall;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.runtime.RuntimeEntrypoints;

import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;

/**
 * This class provides generally useful methods.
 */
@Uninterruptible public class Util extends org.mmtk.vm.gcspy.Util implements Constants {
  private static final boolean DEBUG_ = false;
  public static final int KILOBYTE = 1024;
  public static final int MEGABYTE = 1024 * 1024;

  /**
   * Allocate an array of bytes with malloc
   *
   * @param size The size to allocate
   * @return The start address of the memory allocated in C space
   * @see #free
   */
  public final Address malloc(int size) {
    if (org.jikesrvm.VM.BuildWithGCSpy) {
      Address rtn  = sysCall.sysMalloc(size);
      if (rtn.isZero()) VM.assertions.fail("GCspy malloc failure");
      return rtn;
    } else
      return Address.zero();
  }

  /**
   * Free an array of bytes previously allocated with malloc
   *
   * @param addr The address of some memory previously allocated with malloc
   * @see #malloc
   */
  public final void free(Address addr) {
    if (org.jikesrvm.VM.BuildWithGCSpy)
      if (!addr.isZero())
        sysCall.sysFree(addr);
  }

  /**
   * Convert a String to a 0-terminated array of bytes
   *
   * @param str The string to convert
   * @return The address of a null-terminated array in C-space
   *
   * WARNING: we call out to String.length and String.charAt, both of
   * which are interruptible. We protect these calls with a
   * swLock/swUnlock mechanism, as per VM.sysWrite on String
   */
  public final Address getBytes(String str) {
    if (org.jikesrvm.VM.BuildWithGCSpy) {
      if (str == null)
        return Address.zero();

      if (DEBUG_) {
        Log.write("getBytes: "); Log.write(str); Log.write("->");
      }

      // Grab some memory sufficient to hold the null terminated string,
      // rounded up to an integral number of ints.
      char[] str_backing = java.lang.JikesRVMSupport.getBackingCharArray(str);
      int str_length = java.lang.JikesRVMSupport.getStringLength(str);
      int str_offset = java.lang.JikesRVMSupport.getStringOffset(str);
      int size = (str_length + 4) & -4;
      Address rtn = malloc(size);

      // Write the string into it, one byte at a time (dodgy conversion)
      for (int i=0; i < str_length; i++)  {
        rtn.store((byte)str_backing[str_offset+i], Offset.fromIntSignExtend(i));
      }
      // Zero rest of byte[]
      for (int i=str_length; i < size; i++)  {
        rtn.store((byte)0, Offset.fromIntSignExtend(i-str_offset));
      }
      if (DEBUG_) {
        sysCall.sysWriteBytes(2/*SysTraceFd*/, rtn, size); Log.write("\n");
      }
      return rtn;
    } else {
      return Address.zero();
    }
  }

  /**
   * Pretty print a size, converting from bytes to kilo- or mega-bytes as appropriate
   *
   * @param buffer The buffer (in C space) in which to place the formatted size
   * @param size The size in bytes
   */
  public final void formatSize(Address buffer, int size) {
    if (org.jikesrvm.VM.BuildWithGCSpy)
      sysCall.gcspyFormatSize(buffer, size);
  }


  /**
   * Pretty print a size, converting from bytes to kilo- or mega-bytes as appropriate
   *
   * @param format A format string
   * @param bufsize The size of a buffer large enough to hold the formatted result
   * @param size The size in bytes
   */
  public final Address formatSize(String format, int bufsize, int size) {
    if (org.jikesrvm.VM.BuildWithGCSpy) {
      // - sprintf(tmp, "Current Size: %s\n", gcspy_formatSize(size));
      Address tmp = malloc(bufsize);
      Address formattedSize = malloc(bufsize);
      Address currentSize = getBytes(format);
      formatSize(formattedSize, size);
      sprintf(tmp, currentSize, formattedSize);
      return tmp;
    } else {
      return Address.zero();
    }
  }

  /**
   * Create an array of a particular type.
   * The easiest way to use this is:
   *     Foo[] x = (Foo [])Stream.createDataArray(new Foo[0], numElements);
   * @param templ a data array to use as a template
   * @param numElements number of elements in new array
   * @return the new array
   */
  @Interruptible
  public Object createDataArray(Object templ, int numElements) {
    if (org.jikesrvm.VM.BuildWithGCSpy) {
      RVMArray array = Magic.getObjectType(templ).asArray();
      return RuntimeEntrypoints.resolvedNewArray(numElements,
                              array.getLogElementSize(),
                              ObjectModel.computeArrayHeaderSize(array),
                              array.getTypeInformationBlock(),
                              Plan.ALLOC_GCSPY,
                              ObjectModel.getAlignment(array),
                              ObjectModel.getOffsetForAlignment(array, false),
                              0);
    } else {
      return null;
    }
  }

  //----------- Various methods modelled on string.c ---------------------//

  /**
   * sprintf(char *str, char *format, char* value)
   *
   * @param str The destination 'string' (memory in C space)
   * @param format The format 'string' (memory in C space)
   * @param value The value 'string' (memory in C space)
   * @return The number of characters printed (as returned by C's sprintf
   */
  public final int sprintf(Address str, Address format, Address value) {
    if (org.jikesrvm.VM.BuildWithGCSpy)
      return sysCall.gcspySprintf(str, format, value);
    else
      return 0;
  }
}
