'use strict';
/**
 * Module dependencies.
 */

const Type = require('./type');
const assert = require('assert');
const debug = require('debug')('ffi:cif_var');
const ref = require('ref-napi');
const bindings = require('./bindings');
const POINTER_SIZE = ref.sizeof.pointer;
const ffi_prep_cif_var = bindings.ffi_prep_cif_var;
const FFI_CIF_SIZE = bindings.FFI_CIF_SIZE;
const FFI_DEFAULT_ABI = bindings.FFI_DEFAULT_ABI;
  // status codes
const FFI_OK = bindings.FFI_OK;
const FFI_BAD_TYPEDEF = bindings.FFI_BAD_TYPEDEF;
const FFI_BAD_ABI = bindings.FFI_BAD_ABI;

/**
 * JS wrapper for the `ffi_prep_cif_var` function.
 * Returns a Buffer instance representing a variadic `ffi_cif *` instance.
 */

function CIF_var (rtype, types, numFixedArgs, abi) {
  debug('creating `ffi_cif *` instance with `ffi_prep_cif_var()`');

  // the return and arg types are expected to be coerced at this point...
  assert(!!rtype, 'expected a return "type" object as the first argument');
  assert(Array.isArray(types), 'expected an Array of arg "type" objects as the second argument');
  assert(numFixedArgs >= 1, 'expected the number of fixed arguments to be at least 1');

  // the buffer that will contain the return `ffi_cif *` instance
  const cif = Buffer.alloc(FFI_CIF_SIZE);

  const numTotalArgs = types.length;
  const _argtypesptr = Buffer.alloc(numTotalArgs * POINTER_SIZE);
  const _rtypeptr = Type(rtype);

  for (let i = 0; i < numTotalArgs; i++) {
    const ffiType = Type(types[i]);
    _argtypesptr.writePointer(ffiType, i * POINTER_SIZE);
  }

  // prevent GC of the arg type and rtn type buffers (not sure if this is required)
  cif.rtnTypePtr = _rtypeptr;
  cif.argTypesPtr = _argtypesptr;

  if (typeof abi === 'undefined') {
    debug('no ABI specified (this is OK), using FFI_DEFAULT_ABI');
    abi = FFI_DEFAULT_ABI;
  }

  const status = ffi_prep_cif_var(cif, numFixedArgs, numTotalArgs, _rtypeptr, _argtypesptr, abi);

  if (status !== FFI_OK) {
    switch (status) {
      case FFI_BAD_TYPEDEF:
      {
        const err = new Error('ffi_prep_cif_var() returned an FFI_BAD_TYPEDEF error');
        err.code = 'FFI_BAD_TYPEDEF';
        err.errno = status;
        throw err;
      }
      case FFI_BAD_ABI:
      {
        const err = new Error('ffi_prep_cif_var() returned an FFI_BAD_ABI error');
        err.code = 'FFI_BAD_ABI';
        err.errno = status;
        throw err;
      }
      default:
      {
        const err = new Error('ffi_prep_cif_var() returned an error: ' + status);
        err.errno = status;
        throw err;
      }
    }
  }

  return cif;
}

module.exports = CIF_var;
