// ---------------------------------------------------------------------------
// - LocalSpace.cpp                                                          -
// - afnix:csm module - abstract workspace 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Date.hpp"
#include "Vector.hpp"
#include "System.hpp"
#include "Utility.hpp"
#include "FileInfo.hpp"
#include "InputFile.hpp"
#include "Directory.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "OutputFile.hpp"
#include "LocalSpace.hpp"

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

  // the local space temporary sub directory
  static const String LSP_TSUB_DEF = ".local-space-tmp";

  // map a workspace zone
  static inline String lsp_map_zone (const String& root, const String& zone) {
    return System::join (root, zone);
  }

  // map a workspace file name by zone
  static inline String lsp_map_path (const String& root, const String& zone,
				     const String& name) {
    String zpth = lsp_map_zone (root, zone);
    String path = System::join (zpth, name);
    return path;
  }

  // map a workspace path by root, zone and uri
  static inline String lsp_uri_path (const String& root, const String& zone,
				     const Uri& uri) {
    // check the uri scheme
    String schm = uri.getscheme ();
    if (schm != WorkSpace::URI_SCHM_DEF) {
      throw Exception ("workspace-error", 
		       "invalid uri scheme in local space", schm);
    }
    // get the full path
    String name = uri.getpath().rsubstr (1);
    String path = lsp_map_path (root, zone, name);
    return path;
  }

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

  // create a default localspace

  LocalSpace::LocalSpace (void) {
    // preset root and flags
    d_root = System::join (System::gettdir (), LSP_TSUB_DEF);
    d_tdir = d_root;
    d_gtmp = true;
    // clean old directory if any
    Directory::rmall (d_tdir);
    // make sure the root directory exists
    if (System::isdir (d_root) == false) System::mkdir (d_root);
    if (System::isdir (d_root) == false) {
      throw Exception ("workspace-error", "cannot create local root directory",
		       d_root);
    }
  }

  // create a localspace by root directory

  LocalSpace::LocalSpace (const String& root) {
    d_root = root;
    d_tdir = System::join (d_root, LSP_TSUB_DEF);
    d_gtmp = false;
    // make sure the root directory exists
    if (System::isdir (d_root) == false) System::mhdir (d_root);
    if (System::isdir (d_root) == false) {
      throw Exception ("workspace-error", "cannot create local root directory",
		       d_root);
    }
  }

  // create a localspace by name, info and root directory

  LocalSpace::LocalSpace (const String& name, const String& info,
			  const String& root) : WorkSpace (name, info) {
    d_root = root;
    d_tdir = System::join (d_root, LSP_TSUB_DEF);
    d_gtmp = false;
    // make sure the root directory exists
    if (System::isdir (d_root) == false) System::mhdir (d_root);
    if (System::isdir (d_root) == false) {
      throw Exception ("workspace-error", "cannot create local root directory",
		       d_root);
    }
  }

  // destroy the local space

  LocalSpace::~LocalSpace (void) {
   if (d_gtmp == true) Directory::rmall (d_tdir);
  }

  // return the class name

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

  // get the root directoryy

  String LocalSpace::getroot (void) const {
    rdlock ();
    try {
      String result = d_root;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // check if a zone exists

  bool LocalSpace::iszone (const String& zone) const {
    rdlock ();
    try {
      // map the zone path
      String zpth = lsp_map_zone (d_root, zone);
      // check for a zone
      bool result = System::isdir (zpth);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a new working zone by name
  
  bool LocalSpace::addzone (const String& zone) {
    wrlock ();
    try {
      // get the use path
      String path = System::join (d_root, zone);
      // check if the directory already exists
      if (System::isdir (path) == true) {
	unlock ();
	return true;
      }
      // try to create the directory
      bool result = System::mkdir (path);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // check if an entity exists by zone and uri

  bool LocalSpace::exists (const String& zone, const Uri& uri) const {
    rdlock ();
    try {
      // map the uri to a path
      String path = lsp_uri_path (d_root, zone, uri);
      // check for valid path
      bool result = System::isfile (path);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  String LocalSpace::tonuri (const String& zone, const String& name) const {
    rdlock ();
    try {
      // map the name by zone
      String path = lsp_map_path (d_root, zone, name);
      // check for existence
      if (System::isfile (path) == false) {
	unlock ();
	return "";
      }
      // normalize it
      String result = Uri::nrmname (path);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a workspace zone file list

  Strvec* LocalSpace::getfiles (const String& zone) const {
    rdlock ();
    try {
      // check if the zone exists
      if (iszone (zone) == false) {
	unlock ();
	return nilp;
      }
      // map the zone
      String zpth = lsp_map_zone (d_root, zone);
      // locally map it as a directory
      Directory zdir (zpth);
      // get the file list
      Strvec* result = zdir.getfiles ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a workspace zone file table

  PrintTable* LocalSpace::tofptbl (const String& zone) const {
    rdlock ();
    PrintTable* ptbl = nilp;
    try {
      // prepare the print table
      ptbl = new PrintTable (3);
      ptbl->sethead (0, "Name");
      ptbl->sethead (1, "Size");
      ptbl->sethead (2, "Modification Time");
      // get the file list by zone
      Strvec* flst = getfiles (zone);
      if (flst == nilp) {
	unlock ();
	return ptbl;
      }
      // loop in the list
      long flen = flst->length ();
      for (long k = 0; k < flen; k++) {
	// get the file name and path
	String name = flst->get (k);
	String path = lsp_map_path (d_root, zone, name);
	// get the file information
	FileInfo info (path);
	Date     mtim (info.mtime ());
	// set the table
	long row = ptbl->add ();
	ptbl->set (row, 0, name);
	ptbl->set (row, 1, Utility::tostring (info.length ()));
	ptbl->set (row, 2, mtim.toiso (true));
      }
      unlock ();
      return ptbl;
    } catch (...) {
      delete ptbl;
      unlock ();
      throw;
    }
  }

  // get an input stream by zone and uri

  InputStream* LocalSpace::getis (const String& zone, const Uri& uri) const {
    rdlock ();
    try {
      // map the uri to a path
      String path = lsp_uri_path (d_root, zone, uri);
      // create a local input stream
      InputStream* is = new InputFile (path);
      unlock ();
      return is;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get an output stream by zone and uri

  OutputStream* LocalSpace::getos (const String& zone, const Uri& uri) const {
    rdlock ();
    try {
      // map the uri to a path
      String path = lsp_uri_path (d_root, zone, uri);
      // create a local input stream
      OutputStream* os = new OutputFile (path);
      unlock ();
      return os;
    } 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_GETROOT  = zone.intern ("get-root");

  // create a new object in a generic way

  Object* LocalSpace::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 arguments
    if (argc == 0) return new LocalSpace;
    // check for 1 argument
    if (argc == 1) {
      String root = argv->getstring (0);
      return new LocalSpace (root);
    }
    // check for 3 arguments
    if (argc == 3) {
      String name = argv->getstring (0);
      String info = argv->getstring (1);
      String root = argv->getstring (2);
      return new LocalSpace (name, info, root);
    }
    throw Exception ("argument-error",
		     "too many argument with local space ");
  }

  // return true if the given quark is defined

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

  // apply this object with a set of arguments and a quark
  
  Object* LocalSpace::apply (Runnable* robj, Nameset* nset, const long quark,
			    Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) {
      if (quark == QUARK_GETROOT) return new String (getroot ());
    }
    // call the workspace method
    return WorkSpace::apply (robj, nset, quark, argv);
  }
}
