# Copyright (C) 2008 The Android Open Source Project
#
# 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.

import re
import sys

from command import Command
from error import GitError, NoSuchProjectError

CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$')


class Download(Command):
  COMMON = True
  helpSummary = "Download and checkout a change"
  helpUsage = """
%prog {[project] change[/patchset]}...
"""
  helpDescription = """
The '%prog' command downloads a change from the review system and
makes it available in your project's local working directory.
If no project is specified try to use current directory as a project.
"""

  def _Options(self, p):
    p.add_option('-b', '--branch',
                 help='create a new branch first')
    p.add_option('-c', '--cherry-pick',
                 dest='cherrypick', action='store_true',
                 help="cherry-pick instead of checkout")
    p.add_option('-x', '--record-origin', action='store_true',
                 help='pass -x when cherry-picking')
    p.add_option('-r', '--revert',
                 dest='revert', action='store_true',
                 help="revert instead of checkout")
    p.add_option('-f', '--ff-only',
                 dest='ffonly', action='store_true',
                 help="force fast-forward merge")

  def _ParseChangeIds(self, opt, args):
    if not args:
      self.Usage()

    to_get = []
    project = None

    for a in args:
      m = CHANGE_RE.match(a)
      if m:
        if not project:
          project = self.GetProjects(".")[0]
          print('Defaulting to cwd project', project.name)
        chg_id = int(m.group(1))
        if m.group(2):
          ps_id = int(m.group(2))
        else:
          ps_id = 1
          refs = 'refs/changes/%2.2d/%d/' % (chg_id % 100, chg_id)
          output = project._LsRemote(refs + '*')
          if output:
            regex = refs + r'(\d+)'
            rcomp = re.compile(regex, re.I)
            for line in output.splitlines():
              match = rcomp.search(line)
              if match:
                ps_id = max(int(match.group(1)), ps_id)
        to_get.append((project, chg_id, ps_id))
      else:
        projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only)
        if len(projects) > 1:
          # If the cwd is one of the projects, assume they want that.
          try:
            project = self.GetProjects('.')[0]
          except NoSuchProjectError:
            project = None
          if project not in projects:
            print('error: %s matches too many projects; please re-run inside '
                  'the project checkout.' % (a,), file=sys.stderr)
            for project in projects:
              print('  %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only),
                                    project.revisionExpr), file=sys.stderr)
            sys.exit(1)
        else:
          project = projects[0]
          print('Defaulting to cwd project', project.name)
    return to_get

  def ValidateOptions(self, opt, args):
    if opt.record_origin:
      if not opt.cherrypick:
        self.OptionParser.error('-x only makes sense with --cherry-pick')

      if opt.ffonly:
        self.OptionParser.error('-x and --ff are mutually exclusive options')

  def Execute(self, opt, args):
    for project, change_id, ps_id in self._ParseChangeIds(opt, args):
      dl = project.DownloadPatchSet(change_id, ps_id)
      if not dl:
        print('[%s] change %d/%d not found'
              % (project.name, change_id, ps_id),
              file=sys.stderr)
        sys.exit(1)

      if not opt.revert and not dl.commits:
        print('[%s] change %d/%d has already been merged'
              % (project.name, change_id, ps_id),
              file=sys.stderr)
        continue

      if len(dl.commits) > 1:
        print('[%s] %d/%d depends on %d unmerged changes:'
              % (project.name, change_id, ps_id, len(dl.commits)),
              file=sys.stderr)
        for c in dl.commits:
          print('  %s' % (c), file=sys.stderr)

      if opt.cherrypick:
        mode = 'cherry-pick'
      elif opt.revert:
        mode = 'revert'
      elif opt.ffonly:
        mode = 'fast-forward merge'
      else:
        mode = 'checkout'

      # We'll combine the branch+checkout operation, but all the rest need a
      # dedicated branch start.
      if opt.branch and mode != 'checkout':
        project.StartBranch(opt.branch)

      try:
        if opt.cherrypick:
          project._CherryPick(dl.commit, ffonly=opt.ffonly,
                              record_origin=opt.record_origin)
        elif opt.revert:
          project._Revert(dl.commit)
        elif opt.ffonly:
          project._FastForward(dl.commit, ffonly=True)
        else:
          if opt.branch:
            project.StartBranch(opt.branch, revision=dl.commit)
          else:
            project._Checkout(dl.commit)

      except GitError:
        print('[%s] Could not complete the %s of %s'
              % (project.name, mode, dl.commit), file=sys.stderr)
        sys.exit(1)
