text: /**
   * VersionController
   *
   * @description :: Server-side logic for handling version requests
   * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
   */
  
  var actionUtil = require('sails/lib/hooks/blueprints/actionUtil');
  var url = require('url');
  var Promise = require('bluebird');
  var semver = require('semver');
  var compareVersions = require('compare-versions');
  
  module.exports = {
  
    /**
     * Redirect the update request to the appropriate endpoint
     * (GET /update)
     */
    redirect: function(req, res) {
      var platform = req.param('platform');
      var version = req.param('version');
  
      if (!version) {
        return res.badRequest('Requires "version" parameter');
      }
      if (!platform) {
        return res.badRequest('Requires "platform" parameter');
      }
  
      return res.redirect('/update/' + platform + '/' + version);
    },
  
    /**
     * Sorts versions and returns pages sorted by by sermver
     *
     * ( GET /versions/sorted )
     */
    list: function (req, res) {
      Version
        .find()
        .then(versions => {
          var count = versions.length;
          var page = req.param('page') || req.query.page || 0;
          var start = page * sails.config.views.pageSize;
          var end = start + sails.config.views.pageSize;
          var items = versions
            .sort(function (a, b) {
              return -compareVersions(a.name, b.name);
            })
            .slice(start, end);
  
          const response = {
            total: count,
            offset: start,
            page: page,
            items: items
          }
  
          return Promise.all([
            // load channels
            new Promise(function (resolve, reject) {
              Promise.all(items.map(function (version) {
                return Channel.findOne({
                  name: version.channel
                })
              }))
              .then(resolve)
              .catch(reject)
            }),
            // load assets
            new Promise(function (resolve, reject) {
              Promise.all(items.map(function (version) {
                return Asset.find({
                  version: version.name
                })
              }))
              .then(resolve)
              .catch(reject)
            })
          ])
          .then(function (results) {
            response.items = response.items.map(function (item, index) {
              return {
                channel: results[0][index],
                assets: results[1][index].map(function (asset) {
                  return {
                    name: asset.name,
                    platform: asset.platform,
                    filetype: asset.filetype,
                    hash: asset.hash,
                    size: asset.size,
                    download_count: asset.download_count,
                    asset_version: asset.asset_version,
                    fd: asset.fd,
                    createdAt: asset.createdAt,
                    updatedAt: asset.updatedAt
                  }
                }),
                name: item.name,
                notes: item.notes,
                createdAt: item.createdAt,
                updatedAt: item.updatedAt
              }
            })
  
            return response
          })
        })
        .then(response => {
          res.send(response);
        })
        .catch(res.negotiate);
    },
  
    // Check if there are updated flows, given a version and flows version
    // http://localhost:8080/updateflows/1.1.6/0.0.4
    update_flows: function(req, res) {
      var version = req.param('version');
      var flow_version = req.param('flow_version');
  
      if (!version) {
        return res.badRequest('Requires `version` parameter');
      }
  
      sails.log.debug('Update-Flows: Query', {
        version: version,
        flow_version: flow_version
      });
  
      // Get all assets available for the specific version - filter by platform='flows'
      let r = Asset.find({version, platform: 'flows'}).then(function(availableAssets) {
  
        var semver = require('semver');
        
        // Sort versions which were added after the current one by semver in descending order.
        availableAssets.sort(UtilityService.compareAssetVersion);
  
        // Find the assets with the highest asset version above the checked asset_version
        if (flow_version)
          availableAssets = availableAssets.filter(x=>semver.compare(x.asset_version, flow_version)>=0);
  
        // Prepare a list of flows in this version
        // (it is not possible to have multiple flows with the same name and different versions - we do not allow this in upload)
        let flows = availableAssets.map(function(flow) {
          return {
              url: url.resolve(sails.config.appUrl, '/download-flow/' + flow.hash),
              name: flow.name,
              version: flow.asset_version,
              pub_date: flow.createdAt.toISOString()
          }
        });
  
        // Check if no assets found found
        if (flows.length===0) {
            sails.log.debug('No Asset (flow) candidate detected');
            return res.status(204).send('No updates.');
        }
  
        return res.ok(flows);
      })
      .catch(res.negotiate);
  
      return;
  
    },
  
    /**
     * Serves auto-updates: Status and Squirrel.Mac
     *
     * Assumes stable channel unless specified
     *
     * (GET /update/:platform/:version/:channel)
     */
    general: function(req, res) {
      var platform = req.param('platform');
      var version = req.param('version');
      var channel = req.param('channel') || 'stable';
  
      if (!version) {
        return res.badRequest('Requires `version` parameter');
      }
  
      if (!platform) {
        return res.badRequest('Requires `platform` parameter');
      }
  
      var platforms = PlatformService.detect(platform, true);
  
      sails.log.debug('Update Search Query', {
        platform: platforms,
        version: version,
        channel: channel
      });
  
      // Get specified version object, it's time will be used for the general
      // cutoff.
      Version
        .findOne(version)
        .then(function(currentVersion) {
  
          var applicableChannels, createdAtFilter;
  
          applicableChannels = ChannelService.getApplicableChannels(channel);
          sails.log.debug('Applicable Channels', applicableChannels);
  
          if (currentVersion) {
            createdAtFilter = {
              '>': currentVersion.createdAt
            };
          } else {
            sails.log.debug('The specified `version` does not exist');
          }
  
          sails.log.debug('Time Filter', createdAtFilter);
  
          return Version
            .find(UtilityService.getTruthyObject({
              channel: applicableChannels,
              createdAt: createdAtFilter
            }))
            .populate('assets', {
              platform: platforms
            })
            .then(function(newerVersions) {
              // Sort versions which were added after the current one by semver in
              // descending order.
              newerVersions.sort(UtilityService.compareVersion);
  
              var latestVersion;
              sails.log.debug('Newer Versions', newerVersions);
  
              // Create release notes by aggregating all release-notes of prev versions
              var releaseNotes = _.reduce(
                newerVersions,
                function(prevNotes, newVersion) {
  
                  newVersion.assets = _.filter(newVersion.assets, function(asset) {
                    return asset.filetype === '.zip' || asset.filetype === '.exe' || asset.filetype === '.js' || asset.filetype === '.json';
                  });
  
                  // If one of the assets for this version apply to our desired
                  // platform then we will skip this version
                  if (!newVersion.assets.length) {
                    return prevNotes;
                  }
  
                  if (!latestVersion && semver.lt(version, newVersion.name)) {
                    latestVersion = newVersion;
                  }
  
                  // Skip if no notes available for this version
                  if (!newVersion.notes || !newVersion.notes.length) {
                    return prevNotes;
                  }
  
                  // If not the first changenote, prefix with new line
                  var newChangeNote = !prevNotes.length ? '' : '\n';
  
                  newChangeNote += '## ' + newVersion.name + '\n' + newVersion.notes;
  
                  return prevNotes + newChangeNote;
                },
                '');
  
              var currentVersionName = _.get(currentVersion, 'name');
  
              sails.log.debug('Version candidate', latestVersion);
              sails.log.debug('Current version', currentVersionName);
  
              if (!latestVersion || latestVersion.name === currentVersionName) {
                sails.log.debug('Version candidate denied');
                return res.status(204).send('No updates.');
              }
  
              sails.log.debug('Version candidate accepted');
  
              return res.ok({
                url: url.resolve(
                  sails.config.appUrl,
                  '/download/' + latestVersion.name + '/' +
                  latestVersion.assets[0].platform + '?filetype=' + latestVersion.assets[0].filetype.replace('.','')
                ),
                name: latestVersion.name,
                notes: releaseNotes,
                pub_date: latestVersion.createdAt.toISOString()
              });
            });
        })
        .catch(res.negotiate);
    },
  
    /**
     * Serves auto-updates: Squirrel.Windows: serve RELEASES from latest version
     * Currently, it will only serve a full.nupkg of the latest release with a
     * normalized filename (for pre-release)
     *
     * (GET /update/:platform/:version/:channel/RELEASES)
     */
    windows: function(req, res) {
      var platform = req.param('platform');
      var version = req.param('version');
      var channel = req.param('channel') || 'stable';
  
      if (!version) {
        return res.badRequest('Requires `version` parameter');
      }
  
      if (!platform) {
        return res.badRequest('Requires `platform` parameter');
      }
  
      var platforms = PlatformService.detect(platform, true);
  
      sails.log.debug('Windows Update Search Query', {
        platform: platforms,
        version: version,
        channel: channel
      });
  
      // Get specified version object, it's time will be used for the general
      // cutoff.
      Version
        .findOne(version)
        .then(function(currentVersion) {
          var applicableChannels, createdAtFilter;
  
          applicableChannels = ChannelService.getApplicableChannels(channel);
          sails.log.debug('Applicable Channels', applicableChannels);
  
          if (currentVersion) {
            createdAtFilter = {
              '>=': currentVersion.createdAt
            };
          } else {
            sails.log.debug('The specified `version` does not exist');
          }
  
          sails.log.debug('Time Filter', createdAtFilter);
  
          return Version
            .find(UtilityService.getTruthyObject({
              channel: applicableChannels,
              createdAt: createdAtFilter
            }))
            .populate('assets', {
              platform: platforms
            })
            .then(function(newerVersions) {
              // Sort versions which were added after the current one by semver in
              // descending order.
              newerVersions.sort(UtilityService.compareVersion);
  
              var latestVersion = _.find(
                newerVersions,
                function(newVersion) {
                  _.remove(newVersion.assets, function(o) {
                    return o.filetype !== '.nupkg' || !o.hash;
                  });
  
                  // Make sure the last version is a version with full asset
                  // so RELEASES contains at least one full asset (which is mandatory for Squirrel.Windows)
                  let v = _.filter(
                      newVersion.assets,
                      function(o) {
                        return _.includes(o.name.toLowerCase(), '-full');
                      }
                    );
                  return v.length && semver.lte(
                    version, newVersion.name
                  );
                });
  
              if (!latestVersion) {
                sails.log.debug('Version not found');
                return res.status(500).send('Version not found');
              }
  
              // Add Delta assets from other versions
              var deltaAssets = _.reduce(
                newerVersions,
                function(assets, newVersion) {
                  return assets.concat(
                    _.filter(
                      newVersion.assets,
                      function(asset) {
                        return asset.filetype === '.nupkg'
                          && _.includes(asset.name.toLowerCase(), '-delta')
                          && semver.lte(version, asset.version)
                          && semver.gt(latestVersion.name, asset.version);
                      }));
                }, []);
  
              Array.prototype.unshift.apply(latestVersion.assets, deltaAssets);
  
              latestVersion.assets.sort(function(a1, a2) {
                return semver.compare(a1.version, a2.version);
              });
  
              sails.log.debug('Latest Windows Version', latestVersion);
  
              // Change asset name to use full download link
              assets = _.map(latestVersion.assets, function(asset) {
                asset.name = url.resolve(
                  sails.config.appUrl,
                  '/download/' + asset.version + '/' + asset.platform + '/' +
                  asset.name
                );
  
                return asset;
              });
  
              var output = WindowsReleaseService.generate(assets);
  
              res.header('Content-Length', output.length);
              res.attachment('RELEASES');
              return res.send(output);
            });
        })
        .catch(res.negotiate);
    },
  
    /**
     * Get electron-updater win yml for a specific channel
     * (GET /update/:platform/latest.yml)
     * (GET /update/:platform/:channel.yml)
     * (GET /update/:platform/:channel/latest.yml)
     */
    electronUpdaterWin: function(req, res) {
      var platform = req.param('platform');
      var channel = req.param('channel') || 'stable';
  
      if (!platform) {
        return res.badRequest('Requires `platform` parameter');
      }
  
      var platforms = PlatformService.detect(platform, true);
  
      sails.log.debug('NSIS electron-updater Search Query', {
        platform: platforms,
        channel: channel
      });
  
      var applicableChannels = ChannelService.getApplicableChannels(channel);
      sails.log.debug('Applicable Channels', applicableChannels);
  
      // Get latest version that has a windows asset
      Version
        .find({ channel: applicableChannels })
        .populate('assets')
        .then(function(versions) {
          // TODO: Implement method to get latest version with available asset
          var sortedVersions = versions.sort(UtilityService.compareVersion);
          var latestVersion = null;
          var asset = null;
          for (var i = 0; i < sortedVersions.length; i++) {
            var currentVersion = sortedVersions[i];
            if (currentVersion.assets) {
              for (var j = 0; j < currentVersion.assets.length; j++) {
                var currentAsset = currentVersion.assets[j];
                if (currentAsset.filetype === '.exe' && _.includes(platforms, currentAsset.platform)) {
                  latestVersion = currentVersion;
                  asset = currentAsset;
                  break;
                }
              }
  
              if (latestVersion) {
                break;
              }
            }
          }
  
          if (latestVersion) {
            var downloadPath = url.resolve(
              //sails.config.appUrl,
              "",
              '/download/' + latestVersion.name + '/' + asset.platform + '/' +
              asset.name
            );
  
            var sha2 = asset.hash ? asset.hash.toLowerCase() : null
  
            var latestYml = "version: " + latestVersion.name
                            + "\nreleaseDate: " + latestVersion.updatedAt
                            + "\npath: " + downloadPath
                            + "\nsha2: " + sha2;
            res.ok(latestYml);
          } else {
            res.notFound();
          }
        });
    },
  
    /**
     * Get electron-updater mac yml for a specific channel
     * (GET /update/:platform/latest-mac.yml)
     * (GET /update/:platform/:channel-mac.yml)
     * (GET /update/:platform/:channel/latest-mac.yml)
     */
    electronUpdaterMac: function(req, res) {
      var platform = req.param('platform');
      var channel = req.param('channel') || 'stable';
  
      if (!platform) {
        return res.badRequest('Requires `platform` parameter');
      }
  
      var platforms = PlatformService.detect(platform, true);
  
      sails.log.debug('Mac electron-updater Search Query', {
        platform: platforms,
        channel: channel
      });
  
      var applicableChannels = ChannelService.getApplicableChannels(channel);
      sails.log.debug('Applicable Channels', applicableChannels);
  
      // Get latest version that has a mac asset
      Version
        .find({ channel: applicableChannels })
        .populate('assets')
        .then(function(versions) {
          // TODO: Implement method to get latest version with available asset
          var sortedVersions = versions.sort(UtilityService.compareVersion);
          var latestVersion = null;
          var asset = null;
          for (var i = 0; i < sortedVersions.length; i++) {
            var currentVersion = sortedVersions[i];
            if (currentVersion.assets) {
              for (var j = 0; j < currentVersion.assets.length; j++) {
                var currentAsset = currentVersion.assets[j];
                if (currentAsset.filetype === '.zip' && _.includes(platforms, currentAsset.platform)) {
                  latestVersion = currentVersion;
                  asset = currentAsset;
                  break;
                }
              }
  
              if (latestVersion) {
                break;
              }
            }
          }
  
          if (latestVersion) {
            var downloadPath = url.resolve(
              //sails.config.appUrl,
              "",
              '/download/' + latestVersion.name + '/' + asset.platform + '/' +
              asset.name
            );
  
            var sha2 = asset.hash ? asset.hash.toLowerCase() : null
  
            var latestYml = "version: " + latestVersion.name
                            + "\nreleaseDate: " + latestVersion.updatedAt
                            + "\npath: " + downloadPath
                            + "\nsha2: " + sha2;
            res.ok(latestYml);
          } else {
            res.notFound();
          }
        });
    },
  
    /**
     * Get release notes for a specific version
     * (GET /notes/:version?)
     */
    releaseNotes: function(req, res) {
      var version = req.params.version;
  
      Version
        .findOne(version)
        .then(function(currentVersion) {
          if (!currentVersion) {
            return res.notFound('The specified version does not exist');
          }
  
          return res.format({
            'application/json': function() {
              res.send({
                'notes': currentVersion.notes,
                'pub_date': currentVersion.createdAt.toISOString()
              });
            },
            'default': function() {
              res.send(currentVersion.notes);
            }
          });
        })
        .catch(res.negotiate);
    },
  
    /**
     * Overloaded blueprint function
     * Changes:
     *  - Delete all associated assets & their files
     * @param  {[type]} req [description]
     * @param  {[type]} res [description]
     * @return {[type]}     [description]
     */
    destroy: function(req, res) {
      var pk = actionUtil.requirePk(req);
  
      var query = Version.findOne(pk);
      query.populate('assets');
      query.exec(function foundRecord(err, record) {
        if (err) return res.serverError(err);
        if (!record) return res.notFound(
          'No record found with the specified `name`.'
        );
  
        var deletePromises = _.map(record.assets, function(asset) {
          return Promise.join(
            AssetService.destroy(asset, req),
            AssetService.deleteFile(asset),
            function() {
              sails.log.info('Destroyed asset: ', asset);
            });
        });
  
        Promise.all(deletePromises)
          .then(function allDeleted() {
            return Version.destroy(pk)
              .then(function destroyedRecord() {
  
                if (sails.hooks.pubsub) {
                  Version.publishDestroy(
                    pk, !req._sails.config.blueprints.mirror && req, {
                      previous: record
                    }
                  );
  
                  if (req.isSocket) {
                    Version.unsubscribe(req, record);
                    Version.retire(record);
                  }
                }
  
                sails.log.info('Destroyed version: ', record);
  
                return res.ok(record);
              });
          })
          .error(res.negotiate);
      });
    }
  };
  