/*
 * Copyright (c) 2010-2023 JogAmp Community. All rights reserved.
 * 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.jogamp.gluegen.opengl.nativesig;

import com.jogamp.gluegen.GlueGenException;
import com.jogamp.gluegen.JavaMethodBindingEmitter;
import com.jogamp.gluegen.JavaType;
import com.jogamp.gluegen.MethodBinding;
import com.jogamp.gluegen.cgram.types.Type;
import com.jogamp.gluegen.opengl.GLEmitter;
import com.jogamp.gluegen.opengl.GLJavaMethodBindingEmitter;
import com.jogamp.gluegen.procaddress.ProcAddressJavaMethodBindingEmitter;

/** Review: This Package/Class is not used and subject to be deleted. */
public class NativeSignatureJavaMethodBindingEmitter extends GLJavaMethodBindingEmitter {

  public NativeSignatureJavaMethodBindingEmitter(final GLJavaMethodBindingEmitter methodToWrap) {
    super(methodToWrap);
  }

  public NativeSignatureJavaMethodBindingEmitter(final ProcAddressJavaMethodBindingEmitter methodToWrap, final GLEmitter emitter, final boolean bufferObjectVariant) {
    super(methodToWrap, emitter, bufferObjectVariant);
  }

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

  @Override
  protected StringBuilder appendSignature(final StringBuilder buf)  {
    buf.append(getBaseIndentString());
    appendNativeSignatureAnnotation(buf);
    return super.appendSignature(buf);
  }

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

