text: /**
   * @module cache
   */
  const NodeCache = require("node-cache");
  const log = require("../../util/log");
  const linkedinApi = require("../linkedin_learning");
  const fs = require('fs');
  
  /**
   * The main server cache.
   */
  const modulesCache = new NodeCache({
      checkperiod: 0,
  });
  
  /**
   * Get a cached object.
   * @param {String} key
   * @return {Object} cached object
   */
  const get = (key) => {
      return modulesCache.get(key);
  };
  
  /**
   * Cache an object.
   * @param {String} key
   * @param {String} value
   */
  const set = (key, value) => {
      modulesCache.set(key, value);
  };
  
  /**
   * Return true if key exists in cache.
   * @param {String} key
   * @return {Boolean} true if exists
   */
  const has = (key) => {
      return modulesCache.has(key);
  };
  
  /**
   * Remove an object from the cache.
   * @param {String} key
   */
  const del = (key) => {
      modulesCache.del(key);
  };
  
  /**
   * Flush the cache.
   */
  const flush = () => {
      log.info("About to flush CACHE, current stats: %s", modulesCache.getStats());
      modulesCache.flushAll();
      log.info("Flushed CACHE, stats reset: %s", modulesCache.getStats());
  };
  
  /**
   * Log the cache stats.
   */
  const logStats = () => {
      log.info("CACHE stats: %s", modulesCache.getStats());
  };
  
  /**
   * Update all cached learning objects from LinkedIn Learning API.
   * Log the results and write out failed objects to a separate log file.
   */
  const updateAllFromAPI = async () => {
      log.info("Attempting to update CACHE from LinkedIn-L API..");
  
      const courses = [];
      const videos = [];
      let successCount = 0;
      let errorCount = 0;
      const objectsWithoutURN = {
          courses: [],
          videos: [],
      };
      for await (const key of modulesCache.keys()) {
          // check if object has an LinkedIn Learning API ID
          const val = modulesCache.get(key);
          if (/^urn:li:/.test(val.urn)) {
              const learningObj = await linkedinApi.fetchLearningObject(val.urn);
              if (learningObj) {
                  ++successCount;
                  // update object fields
                  val.title = learningObj.title.value;
                  val.hyperlink = learningObj.details.urls.webLaunch;
                  val.longDescription = learningObj.details.descriptionIncludingHtml ?
                      learningObj.details.descriptionIncludingHtml.value : null;
                  val.shortDescription = learningObj.details.shortDescriptionIncludingHtml ?
                      learningObj.details.shortDescriptionIncludingHtml.value : null;
                  val.picture = learningObj.details.images.primary ?
                      learningObj.details.images.primary : null;
                  val.length = learningObj.details.timeToComplete ?
                      learningObj.details.timeToComplete.duration : null;
                  // store updated object
                  modulesCache.set(key, val);
              } else {
                  ++errorCount;
              }
              // add object to collections despite update
              if (/^course-/.test(key)) {
                  courses.push(modulesCache.get(key));
              } else if (/^video-/.test(key)) {
                  videos.push(modulesCache.get(key));
              }
          } else {
              // log discarded object for not having a URN
              if (/^course-/.test(key)) {
                  objectsWithoutURN.courses.push(val);
              } else if (/^video-/.test(key)) {
                  objectsWithoutURN.videos.push(val);
              }
              modulesCache.del(key);
          }
      }
      // update collections
      modulesCache.set("courses", courses);
      modulesCache.set("videos", videos);
      errorCount === 0 ?
          log.info("CACHE update from LinkedIn-L API finished without errors: %s objects fetched successfully", successCount) :
          log.warn("CACHE update from LinkedIn-L API had at least one error: %s succeeded, %s failed", successCount, errorCount);
      logStats();
  
      fs.writeFileSync(`./log/objects_without_urn_${(new Date()).toUTCString()}`, JSON.stringify(objectsWithoutURN));
  };
  
  /**
   * Update a single cached object from LinkedIn Learning API.
   * @param {String} key
   */
  const updateFromAPI = async (key) => {
      log.info("Attempting to update CACHE(%s) from LinkedIn-L API..", key);
      try {
          const val = modulesCache.get(key);
          const learningObj = await linkedinApi.fetchLearningObject(val.urn);
          if (learningObj) {
              // update object fields
              val.title = learningObj.title.value;
              val.hyperlink = learningObj.details.urls.webLaunch;
              val.longDescription = learningObj.details.descriptionIncludingHtml ?
                  learningObj.details.descriptionIncludingHtml.value : null;
              val.shortDescription = learningObj.details.shortDescriptionIncludingHtml ?
                  learningObj.details.shortDescriptionIncludingHtml.value : null;
              val.picture = learningObj.details.images.primary ?
                  learningObj.details.images.primary : null;
              val.length = learningObj.details.timeToComplete ?
                  learningObj.details.timeToComplete.duration : null;
              // store updated object
              modulesCache.set(key, val);
              // add object to collections if updated
              if (/^course-/.test(key)) {
                  const courses = modulesCache.get("courses");
                  courses.push(modulesCache.get(key));
                  modulesCache.set("courses", courses);
              } else if (/^video-/.test(key)) {
                  const videos = modulesCache.get("videos");
                  videos.push(modulesCache.get(key));
                  modulesCache.set("videos", videos);
              }
          }
          log.info("Successfully updated CACHE(%s) from LinkedIn-L API..", key);
      } catch (error) {
          log.error("Failed to update CACHE(%s from LinkedIn-L API.., err: " + error.message, key);
      }
  };
  
  module.exports = {
      get,
      set,
      has,
      del,
      flush,
      logStats,
      updateAllFromAPI,
      updateFromAPI,
  };
  