function m2tUpdater(about, verbose)
%UPDATER   Auto-update matlab2tikz.
%   Only for internal usage.

%   Copyright (c) 2012--2014, Nico Schlömer <nico.schloemer@gmail.com>
%   All rights reserved.
%
%   Redistribution and use in source and binary forms, with or without
%   modification, are permitted provided that the following conditions are
%   met:
%
%      * Redistributions of source code must retain the above copyright
%        notice, this list of conditions and the following disclaimer.
%      * Redistributions in binary form must reproduce the above copyright
%        notice, this list of conditions and the following disclaimer in
%        the documentation and/or other materials provided with the distribution
%
%   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
%   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
%   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
%   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
%   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
%   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
%   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
%   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
%   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
%   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%   POSSIBILITY OF SUCH DAMAGE.
% =========================================================================
  fileExchangeUrl = about.website;
  version = about.version;

  mostRecentVersion = determineLatestRelease(version, fileExchangeUrl);
  if askToUpgrade(mostRecentVersion, version, verbose)
      tryToUpgrade(fileExchangeUrl, verbose);
      userInfo(verbose, '');
  end
end
% ==============================================================================
function shouldUpgrade = askToUpgrade(mostRecentVersion, version, verbose)
  shouldUpgrade = false;
  if ~isempty(mostRecentVersion)
      userInfo(verbose, '**********************************************\n');
      userInfo(verbose, 'New version (%s) available!\n', mostRecentVersion);
      userInfo(verbose, '**********************************************\n');

      warnAboutUpgradeImplications(version, mostRecentVersion, verbose);
      askToShowChangelog(version);
      reply = input(' *** Would you like to upgrade? y/n [n]:','s');
      shouldUpgrade = ~isempty(reply) && strcmpi(reply(1),'y');
      if ~shouldUpgrade
        userInfo(verbose, ['\nTo disable the self-updater in the future, add ' ...
                           '"''checkForUpdates'',false" to the parameters.\n'] );
      end
  end
end
% ==============================================================================
function tryToUpgrade(fileExchangeUrl, verbose)
  % Download the files and unzip its contents into two folders
  % above the folder that contains the current script.
  % This assumes that the file structure is something like
  %
  %   src/matlab2tikz.m
  %   src/[...]
  %   src/private/m2tUpdater
  %   src/private/[...]
  %   AUTHORS
  %   ChangeLog
  %   [...]
  %
  % on the hard drive and the zip file. In particular, this assumes
  % that the folder on the hard drive is writable by the user
  % and that matlab2tikz.m is not symlinked from some other place.
  pathstr    = fileparts(mfilename('fullpath'));
  targetPath = fullfile(pathstr, '..', '..');

  % Let the user know where the .zip is downloaded to
  userInfo(verbose, 'Downloading and unzipping to ''%s'' ...', targetPath);

  % Try upgrading
  try
      % List current folder structure. Will use last for cleanup
      currentFolderFiles = rdirfiles(targetPath);

      % The FEX now forwards the download request to Github.
      % Go through the forwarding to update the download count and
      % unzip
      html          = urlread([fileExchangeUrl, '?download=true']);
      expression    = '(?<=\<a href=")[\w\-\/:\.]+(?=">redirected)';
      url           = regexp(html, expression,'match','once');
      unzippedFiles = unzip(url, targetPath);

      % The folder structure is additionally packed into the
      % 'MATLAB Search Path' folder defined in FEX. Retrieve the
      % top folder name
      tmp          = strrep(unzippedFiles,[targetPath, filesep],'');
      tmp          = regexp(tmp, filesep,'split','once');
      tmp          = cat(1,tmp{:});
      topZipFolder = unique(tmp(:,1));

      % If packed into the top folder, overwrite files into m2t
      % main directory
      if numel(topZipFolder) == 1
          unzippedFilesTarget = fullfile(targetPath, tmp(:,2));
          for ii = 1:numel(unzippedFiles)
              movefile(unzippedFiles{ii}, unzippedFilesTarget{ii})
          end
          % Add topZipFolder to current folder structure
          currentFolderFiles = [currentFolderFiles; fullfile(targetPath, topZipFolder{1})];
      end

      cleanupOldFiles(currentFolderFiles, unzippedFilesTarget);

      userInfo(verbose, 'Upgrade has completed successfully.');
  catch
      err = lasterror(); %#ok needed for Octave
      
      userInfo(verbose, ...
               ['Upgrade has failed with error message "%s".\n', ...
                'Please install the latest version manually from %s !'], ...
                err.message, fileExchangeUrl);
  end
end
% ==============================================================================
function cleanupOldFiles(currentFolderFiles, unzippedFilesTarget)
% Delete files that were there in the old folder, but that are no longer
% present in the new release.
    newFolderStructure = [getFolders(unzippedFilesTarget);  unzippedFilesTarget];
    deleteFolderFiles  = setdiff(currentFolderFiles, newFolderStructure);
    for ii = 1:numel(deleteFolderFiles)
        x = deleteFolderFiles{ii};
        if exist(x, 'dir') == 7
            % First check for directories since
            % `exist(x, 'file')` also checks for directories!
            rmdir(x,'s');
        elseif exist(x, 'file') == 2
            delete(x);
        end
    end
