# Copyright (c) 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import base64
import json
import os
import re
import time
import urllib2

from common import utils


_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
CONFIG = json.loads(open(os.path.join(_THIS_DIR,
                                      'deps_config.json'), 'r').read())
OLD_GIT_URL_PATTERN = re.compile(r'https?://git.chromium.org/(.*)')


class _VarImpl(object):

  def __init__(self, local_scope):
    self._local_scope = local_scope

  def Lookup(self, var_name):
    if var_name in self._local_scope.get('vars', {}):
      return self._local_scope['vars'][var_name]
    raise Exception('Var is not defined: %s' % var_name)


def _ParseDEPS(content):
  """Parse the DEPS file of chromium."""
  local_scope = {}
  var = _VarImpl(local_scope)
  global_scope = {
      'Var': var.Lookup,
      'deps': {},
      'deps_os': {},
      'include_rules': [],
      'skip_child_includes': [],
      'hooks': [],
  }
  exec(content, global_scope, local_scope)

  local_scope.setdefault('deps', {})
  local_scope.setdefault('deps_os', {})

  return (local_scope['deps'], local_scope['deps_os'])


def _GetComponentName(path, host_dirs):
  """Return the component name of a path."""
  components_renamed = {
      'webkit': 'blink',
  }

  for host_dir in host_dirs:
    if path.startswith(host_dir):
      path = path[len(host_dir):]
      name = path.split('/')[0].lower()
      if name in components_renamed:
        return components_renamed[name].lower()
      else:
        return name.lower()

  # Unknown path, return the whole path as component name.
  return '_'.join(path.split('/'))


def _GetContentOfDEPS(revision):
  chromium_git_file_url_template = CONFIG['chromium_git_file_url']

  # Try .DEPS.git first, because before migration from SVN to GIT, the .DEPS.git
  # has the dependency in GIT repo while DEPS has dependency in SVN repo.
  url = chromium_git_file_url_template % (revision, '.DEPS.git')
  http_status_code, content = utils.GetHttpClient().Get(
      url, retries=5, retry_if_not=404)

  # If .DEPS.git is not found, use DEPS, assuming it is a commit after migration
  # from SVN to GIT.
  if http_status_code == 404:
    url = chromium_git_file_url_template % (revision, 'DEPS')
    http_status_code, content = utils.GetHttpClient().Get(url, retries=5)

  if http_status_code == 200:
    return base64.b64decode(content)
  else:
    return ''


def GetChromiumComponents(chromium_revision,
                          os_platform='unix',
                          deps_file_downloader=_GetContentOfDEPS):
  """Return a list of components used by Chrome of the given revision.

  Args:
    chromium_revision: Revision of the Chrome build: svn revision, or git hash.
    os_platform: The target platform of the Chrome build, eg. win, mac, etc.
    deps_file_downloader: A function that takes the chromium_revision as input,
                          and returns the content of the DEPS file. The returned
                          content is assumed to be trusted input and will be
                          evaluated as python code.

  Returns:
    A map from component path to parsed component name, repository URL,
    repository type and revision.
    Return None if an error occurs.
  """
  if os_platform.lower() == 'linux':
    os_platform = 'unix'

  chromium_git_base_url = CONFIG['chromium_git_base_url']

  if not utils.IsGitHash(chromium_revision):
    # Convert svn revision or commit position to Git hash.
    cr_rev_url_template = CONFIG['cr_rev_url']
    url = cr_rev_url_template % chromium_revision
    status_code, content = utils.GetHttpClient().Get(
        url, timeout=120, retries=5, retry_if_not=404)
    if status_code != 200 or not content:
      if status_code == 404:
        print 'Chromium commit position %s is not found.' % chromium_revision
      return None

    cr_rev_data = json.loads(content)
    if 'git_sha' not in cr_rev_data:
      return None

    if 'repo' not in cr_rev_data or cr_rev_data['repo'] != 'chromium/src':
      print ('%s seems like a commit position of "%s", but not "chromium/src".'
             % (chromium_revision, cr_rev_data['repo']))
      return None

    chromium_revision = cr_rev_data.get('git_sha')
    if not chromium_revision:
      return None

  # Download the content of DEPS file in chromium.
  deps_content = deps_file_downloader(chromium_revision)
  if not deps_content:
    return None

  all_deps = {}

  # Parse the content of DEPS file.
  deps, deps_os = _ParseDEPS(deps_content)
  all_deps.update(deps)
  if os_platform is not None:
    all_deps.update(deps_os.get(os_platform, {}))

  # Figure out components based on the dependencies.
  components = {}
  host_dirs = CONFIG['host_directories']
  for component_path, component_repo_url in all_deps.iteritems():
    if component_repo_url is None:
      # For some platform like iso, some component is ignored.
      continue

    name = _GetComponentName(component_path, host_dirs)
    repository, revision = component_repo_url.split('@')
    match = OLD_GIT_URL_PATTERN.match(repository)
    if match:
      repository = 'https://chromium.googlesource.com/%s' % match.group(1)
    is_git_hash = utils.IsGitHash(revision)
    if is_git_hash:
      repository_type = 'git'
    else:
      repository_type = 'svn'
    if not component_path.endswith('/'):
      component_path += '/'
    components[component_path] = {
        'path': component_path,
        'name': name,
        'repository': repository,
        'repository_type': repository_type,
        'revision': revision
    }

  # Add chromium as a component.
  components['src/'] = {
      'path': 'src/',
      'name': 'chromium',
      'repository': chromium_git_base_url,
      'repository_type': 'git',
      'revision': chromium_revision
  }

  return components


def GetChromiumComponentRange(old_revision,
                              new_revision,
                              os_platform='unix',
                              deps_file_downloader=_GetContentOfDEPS):
  """Return a list of components with their revision ranges.

  Args:
    old_revision: The old revision of a Chrome build.
    new_revision: The new revision of a Chrome build.
    os_platform: The target platform of the Chrome build, eg. win, mac, etc.
    deps_file_downloader: A function that takes the chromium_revision as input,
                          and returns the content of the DEPS file. The returned
                          content is assumed to be trusted input and will be
                          evaluated as python code.

  Returns:
    A map from component path to its parsed regression and other information.
    Return None if an error occurs.
  """
  old_components = GetChromiumComponents(old_revision, os_platform,
                                         deps_file_downloader)
  if not old_components:
    return None

  new_components = GetChromiumComponents(new_revision, os_platform,
                                         deps_file_downloader)
  if not new_components:
    return None

  components = {}
  for path in new_components:
    new_component = new_components[path]
    old_revision = None

    if path in old_components:
      old_component = old_components[path]
      old_revision = old_component['revision']

    components[path] = {
        'path': path,
        'rolled': new_component['revision'] != old_revision,
        'name': new_component['name'],
        'old_revision': old_revision,
        'new_revision': new_component['revision'],
        'repository': new_component['repository'],
        'repository_type': new_component['repository_type']
    }

  return components