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

const ref = require('ref-napi');
const assert = require('assert');
const bindings = require('./bindings');
const Callback = require('./callback');
const ForeignFunction = require('./foreign_function');
const debug = require('debug')('ffi:FunctionType');

/**
 * Module exports.
 */

module.exports = Function;

/**
 * Creates and returns a "type" object for a C "function pointer".
 *
 * @api public
 */

function Function (retType, argTypes, abi) {
  if (!(this instanceof Function)) {
    return new Function(retType, argTypes, abi);
  }

  debug('creating new FunctionType');

  // check args
  assert(!!retType, 'expected a return "type" object as the first argument');
  assert(Array.isArray(argTypes), 'expected Array of arg "type" objects as the second argument');

  // normalize the "types" (they could be strings, so turn into real type
  // instances)
  this.retType = ref.coerceType(retType);
  this.argTypes = argTypes.map(ref.coerceType);
  this.abi = null == abi ? bindings.FFI_DEFAULT_ABI : abi;
}

/**
 * The "ffi_type" is set for node-ffi functions.
 */

Function.prototype.ffi_type = bindings.FFI_TYPES.pointer;

/**
 * The "size" is always pointer-sized.
 */

Function.prototype.size = ref.sizeof.pointer;

/**
 * The "alignment" is always pointer-aligned.
 */

Function.prototype.alignment = ref.alignof.pointer;

/**
 * The "indirection" is always 1 to ensure that our get()/set() get called.
 */

Function.prototype.indirection = 1;

/**
 * Returns a ffi.Callback pointer (Buffer) of this function type for the
 * given `fn` Function.
 */

Function.prototype.toPointer = function toPointer (fn) {
  return Callback(this.retType, this.argTypes, this.abi, fn);
};

/**
 * Returns a ffi.ForeignFunction (Function) of this function type for the
 * given `buf` Buffer.
 */

Function.prototype.toFunction = function toFunction (buf) {
  return ForeignFunction(buf, this.retType, this.argTypes, this.abi);
};

/**
 * get function; return a ForeignFunction instance.
 */

Function.prototype.get = function get (buffer, offset) {
  debug('ffi FunctionType "get" function');
  const ptr = buffer.readPointer(offset);
  return this.toFunction(ptr);
};

/**
 * set function; return a Callback buffer.
 */

Function.prototype.set = function set (buffer, offset, value) {
  debug('ffi FunctionType "set" function');
  let ptr;
  if ('function' == typeof value) {
    ptr = this.toPointer(value);
  } else if (Buffer.isBuffer(value)) {
    ptr = value;
  } else {
    throw new Error('don\'t know how to set callback function for: ' + value);
  }
  buffer.writePointer(ptr, offset);
};
