/* Copyright (c) 2014 Sanjay Dasgupta, All Rights Reserved
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package sna

import com.sun.jna.{Function => JNAFunction}

import scala.collection.mutable
import scala.language.dynamics

class Library(val libName: String) extends Dynamic {

  class Invocation(val jnaFunction: JNAFunction, val args: Array[Object]) {

    // TODO: this does not call without a passed type parameter
    def apply[R](implicit m: Manifest[R]): R = {
      //println("invoking " + jnaFunction.getName + ". class " + m.runtimeClass.toString)
      if (m.runtimeClass == classOf[Unit]) {
        jnaFunction.invoke(args).asInstanceOf[R]
      } else {
        jnaFunction.invoke(m.runtimeClass, args).asInstanceOf[R]
      }
    }

    def as[R](implicit m: Manifest[R])           = apply[R](m)
    def asInstanceOf[R](implicit m: Manifest[R]) = apply[R](m)
  }

  def applyDynamic(functionName: String)(args: Any*) = {
    new Invocation(loadFunction(functionName), args.map(_.asInstanceOf[Object]).toArray[Object])
  }

  private def loadFunction(functionName: String): JNAFunction = {
    var jnaFunction: JNAFunction = null
    if (functionCache.contains(functionName)) {
      jnaFunction = functionCache(functionName)
    } else {
      jnaFunction = JNAFunction.getFunction(libName, functionName)
      functionCache(functionName) = jnaFunction
    }

    jnaFunction
  }

  def prefetch(functionName: String): Unit = {
    loadFunction(functionName)
  }

  private val functionCache = mutable.Map.empty[String, JNAFunction]
}