end
% ==============================================================================
function mostRecentVersion = determineLatestRelease(version, fileExchangeUrl)
  % Read in the Github releases page
  url = 'https://github.com/matlab2tikz/matlab2tikz/releases/';
  try
      html = urlread(url);
  catch %#ok
      % Couldn't load the URL -- never mind.
      html = '';
      warning('m2tUpdate:siteNotFound', ...
              ['Cannot determine the latest version.\n', ...
               'Either your internet is down or something went wrong.\n', ...
               'You might want to check for updates by hand at %s.\n'], ...
               fileExchangeUrl);
  end

  % Parse tag names which are the version number in the format ##.##.##
  % It assumes that releases will always be tagged with the version number
  expression = '(?<=matlab2tikz\/matlab2tikz\/releases\/tag\/)\d+\.\d+\.\d+';
  tags       = regexp(html, expression, 'match');
  ntags      = numel(tags);

  % Keep only new releases
  inew = false(ntags,1);
  for ii = 1:ntags
      inew(ii) = isVersionBelow(version, tags{ii});
  end
  nnew = nnz(inew);

  % One new release
  if nnew == 1
      mostRecentVersion = tags{inew};
  % Several new release, pick latest
  elseif nnew > 1
      tags   = tags(inew);
      tagnum = zeros(nnew,1);
      for ii = 1:nnew
          tagnum(ii) = [10000,100,1] * versionArray(tags{ii});
      end
      [~, imax]         = max(tagnum);
      mostRecentVersion = tags{imax};
  % No new
  else
      mostRecentVersion = '';
  end
end
% ==============================================================================
function askToShowChangelog(currentVersion)
% Asks whether the user wants to see the changelog and then shows it.
    reply = input(' *** Would you like to see the changelog? y/n [y]:' ,'s');
    shouldShow =  isempty(reply) || ~strcmpi(reply(1),'n') ;
    if shouldShow
        fprintf(1, '\n%s\n', changelogUntilVersion(currentVersion));
    end
end
% ==============================================================================
function changelog = changelogUntilVersion(currentVersion)
% This function retrieves the chunk of the changelog until the current version.
    URL = 'https://github.com/matlab2tikz/matlab2tikz/raw/master/CHANGELOG.md';
    changelog = urlread(URL);
    currentVersion = versionString(currentVersion);

    % Header is "# YYYY-MM-DD Version major.minor.patch [Manager](email)"
    % Just match for the part until the version number. Here, we're actually
    % matching a tiny bit too broad due to the periods in the version number
    % but the outcome should be the same if we keep the changelog format
    % identical.
    pattern = ['\#\s*[\d-]+\s*Version\s*' currentVersion];
    idxVersion = regexpi(changelog, pattern);
    if ~isempty(idxVersion)
        changelog = changelog(1:idxVersion-1);
    else
        % Just show the whole changelog if we don't find the old version.
    end
end
% ==============================================================================
function warnAboutUpgradeImplications(currentVersion, latestVersion, verbose)
% This warns the user about the implications of upgrading as dictated by
% Semantic Versioning.
    switch upgradeSize(currentVersion, latestVersion);
        case 'major'
          % The API might have changed in a backwards incompatible way.
          userInfo(verbose, 'This is a MAJOR upgrade!\n');
          userInfo(verbose, ' - New features may have been introduced.');
          userInfo(verbose, ' - Some old code/options may no longer work!\n');

        case 'minor'
          % The API may NOT have changed in a backwards incompatible way.
          userInfo(verbose, 'This is a MINOR upgrade.\n');
          userInfo(verbose, ' - New features may have been introduced.');
          userInfo(verbose, ' - Some options may have been deprecated.');
          userInfo(verbose, ' - Old code should continue to work but might produce warnings.\n');

        case 'patch'
          % No new functionality is introduced
          userInfo(verbose, 'This is a PATCH.\n');
          userInfo(verbose, ' - Only bug fixes are included in this upgrade.');
          userInfo(verbose, ' - Old code should continue to work as before.')
    end
    userInfo(verbose, 'Please check the changelog for detailed information.\n');
    userWarn(verbose, '\n!! By upgrading you will lose any custom changes !!\n');
end
% ==============================================================================
function cls = upgradeSize(currentVersion, latestVersion)
% Determines whether the upgrade is major, minor or a patch.
    currentVersion = versionArray(currentVersion);
    latestVersion = versionArray(latestVersion);
    description = {'major', 'minor', 'patch'};
    for ii = 1:numel(description)
        if latestVersion(ii) > currentVersion(ii)
            cls = description{ii};
            return
        end
    end
    cls = 'unknown';
end
% ==============================================================================
function userInfo(verbose, message, varargin)
    % Display information (i.e. to stdout)
    if verbose
        userPrint(1, message, varargin{:});
    end
end
function userWarn(verbose, message, varargin)
    % Display warnings (i.e. to stderr)
    if verbose
        userPrint(2, message, varargin{:});
    end
end
function userPrint(fid, message, varargin)
    % Print messages (info/warnings) to a stream/file.
    mess = sprintf(message, varargin{:});
    
    % Replace '\n' by '\n *** ' and print.
    mess = strrep( mess, sprintf('\n'), sprintf('\n *** ') );
    fprintf(fid, ' *** %s\n', mess );
end
% =========================================================================
function list = rdirfiles(rootdir)
  % Recursive files listing
  s    = dir(rootdir);
  list = {s.name}';

  % Exclude .git, .svn, . and ..
  [list, idx] = setdiff(list, {'.git','.svn','.','..'});

  % Add root
  list = fullfile(rootdir, list);

  % Loop for sub-directories
  pdir = find([s(idx).isdir]);
  for ii = pdir
      list = [list; rdirfiles(list{ii})]; %#ok<AGROW>
  end

  % Drop directories
  list(pdir) = [];
end
% =========================================================================
function list = getFolders(list)
  % Extract the folder structure from a list of files and folders

  for ii = 1:numel(list)
      if exist(list{ii},'file') == 2
          list{ii} = fileparts(list{ii});
      end
  end
  list = unique(list);
end
% =========================================================================
