/*
 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 * 
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */

package com.sun.gluegen.nativesig;

import java.io.*;
import java.util.*;
import java.text.MessageFormat;

import com.sun.gluegen.*;
import com.sun.gluegen.cgram.types.*;
import com.sun.gluegen.cgram.*;
import com.sun.gluegen.opengl.*;
import com.sun.gluegen.procaddress.*;

public class NativeSignatureJavaMethodBindingEmitter extends GLJavaMethodBindingEmitter {
  public NativeSignatureJavaMethodBindingEmitter(GLJavaMethodBindingEmitter methodToWrap) {
    super(methodToWrap);
  }

  public NativeSignatureJavaMethodBindingEmitter(ProcAddressJavaMethodBindingEmitter methodToWrap) {
    super(methodToWrap, false);
  }

  public NativeSignatureJavaMethodBindingEmitter(JavaMethodBindingEmitter methodToWrap,
                                                 NativeSignatureEmitter emitter) {
    super(methodToWrap, false, null, false, false, emitter);
  }

  protected void emitSignature(PrintWriter writer) {
    writer.print(getBaseIndentString());
    emitNativeSignatureAnnotation(writer);
    super.emitSignature(writer);
  }

  protected void emitNativeSignatureAnnotation(PrintWriter writer) {
    if (hasModifier(JavaMethodBindingEmitter.NATIVE)) {
      // Emit everything as a leaf for now
      // FIXME: make this configurable
      writer.print("@NativeSignature(\"l");
      MethodBinding binding = getBinding();
      if (callThroughProcAddress) {
        writer.print("p");
      }
      writer.print("(");
      if (callThroughProcAddress) {
        writer.print("P");
      }
      for (int i = 0; i < binding.getNumArguments(); i++) {
        emitNativeSignatureElement(writer, binding.getJavaArgumentType(i), binding.getCArgumentType(i), i);
      }
      writer.print(")");
      emitNativeSignatureElement(writer, binding.getJavaReturnType(), binding.getCReturnType(), -1);
      writer.println("\")");
    }
  }

  protected void emitNativeSignatureElement(PrintWriter writer, JavaType type, Type cType, int index) {
    if (type.isVoid()) {
      if (index > 0) {
        throw new InternalError("Error parsing arguments -- void should not be seen aside from argument 0");
      }
      return;
    }

    if (type.isNIOBuffer()) {
      writer.print("A");
    } else if (type.isPrimitiveArray()) {
      writer.print("MO");
    } else if (type.isPrimitive()) {
      Class clazz = type.getJavaClass();
      if      (clazz == Byte.TYPE)      { writer.print("B"); }
      else if (clazz == Character.TYPE) { writer.print("C"); }
      else if (clazz == Double.TYPE)    { writer.print("D"); }
      else if (clazz == Float.TYPE)     { writer.print("F"); }
      else if (clazz == Integer.TYPE)   { writer.print("I"); }
      else if (clazz == Long.TYPE)      {
        // See if this is intended to be a pointer at the C level
        if (cType.isPointer()) {
          writer.print("A");
        } else {
          writer.print("J");
        }
      }
      else if (clazz == Short.TYPE)     { writer.print("S"); }
      else if (clazz == Boolean.TYPE)   { writer.print("Z"); }
      else throw new InternalError("Unhandled primitive type " + clazz);
    } else if (type.isString()) {
      writer.print("A");
    } else {
      throw new RuntimeException("Type not yet handled: " + type);
    }
  }

  protected String getReturnTypeString(boolean skipArray) {
    if (isForImplementingMethodCall()) {
      JavaType returnType = getBinding().getJavaReturnType();
      if (returnType.isString() || returnType.isNIOByteBuffer()) {
        // Treat these as addresses
        return "long";
      }
    }
    return super.getReturnTypeString(skipArray);
  }

