// ---------------------------------------------------------------------------
// - Library.cpp                                                             -
// - standard object library - shared library class implementation           -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2011 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Vector.hpp"
#include "Library.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "cshl.hpp"

namespace afnix {
  
  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // this function compute the dynamic entry point by adding the "dli" prefix
  // and mapping the '-' character to '_' character
  static String map_handle (const String& name) {
    String data;
    long   len = name.length ();
    for (long i = 0; i < len; i++) {
      char c = name[i];
      if ((c == '-') || (c == '+')) c = '_';
      data = data + c;
    }
    String result = "dli_";
    result = result + data;
    return result;
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a self library name

  Library::Library  (void) {
    d_name = "main";
    d_rflg = false;
    d_iflg = false;
    p_hand = c_dlmain ();
    // cannot open main handle
    if (p_hand == nilp) {
      throw Exception ("name-error", "cannot open self library");
    }
  }

  // create a new shared library object

  Library::Library (const String& name) {
    // register the library name and flag
    d_name = name;
    d_rflg = false;
    d_iflg = false;
    p_hand = nilp;
    // try to open the library
    char* lname = name.tochar ();
    p_hand = c_dlopen (lname);
    delete [] lname;
    // not found
    if (p_hand == nilp) {
      throw Exception ("name-error", "cannot open library", name);
    }
  }

  // create a registered library by name and handle

  Library::Library (const String& name, void* hand) {
    // register the library name and flag
    d_name = name;
    d_rflg = true;
    d_iflg = false;
    p_hand = hand;
    // check the handle
    if (p_hand == nilp) {
      throw Exception ("handle-error", "cannot register library", name);
    }
  }

  // delete this shared library by closing it

  Library::~Library (void) {
    if (d_rflg == false) c_dlclose (p_hand);
  }
  
  // return the class name

  String Library::repr (void) const {
    return "Library";
  }

  // return the library name

  String Library::getname (void) const {
    return d_name;
  }

  // try to find a symbol by name in this library - this is for internal test

  void* Library::find (const String& name) const {
    char* sname = name.tochar ();
    void* sym   = c_dlsym (sname, p_hand);
    delete [] sname;
    if (sym == nilp) 
      throw Exception ("library-error", "cannot find symbol", name);
    return sym;
  }

  // initialize a dynamic library

  Object* Library::dlinit (Runnable* robj, Vector* argv) const {
    wrlock ();
    if (d_iflg == true) {
      unlock ();
      return nilp;
    }
    try {
      // get the symbol
      void* sym = d_rflg ? p_hand : find (map_handle (d_name));
      // map the symbol and call it
      typedef Object* (*t_func) (Runnable*, Vector*);
      t_func func = (t_func) sym;
      Object* result = func (robj, argv);
      // post and mark initialized
      robj->post (result);
      d_iflg = true;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 1;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_INITIALIZE = zone.intern ("initialize");

  // create a new shared library in a generic way

  Object* Library::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Library;
    // check for 1 argument
    if (argc == 1) {
      String name = argv->getstring (0);
      return new Library (name);
    }
    // invalid argument 
    throw Exception ("argument-error", 
		     "invalid number of arguments with library");
  }

  // return true if the given quark is defined

  bool Library::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }      
    bool result = hflg ? Nameable::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }
  
  // apply this object with a set of arguments and a quark

  Object* Library::apply (Runnable* robj, Nameset* nset, const long quark,
			  Vector* argv) {
    // map the generic procedure
    if (quark == QUARK_INITIALIZE) {
      wrlock ();
      try {
	Object* result = dlinit (robj, argv);
	robj->post (result);
	unlock ();
	return result;
      } catch (...) {
	unlock ();
	throw;
      }
    }

    // call the nameable method
    return Nameable::apply (robj, nset, quark, argv);
  }
}
