text: 'use strict';
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief JavaScript base module
  // /
  // / @file
  // /
  // / DISCLAIMER
  // /
  // / Copyright 2004-2013 triAGENS GmbH, Cologne, Germany
  // /
  // / Licensed under the Apache License, Version 2.0 (the "License")
  // / you may not use this file except in compliance with the License.
  // / You may obtain a copy of the License at
  // /
  // /     http://www.apache.org/licenses/LICENSE-2.0
  // /
  // / Unless required by applicable law or agreed to in writing, software
  // / distributed under the License is distributed on an "AS IS" BASIS,
  // / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // / See the License for the specific language governing permissions and
  // / limitations under the License.
  // /
  // / Copyright holder is triAGENS GmbH, Cologne, Germany
  // /
  // / @author Dr. Frank Celler
  // / @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany
  // //////////////////////////////////////////////////////////////////////////////
  
  var internal = require('internal');
  var fs = require('fs');
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief errors
  // //////////////////////////////////////////////////////////////////////////////
  
  Object.keys(internal.errors).forEach(function (key) {
    exports[key] = internal.errors[key].code;
  });
  
  exports.aql = function (strings, ...args) {
    const bindVars = {};
    const bindVals = [];
    let query = strings[0];
    for (let i = 0; i < args.length; i++) {
      const rawValue = args[i];
      let value = rawValue;
      if (rawValue && typeof rawValue.toAQL === 'function') {
        query += `${rawValue.toAQL()}${strings[i + 1]}`;
        continue;
      }
      const index = bindVals.indexOf(rawValue);
      const isKnown = index !== -1;
      let name = `value${isKnown ? index : bindVals.length}`;
      if (rawValue && rawValue.isArangoCollection) {
        name = `@${name}`;
        value = rawValue.name();
      }
      if (!isKnown) {
        bindVals.push(rawValue);
        bindVars[name] = value;
      }
      query += `@${name}${strings[i + 1]}`;
    }
    return {query, bindVars};
  };
  
  exports.errors = internal.errors;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief ArangoError
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.ArangoError = internal.ArangoError;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief defines a module
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.defineModule = function (path, file) {
    var content;
    var m;
    var mc;
  
    content = fs.read(file);
  
    mc = internal.db._collection('_modules');
  
    if (mc === null) {
      mc = internal.db._create('_modules', { isSystem: true });
    }
  
    path = module.normalize(path);
    m = mc.firstExample({ path: path });
  
    if (m === null) {
      mc.save({ path: path, content: content });
    }else {
      mc.replace(m, { path: path, content: content });
    }
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief normalizeURL
  // /
  // / If @FA{path} starts with "." or "..", then it is a relative path.
  // / Otherwise it is an absolute path. Normalizing will remove `//`,
  // / `/./`, `/../` from the url - expect in the beginning, where it keeps
  // / `../` and or at most one `./`.
  // /
  // / If @FA{path} is empty, the url `./` will be returned.
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.normalizeURL = function (path) {
    var i;
    var n;
    var p;
    var q;
    var r;
    var x;
  
    if (path === '') {
      return './';
    }
  
    p = path.split('/');
  
    // relative path
    if (p[0] === '.' || p[0] === '..') {
      r = p[0] + '/';
      p.shift();
      q = p;
    }
  
    // absolute path
    else if (p[0] === '') {
      r = '/';
      p.shift();
      q = p;
    }
  
    // assume that the path is relative
    else {
      r = './';
      q = p;
    }
  
    // normalize path
    n = [];
  
    for (i = 0;  i < q.length;  ++i) {
      x = q[i];
  
      if (x === '..') {
        if (n.length === 0) {
          if (r === '../') {
            n.push(x);
          }
          else if (r === './') {
            r = '../';
          }else {
            throw "cannot use '..' to escape top-level-directory";
          }
        }
        else if (n[n.length - 1] === '..') {
          n.push(x);
        }else {
          n.pop();
        }
      }
      else if (x !== '' && x !== '.') {
        n.push(x);
      }
    }
  
    return r + n.join('/');
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief inspect
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.inspect = internal.inspect;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief output
  // /
  // / In order to allow "capture" output to work, we cannot assigne the
  // / function here.
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.output = function () {
    internal.output.apply(internal.output, arguments);
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief print
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.print = internal.print;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief printf
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.printf = internal.printf;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief sprintf
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.sprintf = internal.sprintf;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief printObject
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.printObject = internal.printObject;
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief 2D ASCII table printing
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.printTable = function (list, columns, options) {
    options = options || { };
    if (options.totalString === undefined) {
      options.totalString = '%s document(s)\n';
    }
  
    var pad = '...';
    var descriptions, matrix, col, what, j;
  
    if (columns === undefined) {
      what = list[0];
    }
    else if (Array.isArray(columns)) {
      what = { };
  
      columns.forEach(function (col) {
        what[col] = null;
      });
    }else {
      what = columns;
    }
  
    j = 0;
    descriptions = [];
    matrix = [ [] ];
  
    for (col in what) {
      if (what.hasOwnProperty(col)) {
        var fixedLength = null;
  
        if (columns && columns.hasOwnProperty(col) && columns[col] > 0) {
          fixedLength = columns[col] >= pad.length ? columns[col] : pad.length;
        }
  
        // header
        var name = col;
  
        // rename header?
        if (options.hasOwnProperty('rename')) {
          if (options.rename.hasOwnProperty(col)) {
            name = options.rename[col];
          }
        }
  
        descriptions.push({
          id: col,
          fixedLength: fixedLength,
          length: fixedLength || name.length
        });
  
        matrix[0][j++] = name;
      }
    }
  
    // determine values & max widths
    list.forEach(function (row, i) {
      matrix[i + 1] = [];
      descriptions.forEach(function (col) {
        if (row.hasOwnProperty(col.id)) {
          var value;
          if (options.prettyStrings && typeof row[col.id] === 'string') {
            value = row[col.id];
          }else {
            value = JSON.stringify(row[col.id]) || '';
          }
  
          matrix[i + 1].push(value);
  
          if (value.length > col.length && ! col.fixedLength) {
            col.length = Math.min(value.length, 100);
          }
        }else {
          // undefined
          matrix[i + 1].push('');
        }
      });
    });
  
    var divider = function () {
      var parts = [];
      descriptions.forEach(function (desc) {
        parts.push(exports.stringPadding('', desc.length, '-', 'r'));
      });
  
      if (options.framed) {
        return '+-' + parts.join('-+-') + '-+\n';
      }
  
      return parts.join('   ') + '\n';
    };
  
    var compose = function () {
      var result = '';
  
      if (options.framed) {
        result += divider();
      }
      matrix.forEach(function (row, i) {
        var parts = [];
  
        row.forEach(function (col, j) {
          var len = descriptions[j].length, value = row[j];
          if (value.length > len) {
            value = value.substr(0, len - pad.length) + pad;
          }
          parts.push(exports.stringPadding(value, len, ' ', 'r'));
        });
  
        if (options.framed) {
          result += '| ' + parts.join(' | ') + ' |\n';
        }else {
          result += parts.join('   ') + '\n';
        }
  
        if (i === 0) {
          result += divider();
        }
      });
  
      result += divider();
  
      if (! options.hideTotal) {
        result += internal.sprintf(options.totalString, String(list.length));
      }
      return result;
    };
  
    if (! Array.isArray(list)) {
      // not an array
      return;
    }
  
    if (list.length === 0) {
      exports.print(options.emptyString || 'no document(s)');
    }else {
      exports.print(compose());
    }
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief stringPadding
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.stringPadding = function (str, len, pad, dir) {
    // yes, this is more code than new Array(length).join(chr), but it makes jslint happy
    function fill (length, chr) {
      var result = '', i;
      for (i = 0; i < length; ++i) {
        result += chr;
      }
      return result;
    }
  
    if (typeof (len) === 'undefined') {
      len = 0;
    }
    if (typeof (pad) === 'undefined') {
      pad = ' ';
    }
  
    if (len + 1 >= str.length) {
      switch (dir || 'r') {
  
        // LEFT
        case 'l':
          str = fill(len + 1 - str.length, pad) + str;
          break;
  
        // BOTH
        case 'b':
          var padlen = len - str.length;
          var right = Math.ceil(padlen / 2);
          var left = padlen - right;
          str = fill(left + 1, pad) + str + fill(right + 1, pad);
          break;
  
        default:
          str = str + fill(len + 1 - str.length, pad);
          break;
      }
    }
  
    return str;
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief throws an error in case a download failed
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.throwDownloadError = function (msg) {
    throw new exports.ArangoError({
      errorNum: exports.errors.ERROR_SERVICE_DOWNLOAD_FAILED.code,
      errorMessage: exports.errors.ERROR_SERVICE_DOWNLOAD_FAILED.message + ': ' + String(msg)
    });
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief throws an error in case of missing file
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.throwFileNotFound = function (msg) {
    throw new exports.ArangoError({
      errorNum: exports.errors.ERROR_FILE_NOT_FOUND.code,
      errorMessage: exports.errors.ERROR_FILE_NOT_FOUND.message + ': ' + String(msg)
    });
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief throws an error in case of a bad parameter
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.throwBadParameter = function (msg) {
    throw new exports.ArangoError({
      errorNum: exports.errors.ERROR_BAD_PARAMETER.code,
      errorMessage: exports.errors.ERROR_BAD_PARAMETER.message + ': ' + String(msg)
    });
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief checks parameter, throws an error if missing
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.checkParameter = function (usage, descs, vars) {
    var i;
  
    for (i = 0;  i < descs.length;  ++i) {
      var desc = descs[i];
  
      if (typeof vars[i] === 'undefined') {
        exports.throwBadParameter(desc[0] + ' missing, usage: ' + usage);
      }
  
      if (typeof vars[i] !== desc[1]) {
        exports.throwBadParameter(desc[0] + " should be a '" + desc[1] + "', "
          + "not '" + (typeof vars[i]) + "'");
      }
    }
  };
  
  // //////////////////////////////////////////////////////////////////////////////
  // / @brief generate info message for newer version(s) available
  // //////////////////////////////////////////////////////////////////////////////
  
  exports.checkAvailableVersions = function (version) {
    var console = require('console');
    var log;
  
    if (require('@arangodb').isServer) {
      log = console.info;
    }else {
      log = internal.print;
    }
  
    if (version === undefined) {
      version = internal.version;
    }
  
    if (version.match(/beta|alpha|preview|devel/) !== null) {
      log("You are using an alpha/beta/preview version ('" + version + "') of ArangoDB");
      return;
    }
  
    try {
      var u = 'https://www.arangodb.com/repositories/versions.php?version=' + version +
      '&os=' + internal.platform;
      var d = internal.download(u, '', {timeout: 10});
      var v = JSON.parse(d.body);
  
      if (v.hasOwnProperty('bugfix')) {
        log("Please note that a new bugfix version '" + v.bugfix.version + "' is available");
      }
  
      if (v.hasOwnProperty('minor')) {
        log("Please note that a new minor version '" + v.minor.version + "' is available");
      }
  
      if (v.hasOwnProperty('major')) {
        log("Please note that a new major version '" + v.major.version + "' is available");
      }
    } catch (err) {
      if (console && console.debug) {
        console.debug('cannot check for newer version: ', err.stack);
      }
    }
  };
  