// ---------------------------------------------------------------------------
// - Resolver.cpp                                                            -
// - afnix engine - file path resolver 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-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Vector.hpp"
#include "System.hpp"
#include "Boolean.hpp"
#include "Resolver.hpp"
#include "Runnable.hpp"
#include "InputFile.hpp"
#include "Librarian.hpp"
#include "QuarkZone.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the resolver structure
  struct s_rpath {
    // the resolving path
    String d_path;
    // the librarian object
    Librarian* p_lib ;
    // next path in list
    s_rpath* p_next;
    // create a new path
    s_rpath (const String& path) {
      d_path = path;
      p_lib  = nilp;
      p_next = nilp;
      if (Librarian::valid (path) == true) {
	Object::iref (p_lib = new Librarian (path));
      } else {
	if (System::isdir (path) == false)
	  throw Exception ("path-error", "invalid path for resolver", path);
      }
    }
    // destroy this resolver path
    ~s_rpath (void) {
      Object::dref (p_lib);
      delete p_next;
    }
    // apend a path at the end of this one
    void append (s_rpath* rpath) {
      s_rpath* last = this;
      while (last->p_next != nilp) last = last->p_next;
      last->p_next = rpath;
    }
    // return true if the name is valid
    bool valid (const String& name) {
      // check if we have a librarian
      if ((p_lib != nilp) && (p_lib->exists (name) == true)) return true;
      // check for a directory
      String fpath = System::join (d_path, name);
      if (System::isfile (fpath) == true) return true;
      return false;
    }
    // return a path string if the name is valid
    String getpath (const String& name) {
      String result;
      // check if we have a librarian
      if ((p_lib != nilp) && (p_lib->exists (name) == true)) {
	result = p_lib->getname () + '!' + name;
	return result;
      }
      // check for a directory
      String fpath = System::join (d_path, name);
      if (System::isfile (fpath) == true) result = fpath;
      return result;
    }
    // return an input stream for this path
    Input* getstream (const String& name) {
      // check if we have a librarian
      if ((p_lib != nilp) && (p_lib->exists (name) == true)) 
	return p_lib->extract (name);
      // check for a directory
      String fpath = System::join (d_path, name);
      if (System::isfile (fpath) == true) return new InputFile (fpath);
      return nilp;
    }
  };

  // this procedure return true if a path alrady exists in a path list
  static bool exists_path (s_rpath* rpath, const String& path) {
    while (rpath != nilp) {
      if (rpath->d_path == path) return true;
      rpath = rpath->p_next;
    }
    return false;
  }

  // this procedure returns a descriptor if a match is found
  static s_rpath* find_rpath (s_rpath* rpath, const String& name) {
    while (rpath != nilp) {
      if (rpath->valid (name) == true) return rpath;
      rpath = rpath->p_next;
    }
    return nilp;
  }

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

  // create an empty resolver

  Resolver::Resolver (void) {
    p_rpath = nilp;
  }

  // create a resolver with on initial path

  Resolver::Resolver (const String& path) {
    // initialize to empty by default
    p_rpath = nilp;
    // add the initial path
    add (path);
  }

  // create a resolver with a list of path

  Resolver::Resolver (const Strvec& paths) {
    // initialize to empty by default
    p_rpath = nilp;
    // loop to add path
    long rlen = paths.length ();
    for (long i = 0; i < rlen; i++) add (paths.get (i));
  }

  // destroy this resolver

  Resolver::~Resolver (void) {
    delete p_rpath;
  }

  // return the class name

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

  // add a path to this resolver

  void Resolver::add (const String& path) {
    if (path.length () == 0) return;
    wrlock ();
    if (exists_path (p_rpath, path) == true) {
      unlock ();
      return;
    }
    // create a new path
    s_rpath* rpath = new s_rpath (path);
    if (p_rpath == nilp) {
      p_rpath = rpath;
    } else {
      p_rpath->append (rpath);
    }
    unlock ();
  }

  // return true if the name makes a valid path

  bool Resolver::valid (const String& name) const {
    // check first locally
    if (System::isfile (name) == true) return true;
    // look in the path
    rdlock ();
    try {
      bool result = (find_rpath (p_rpath, name) == nilp) ? false : true;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return a resolved path

  String Resolver::getpath (const String& name) const {
    // check locally
    if (System::isfile (name) == true) return name;
    rdlock ();
    String result;
    s_rpath* rpath = find_rpath (p_rpath, name);
    if (rpath != nilp) result = rpath->getpath (name);
    unlock ();
    return result;
  }

  // get an input stream by name

  Input* Resolver::get (const String& name) const {
    // check locally
    if (System::isfile (name) == true) return new InputFile (name);
    // check in the resolver
    rdlock ();
    try {
      s_rpath* rpath = find_rpath (p_rpath, name);
      Input* is = (rpath == nilp) ? nilp : rpath->getstream (name);
      unlock ();
      return is;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // lookup an input stream by name

  Input* Resolver::lookup (const String& name) const {
    // check locally
    if (System::isfile (name) == true) return new InputFile (name);
    // check in the resolver
    rdlock ();
    s_rpath* rpath = find_rpath (p_rpath, name);
    if (rpath == nilp) {
      unlock ();
      throw Exception ("resolver-error", "cannot resolve file", name);
    }
    try {
      Input* is = (rpath == nilp) ? nilp : rpath->getstream (name);
      unlock ();
      return is;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the afnix path name from a name

  String Resolver::alpname (const String& name) const {
    // check for an extension
    String fext = System::xext (name);
    if (fext.length () != 0)   return getpath (name);
    // check for name first
    if (valid (name) == true)  return getpath (name);
    // check for axc
    String fname = name + ".axc";
    if (valid (fname) == true) return getpath (fname);
    // check for als
    fname = name + ".als";
    if (valid (fname) == true) return getpath (fname);
    return "";
  }

  // return true if the name is a valid afnix file

  bool Resolver::alpvld (const String& name) const {
    // check for an extension
    String fext = System::xext (name);
    if (fext.length () != 0)   return valid (name);
    // check for name first
    if (valid (name) == true)  return valid (name);
    // check for axc
    String fname = name + ".axc";
    if (valid (fname) == true) return true;
    // check for als
    fname = name + ".als";
    if (valid (fname) == true) return true;
    return false;
  }

  // get an input stream for an afnix file

  Input* Resolver::alpget (const String& name) const {
    // check for an extension
    String fext = System::xext (name);
    if (fext.length () != 0)   return get (name);
    // check for name first
    if (valid (name) == true)  return get (name);
    // check for axc
    String fname = name + ".axc";
    if (valid (fname) == true) return get (fname);
    // check for als
    fname = name + ".als";
    if (valid (fname) == true) return get (fname);
    return nilp;
  }

  // lookup an input stream for an afnix file

  Input* Resolver::alplkp (const String& name) const {
    // check for an extension
    String fext = System::xext (name);
    if (fext.length () != 0)   return lookup (name);
    // check for name first
    if (valid (name) == true)  return lookup (name);
    // check for axc 
    String fname = name + ".axc";
    if (valid (fname) == true) return lookup (fname);
    // check for als
    fname = name + ".als";
    if (valid (fname) == true) return lookup (fname);
    throw Exception ("resolver-error", "cannot resolve file", name);
  }

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

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

  // the object supported quarks
  static const long QUARK_ADD    = zone.intern ("add");
  static const long QUARK_VALID  = zone.intern ("valid-p");
  static const long QUARK_LOOKUP = zone.intern ("lookup");

  // create a new object in a generic way

  Object* Resolver::mknew (Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // check for 0 argument
    if (argc == 0) return new Resolver;
    // check for 1 argument
    if (argc == 1) {
      String path = argv->getstring (0);
      return new Resolver (path);
    }
    // illegal arguments
    throw Exception ("argument-error", "too many arguments with resolver");
  }

  // return true if the given quark is defined

  bool Resolver::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* Resolver::apply (Runnable* robj, Nameset* nset, const long quark,
			   Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_ADD) {
	String fpath = argv->getstring (0);
	add (fpath);
	return nilp;
      }
      if (quark == QUARK_VALID) {
	String fpath = argv->getstring (0);
	return new Boolean (valid (fpath));
      }
      if (quark == QUARK_LOOKUP) {
	rdlock ();
	try {
	  String fpath = argv->getstring (0);
	  Object* result = lookup (fpath);
	  robj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
