//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "Library.hpp"

#include "Information.hpp"
#include <QFileInfo>

Library::Library(QString path) : filename(path), handle(0) {}

#if defined(linux) || defined(__linux__)

#  include <QFile>

#  include <dlfcn.h>

bool Library::load()
{
  QByteArray ba = filename.toLocal8Bit();
  handle = dlopen(ba.data(), RTLD_NOW | RTLD_GLOBAL);
  if(!handle) {
    error_string = QString::fromLocal8Bit(dlerror());
    return false;
  } else {
    error_string = "No Error";
    return true;
  }
}

bool Library::unload()
{
  if(dlclose(handle)) {
    char* err = dlerror();
    error_string = QString::fromLocal8Bit(err);
    return false;
  } else {
    handle = 0;
    error_string = "No Error";
    return true;
  }
}

bool Library::isLibrary(QString path)
{
  return path.endsWith(".so", Qt::CaseInsensitive);
  // FIXME: this is dead code
  if(path.endsWith(".so", Qt::CaseInsensitive)) {
    // Check magic number
    // First, ELF
    QFile f(path);
    if(!f.open(QIODevice::ReadOnly))
      return false;

    const char elf[4] = { '\177', 'E', 'L', 'F' };
    char buffer[4];
    f.read(buffer, 4);
    if(memcmp(elf, buffer, 4) != 0)
      return false;

    quint8 sys;
    // Check 32/64 bits
    if(!f.seek(4))
      return false;
    f.read((char*)&sys, 1);
#  ifdef __x86_64
    if(sys != 2)
      return false;
// err << "  64 bits" << endl;
#  else
    if(sys != 1)
      return false;
// err << "  32 bits" << endl;
#  endif

    quint16 typ;
    // Checked shared lib
    if(!f.seek(16))
      return false;
    f.read((char*)&typ, 2);
    if(typ != 3)
      return false;
    // err << "  shared lib" << endl;
    f.close();
    return true;
  }
  return false;
}

#elif defined(__APPLE__)

#  include <QFileInfo>
#  include <QProcess>
#  include <stdio.h>
#  include <mach-o/dyld.h>

bool Library::load()
{
  // Success or failure result value
  NSObjectFileImage img;   // Represents the bundle's object file
  /* Get an object file for the bundle. */
  QByteArray ba = filename.toLocal8Bit();
  int rc = NSCreateObjectFileImageFromFile(ba.data(), &img);
  if(rc != NSObjectFileImageSuccess) {
    error_string = QString("Could not load %1.").arg(filename);
    return false;
  }
  /* Get a handle for the bundle. */
  handle = NSLinkModule(img, ba.data(), NSLINKMODULE_OPTION_RETURN_ON_ERROR);
  if(!handle) {
    error_string = QString("Could not load %1.").arg(filename);
    return false;
  }
  return true;
}

bool Library::unload()
{
  if(!NSUnLinkModule((NSModule)handle, NSUNLINKMODULE_OPTION_NONE)) {
    error_string = QString("Could not unload %1.").arg(filename);
    return false;
  }
  handle = 0;
  return true;
}

bool Library::isLibrary(QString path)
{
  QFileInfo fi(path);
  if(fi.exists() and (path.endsWith(".so", Qt::CaseInsensitive) or path.endsWith(".dylib", Qt::CaseInsensitive)
                      or path.endsWith(".bundle", Qt::CaseInsensitive))) {
    QProcess filetype;
    filetype.start("file", QStringList() << path);
    if(!filetype.waitForStarted(1000))
      return false;
    if(!filetype.waitForFinished(1000))
      return false;
    QString str = QString::fromLocal8Bit(filetype.readAllStandardOutput());
    QStringList lines = str.split("\n");
    // First, check Mach-O
    QString macho("Mach-O");
    if(lines.filter(macho).isEmpty())
      return false;
    //  err << "  Mach-O" << endl;
    // Then, test shared lib
    QString dylib("dynamically linked shared library");
    QString bundle("bundle");
    if(lines.filter(dylib).isEmpty() and lines.filter(bundle).isEmpty())
      return false;
//  err << "  DLL" << endl;
#  ifdef __x86_64
    QString arch("x86_64");
#  else
    QString arch("i386");
#  endif
    if(lines.filter(arch).isEmpty())
      return false;
    return true;
  }
  return false;
}

#elif defined(WIN32) || defined(WIN64)

bool Library::load()
{
  QByteArray ba = filename.toLocal8Bit();
  handle = LoadLibrary(ba);
  if(!handle) {
    char msg[1000];
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), 0, msg, 1000, NULL);
    error_string = QString::fromLocal8Bit(msg);
    return false;
  }
  return true;
}

bool Library::unload()
{
  if(!FreeLibrary(handle)) {
    error_string = "Error while closing DLL";
    return false;
  }
  handle = 0;
  return true;
}

bool Library::isLibrary(QString path)
{
  return QFileInfo(path).exists() and path.endsWith(".dll", Qt::CaseInsensitive);
}

#else
#  error "MorphoGraphX doesn't know how to load libraries on your system. Please implement Library.cpp"
#endif
