# -*- coding: utf-8 -*-
# Copyright 2011 Google Inc. All Rights Reserved.
#
# 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.
"""Implementation of gsutil version command."""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
from __future__ import unicode_literals

from hashlib import md5
import os
import platform
import re
import sys

import six
import boto
import crcmod
import gslib
from gslib.command import Command
from gslib.utils import system_util
from gslib.utils.boto_util import GetFriendlyConfigFilePaths
from gslib.utils.boto_util import UsingCrcmodExtension
from gslib.utils.constants import UTF8
from gslib.utils.parallelism_framework_util import CheckMultiprocessingAvailableAndInit

_SYNOPSIS = """
  gsutil version
"""

_DETAILED_HELP_TEXT = ("""
<B>SYNOPSIS</B>
""" + _SYNOPSIS + """


<B>DESCRIPTION</B>
  Prints information about the version of gsutil.

<B>OPTIONS</B>
  -l          Prints additional information, such as the version of Python
              being used, the version of the Boto library, a checksum of the
              code, the path to gsutil, and the path to gsutil's configuration
              file.
""")


class VersionCommand(Command):
  """Implementation of gsutil version command."""

  # Command specification. See base class for documentation.
  command_spec = Command.CreateCommandSpec(
      'version',
      command_name_aliases=['ver'],
      usage_synopsis=_SYNOPSIS,
      min_args=0,
      max_args=0,
      supported_sub_args='l',
      file_url_ok=False,
      provider_url_ok=False,
      urls_start_arg=0,
  )
  # Help specification. See help_provider.py for documentation.
  help_spec = Command.HelpSpec(
      help_name='version',
      help_name_aliases=['ver'],
      help_type='command_help',
      help_one_line_summary='Print version info about gsutil',
      help_text=_DETAILED_HELP_TEXT,
      subcommand_help_text={},
  )

  def RunCommand(self):
    """Command entry point for the version command."""
    long_form = False
    if self.sub_opts:
      for o, _ in self.sub_opts:
        if o == '-l':
          long_form = True

    config_paths = ', '.join(GetFriendlyConfigFilePaths())

    shipped_checksum = gslib.CHECKSUM
    try:
      cur_checksum = self._ComputeCodeChecksum()
    except IOError:
      cur_checksum = 'MISSING FILES'
    if shipped_checksum == cur_checksum:
      checksum_ok_str = 'OK'
    else:
      checksum_ok_str = '!= %s' % shipped_checksum

    sys.stdout.write('gsutil version: %s\n' % gslib.VERSION)

    if long_form:

      long_form_output = (
          'checksum: {checksum} ({checksum_ok})\n'
          'boto version: {boto_version}\n'
          'python version: {python_version}\n'
          'OS: {os_version}\n'
          'multiprocessing available: {multiprocessing_available}\n'
          'using cloud sdk: {cloud_sdk}\n'
          'pass cloud sdk credentials to gsutil: {cloud_sdk_credentials}\n'
          'config path(s): {config_paths}\n'
          'gsutil path: {gsutil_path}\n'
          'compiled crcmod: {compiled_crcmod}\n'
          'installed via package manager: {is_package_install}\n'
          'editable install: {is_editable_install}\n')

      sys.stdout.write(
          long_form_output.format(
              checksum=cur_checksum,
              checksum_ok=checksum_ok_str,
              boto_version=boto.__version__,
              python_version=sys.version.replace('\n', ''),
              os_version='%s %s' % (platform.system(), platform.release()),
              multiprocessing_available=(
                  CheckMultiprocessingAvailableAndInit().is_available),
              cloud_sdk=system_util.InvokedViaCloudSdk(),
              cloud_sdk_credentials=system_util.CloudSdkCredPassingEnabled(),
              config_paths=config_paths,
              gsutil_path=(GetCloudSdkGsutilWrapperScriptPath() or
                           gslib.GSUTIL_PATH),
              compiled_crcmod=UsingCrcmodExtension(),
              is_package_install=gslib.IS_PACKAGE_INSTALL,
              is_editable_install=gslib.IS_EDITABLE_INSTALL,
          ))

    return 0

  def _ComputeCodeChecksum(self):
    """Computes a checksum of gsutil code.

    This checksum can be used to determine if users locally modified
    gsutil when requesting support. (It's fine for users to make local mods,
    but when users ask for support we ask them to run a stock version of
    gsutil so we can reduce possible variables.)

    Returns:
      MD5 checksum of gsutil code.
    """
    if gslib.IS_PACKAGE_INSTALL:
      return 'PACKAGED_GSUTIL_INSTALLS_DO_NOT_HAVE_CHECKSUMS'
    m = md5()
    # Checksum gsutil and all .py files under gslib directory.
    files_to_checksum = [gslib.GSUTIL_PATH]
    for root, _, files in os.walk(gslib.GSLIB_DIR):
      for filepath in files:
        if filepath.endswith('.py'):
          files_to_checksum.append(os.path.join(root, filepath))
    # Sort to ensure consistent checksum build, no matter how os.walk
    # orders the list.
    for filepath in sorted(files_to_checksum):
      if six.PY2:
        f = open(filepath, 'rb')
        content = f.read()
        content = re.sub(r'(\r\n|\r|\n)', b'\n', content)
        m.update(content)
        f.close()
      else:
        f = open(filepath, 'r', encoding=UTF8)
        content = f.read()
        content = re.sub(r'(\r\n|\r|\n)', '\n', content)
        m.update(content.encode(UTF8))
        f.close()
    return m.hexdigest()


def GetCloudSdkGsutilWrapperScriptPath():
  """If gsutil was invoked via the Cloud SDK, find its gsutil wrapper script.

  Returns:
    (str) The path to the Cloud SDK's gsutil wrapper script, or an empty string
    if gsutil was not invoked via the Cloud SDK or the wrapper script could not
    be found at its expected path.
  """
  # If running via the Cloud SDK, the "real" gsutil script was probably invoked
  # indirectly through gcloud's gsutil wrapper script (the former is at
  # <sdk-root>/platform/gsutil/gsutil, while the latter is located at
  # <sdk-root>/bin/gsutil). We should print the wrapper script's path if it
  # exists and we were invoked via the Cloud SDK.
  gsutil_path = gslib.GSUTIL_PATH
  if system_util.InvokedViaCloudSdk():
    platform_path_suffix = os.path.join('platform', 'gsutil', 'gsutil')
    if gsutil_path.endswith(platform_path_suffix):
      bin_path = os.path.join(
          gsutil_path[0:gsutil_path.rfind(platform_path_suffix)],
          'bin',
          'gsutil',
      )
      if os.path.exists(bin_path):
        return bin_path

  return ''