  protected void appendNativeSignatureElement(final StringBuilder buf, final JavaType type, final Type cType, final 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()) {
      buf.append("A");
    } else if (type.isPrimitiveArray()) {
      buf.append("MO");
    } else if (type.isPrimitive()) {
      final Class<?> clazz = type.getJavaClass();
      if      (clazz == Byte.TYPE)      { buf.append("B"); }
      else if (clazz == Character.TYPE) { buf.append("C"); }
      else if (clazz == Double.TYPE)    { buf.append("D"); }
      else if (clazz == Float.TYPE)     { buf.append("F"); }
      else if (clazz == Integer.TYPE)   { buf.append("I"); }
      else if (clazz == Long.TYPE)      {
        // See if this is intended to be a pointer at the C level
        if (cType.isPointer()) {
          buf.append("A");
        } else {
          buf.append("J");
        }
      }
      else if (clazz == Short.TYPE)     { buf.append("S"); }
      else if (clazz == Boolean.TYPE)   { buf.append("Z"); }
      else throw new InternalError("Unhandled primitive type " + clazz);
    } else if (type.isString()) {
      buf.append("A");
    } else {
      throw new RuntimeException("Type not yet handled: " + type);
    }
  }

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

  @Override
  protected void emitPreCallSetup(final MethodBinding binding) {
    super.emitPreCallSetup(binding);
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final JavaType type = binding.getJavaArgumentType(i);
      if (type.isNIOBuffer() && !useNIODirectOnly ) {
        // 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
        unit.emitln("  Object " + getNIOBufferArrayName(i) + " = (_direct ? null : Buffers.getArray(" +
                       getArgumentName(i) + "));");
      } else if (type.isString()) {
        unit.emitln("    long " + binding.getArgumentName(i) + "_c_str = BuffersInternal.newCString(" + binding.getArgumentName(i) + ");");
      }
      // FIXME: going to need more of these for Buffer[] and String[], at least
    }
  }

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

  @Override
  protected int appendArguments(final StringBuilder buf) {
    boolean needComma = false;
    int numEmitted = 0;

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

    if (isPrivateNativeMethod() && binding.hasContainingType()) {
      if (needComma) {
        buf.append(", ");
      }

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

    for (int i = 0; i < binding.getNumArguments(); i++) {
      final 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) {
        buf.append(", ");
      }

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

      ++numEmitted;
      needComma = true;

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

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

  @Override
  protected void emitReturnVariableSetupAndCall(final MethodBinding binding) {
    unit.emit("    ");
    final JavaType returnType = binding.getJavaReturnType();
    boolean needsResultAssignment = false;

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

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

    if (needsResultAssignment) {
      unit.emit("  _res = ");
      if (returnType.isString()) {
        unit.emit("BuffersInternal.newJavaString(");
      } else if (returnType.isNIOByteBuffer()) {
        unit.emit("BuffersInternal.newDirectByteBuffer(");
      }
    } else {
      unit.emit("  ");
      if (!returnType.isVoid()) {
        unit.emit("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);
      if (returnType.isString() || returnType.isNIOByteBuffer()) {
        unit.emit(")");
      }
      unit.emit(";");
      unit.emitln();
    } else {
      emitCall(binding);
      if (returnType.isString() || returnType.isNIOByteBuffer()) {
        unit.emit(")");
      }
      unit.emit(";");
    }

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

  protected int emitCallArguments(final MethodBinding binding, final 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) {
      unit.emit("__addr_");
      needComma = true;
      ++numArgsEmitted;
    }

    if (binding.hasContainingType()) {
      // Emit this pointer
      assert(binding.getContainingType().isCompoundTypeWrapper());
      unit.emit("BuffersInternal.getDirectBufferAddress(");
      unit.emit("getBuffer()");
      unit.emit(")");
      needComma = true;
      ++numArgsEmitted;
    }
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final 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) {
        unit.emit(", ");
      }

      if (type.isCompoundTypeWrapper()) {
        unit.emit("BuffersInternal.getDirectBufferAddress(");
        unit.emit("((");
      }

      if (type.isNIOBuffer()) {
        if (!direct) {
          unit.emit(getNIOBufferArrayName(i));
        } else {
          unit.emit("BuffersInternal.getDirectBufferAddress(");
          unit.emit(getArgumentName(i));
          unit.emit(")");
        }
      } else {
        unit.emit(getArgumentName(i));
      }

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

      if (type.isNIOBuffer()) {
        if (direct) {
          unit.emit("+ Buffers.getDirectBufferByteOffset(" + getArgumentName(i) + ")");
        } else {
          unit.emit(", BuffersInternal.arrayBaseOffset(" +
                       getNIOBufferArrayName(i) +
                       ") + Buffers.getIndirectBufferByteOffset(" + getArgumentName(i) + ")");
        }
      } else if (type.isNIOBufferArray()) {
        unit.emit(", " + byteOffsetArrayArgName(i));
      }

      // Add Array offset parameter for primitive arrays
      if (type.isPrimitiveArray()) {
        unit.emit(", ");
        unit.emit("BuffersInternal.arrayBaseOffset(" + getArgumentName(i) + ") + ");
        if(type.isFloatArray()) {
          unit.emit("Buffers.SIZEOF_FLOAT * ");
        } else if(type.isDoubleArray()) {
          unit.emit("Buffers.SIZEOF_DOUBLE * ");
        } else if(type.isByteArray()) {
          unit.emit("1 * ");
        } else if(type.isLongArray()) {
          unit.emit("Buffers.SIZEOF_LONG * ");
        } else if(type.isShortArray()) {
          unit.emit("Buffers.SIZEOF_SHORT * ");
        } else if(type.isIntArray()) {
          unit.emit("Buffers.SIZEOF_INT * ");
        } else {
          throw new GlueGenException("Unsupported type for calculating array offset argument for " +
                                     getArgumentName(i) +
                                     "-- error occurred while processing Java glue code for " + binding.getCSymbol().getAliasedString(),
                                     binding.getCSymbol().getASTLocusTag());
        }
        unit.emit(offsetArgName(i));
      }

      if (type.isString()) {
        unit.emit("_c_str");
      }

      if (type.isCompoundTypeWrapper()) {
        unit.emit(")");
      }

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

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

    super.emitCallResultReturn(binding);
  }

  @Override
  public String getNativeName() {
    final String res = super.getNativeName();
    if (isPrivateNativeMethod() && bufferObjectVariant) {
      return res + "BufObj";
    }
    return res;
  }

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