  protected void emitPreCallSetup(MethodBinding binding, PrintWriter writer) {
    super.emitPreCallSetup(binding, writer);
    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.isNIOBuffer() && !directNIOOnly) {
        // Emit declarations for variables holding primitive arrays as type Object
        // We don't know 100% sure we're going to use these at this point in the code, though
        writer.println("  Object " + getNIOBufferArrayName(i) + " = (_direct ? null : BufferFactory.getArray(" +
                       getArgumentName(i) + "));");
      } else if (type.isString()) {
        writer.println("    long " + binding.getArgumentName(i) + "_c_str = BufferFactoryInternal.newCString(" + binding.getArgumentName(i) + ");");
      }
      // FIXME: going to need more of these for Buffer[] and String[], at least
    }
  }

  protected String getNIOBufferArrayName(int argNumber) {
    return "__buffer_array_" + argNumber;
  }

  protected int emitArguments(PrintWriter writer)
  {
    boolean needComma = false;
    int numEmitted = 0;

    if (callThroughProcAddress) {
      if (changeNameAndArguments) {
        writer.print("long procAddress");
        ++numEmitted;
        needComma = true;
      }
    }

    if (forImplementingMethodCall && binding.hasContainingType()) {
      if (needComma) {
        writer.print(", ");
      }

      // Always emit outgoing "this" argument
      writer.print("long ");
      writer.print(javaThisArgumentName());      
      ++numEmitted;
      needComma = true;
    }

    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.isVoid()) { 
        // Make sure this is the only param to the method; if it isn't,
        // there's something wrong with our parsing of the headers.
        if (binding.getNumArguments() != 1) {
          throw new InternalError(
            "\"void\" argument type found in " +
            "multi-argument function \"" + binding + "\"");
        }
        continue;
      } 

      if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        // Don't need to expose these at the Java level
        continue;
      }

      if (needComma) {
        writer.print(", ");
      }

      if (forImplementingMethodCall &&
          (forDirectBufferImplementation && type.isNIOBuffer() ||
           type.isString())) {
        // Direct Buffers and Strings go out as longs
        writer.print("long");
        // FIXME: will need more tests here to handle other constructs like String and direct Buffer arrays
      } else {
        writer.print(erasedTypeString(type, false));
      }
      writer.print(" ");
      writer.print(getArgumentName(i));

      ++numEmitted;
      needComma = true;

      // Add Buffer and array index offset arguments after each associated argument
      if (forIndirectBufferAndArrayImplementation) {
        if (type.isNIOBuffer()) {
          writer.print(", int " + byteOffsetArgName(i));
        } else if (type.isNIOBufferArray()) {
          writer.print(", int[] " + 
                       byteOffsetArrayArgName(i));
        }
      }

      // Add offset argument after each primitive array
      if (type.isPrimitiveArray()) {
        writer.print(", int " + offsetArgName(i));
      }
    }
    return numEmitted;
  }

  protected void emitReturnVariableSetupAndCall(MethodBinding binding, PrintWriter writer) {
    writer.print("    ");
    JavaType returnType = binding.getJavaReturnType();
    boolean needsResultAssignment = false;

    if (!returnType.isVoid()) {
      if (returnType.isCompoundTypeWrapper() ||
          returnType.isNIOByteBuffer()) {
        writer.println("ByteBuffer _res;");
        needsResultAssignment = true;
      } else if (returnType.isArrayOfCompoundTypeWrappers()) {
        writer.println("ByteBuffer[] _res;");
        needsResultAssignment = true;
      } else if (returnType.isString() || returnType.isNIOByteBuffer()) {
        writer.print(returnType);
        writer.println(" _res;");
        needsResultAssignment = true;
      } else {
        // Always assign to "_res" variable so we can clean up
        // outgoing String arguments, for example
        emitReturnType(writer);
        writer.println(" _res;");
        needsResultAssignment = true;
      }
    }

    if (binding.signatureCanUseIndirectNIO() && !directNIOOnly) {
      // Must generate two calls for this gated on whether the NIO
      // buffers coming in are all direct or indirect
      writer.println("if (_direct) {");
      writer.print  ("    ");
    }

    if (needsResultAssignment) {
      writer.print("  _res = ");
      if (returnType.isString()) {
        writer.print("BufferFactoryInternal.newJavaString(");
      } else if (returnType.isNIOByteBuffer()) {
        writer.print("BufferFactoryInternal.newDirectByteBuffer(");
      }
    } else {
      writer.print("  ");
      if (!returnType.isVoid()) {
        writer.print("return ");
      }
    }

    if (binding.signatureUsesJavaPrimitiveArrays() &&
        !binding.signatureCanUseIndirectNIO()) {
      // FIXME: what happens with a C function of the form
      //  void foo(int* arg0, void* arg1);
      // ?

      // Only one call being made in this body, going to indirect
      // buffer / array entry point
      emitCall(binding, writer, false);
      if (returnType.isString() || returnType.isNIOByteBuffer()) {
        writer.print(")");
      }
      writer.print(";");
      writer.println();
    } else {
      emitCall(binding, writer, true);
      if (returnType.isString() || returnType.isNIOByteBuffer()) {
        writer.print(")");
      }
      writer.print(";");
    }

    if (binding.signatureCanUseIndirectNIO() && !directNIOOnly) {
      // Must generate two calls for this gated on whether the NIO
      // buffers coming in are all direct or indirect
      writer.println();
      writer.println("    } else {");
      writer.print  ("    ");
      if (needsResultAssignment) {
        writer.print("    _res = ");
      } else {
        writer.print("  ");
        if (!returnType.isVoid()) {
          writer.print("return ");
        }
      }
      emitCall(binding, writer, false);
      writer.print(";");
      writer.println();
      writer.println("    }");
    } else {
      writer.println();
    }
    emitPrologueOrEpilogue(epilogue, writer);
    if (needsResultAssignment) {
      emitCallResultReturn(binding, writer);
    }
  }

  protected int emitCallArguments(MethodBinding binding, PrintWriter writer, boolean direct) {
    // Note that we override this completely because we both need to
    // move the potential location of the outgoing proc address as
    // well as change the way we pass out Buffers, arrays, Strings, etc.

    boolean needComma = false;
    int numArgsEmitted = 0;

    if (callThroughProcAddress) {
      writer.print("__addr_");
      needComma = true;
      ++numArgsEmitted;
    }

    if (binding.hasContainingType()) {
      // Emit this pointer
      assert(binding.getContainingType().isCompoundTypeWrapper());
      writer.print("BufferFactoryInternal.getDirectBufferAddress(");
      writer.print("getBuffer()");
      writer.print(")");
      needComma = true;
      ++numArgsEmitted;
    }
    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        // Don't need to expose these at the Java level
        continue;
      }

      if (type.isVoid()) {
        // Make sure this is the only param to the method; if it isn't,
        // there's something wrong with our parsing of the headers.
        assert(binding.getNumArguments() == 1);
        continue;
      } 

      if (needComma) {
        writer.print(", ");
      }

      if (type.isCompoundTypeWrapper()) {
        writer.print("BufferFactoryInternal.getDirectBufferAddress(");
        writer.print("((");
      }

      if (type.isNIOBuffer()) {
        if (!direct) {
          writer.print(getNIOBufferArrayName(i));
        } else {
          writer.print("BufferFactoryInternal.getDirectBufferAddress(");
          writer.print(getArgumentName(i));
          writer.print(")");
        }
      } else {
        writer.print(getArgumentName(i));
      }

      if (type.isCompoundTypeWrapper()) {
        writer.print(" == null) ? null : ");
        writer.print(getArgumentName(i));
        writer.print(".getBuffer())");
        writer.print(")");
      }

      if (type.isNIOBuffer()) {
        if (direct) {
          writer.print("+ BufferFactory.getDirectBufferByteOffset(" + getArgumentName(i) + ")");
        } else {
          writer.print(", BufferFactoryInternal.arrayBaseOffset(" +
                       getNIOBufferArrayName(i) +
                       ") + BufferFactory.getIndirectBufferByteOffset(" + getArgumentName(i) + ")");
        }
      } else if (type.isNIOBufferArray()) {
        writer.print(", " + byteOffsetArrayArgName(i));
      }

      // Add Array offset parameter for primitive arrays
      if (type.isPrimitiveArray()) {
        writer.print(", ");
        writer.print("BufferFactoryInternal.arrayBaseOffset(" + getArgumentName(i) + ") + ");
        if(type.isFloatArray()) {
          writer.print("BufferFactory.SIZEOF_FLOAT * ");
        } else if(type.isDoubleArray()) {
          writer.print("BufferFactory.SIZEOF_DOUBLE * ");
        } else if(type.isByteArray()) {
          writer.print("1 * ");
        } else if(type.isLongArray()) {
          writer.print("BufferFactory.SIZEOF_LONG * ");
        } else if(type.isShortArray()) {
          writer.print("BufferFactory.SIZEOF_SHORT * ");
        } else if(type.isIntArray()) {
          writer.print("BufferFactory.SIZEOF_INT * ");
        } else {
          throw new RuntimeException("Unsupported type for calculating array offset argument for " +
                                     getArgumentName(i) +
                                     "-- error occurred while processing Java glue code for " + getName());
        }
        writer.print(offsetArgName(i));
      }

      if (type.isString()) {
        writer.print("_c_str");
      }

      if (type.isCompoundTypeWrapper()) {
        writer.print(")");
      }

      needComma = true;
      ++numArgsEmitted;
    }
    return numArgsEmitted;
  }

  protected void emitCallResultReturn(MethodBinding binding, PrintWriter writer) {
    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.isString()) {
        writer.println(";");
        writer.println("    BufferFactoryInternal.freeCString(" + binding.getArgumentName(i) + "_c_str);");
      }
      // FIXME: will need more of these cleanups for things like Buffer[] and String[] (see above)
    }

    super.emitCallResultReturn(binding, writer);
  }

  public String getName() {
    String res = super.getName();
    if (forImplementingMethodCall && bufferObjectVariant) {
      return res + "BufObj";
    }
    return res;
  }

  protected String getImplMethodName(boolean direct) {
    String name = null;
    if (direct) {
      name = binding.getRenamedMethodName() + "$0";
    } else {
      name = binding.getRenamedMethodName() + "$1";
    }
    if (bufferObjectVariant) {
      return name + "BufObj";
    }
    return name;
  }
}
