# Copyright (c) 2011 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.
"""Platform-specific utility methods shared by several scripts."""

import os
import re
import subprocess
import sys

import google.path_utils

# Cache a single cygpath process for use throughout, even across instances of
# the PlatformUtility class.
_cygpath_proc = None

class PlatformUtility(object):
  def __init__(self, base_dir):
    """Args:
         base_dir: a directory above which third_party/cygwin can be found,
             used to locate the cygpath executable for path conversions.
    """
    self._cygwin_root = None
    self._base_dir = base_dir

  def _CygwinRoot(self):
    """Returns the full path to third_party/cygwin/."""
    if not self._cygwin_root:
      self._cygwin_root = google.path_utils.FindUpward(self._base_dir,
                                                       'third_party', 'cygwin')
    return self._cygwin_root

  def _PathToExecutable(self, executable):
    """Returns the full path to an executable in Cygwin's bin dir."""
    return os.path.join(self._CygwinRoot(), 'bin', executable)

  def GetAbsolutePath(self, path, force=False):
    """Returns an absolute windows path. If platform is cygwin, converts it to
    windows style using cygpath.

    For performance reasons, we use a single cygpath process, shared among all
    instances of this class. Otherwise Python can run out of file handles.
    """
    if not force and sys.platform != "cygwin":
      return os.path.abspath(path)
    global _cygpath_proc
    if not _cygpath_proc:
      cygpath_command = [self._PathToExecutable("cygpath.exe"),
                         "-a", "-m", "-f", "-"]
      _cygpath_proc = subprocess.Popen(cygpath_command,
                                       stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE)
    _cygpath_proc.stdin.write(path + "\n")
    return _cygpath_proc.stdout.readline().rstrip()

  def GetFilesystemRoot(self):
    """Returns the root directory of the file system."""
    return os.environ['SYSTEMDRIVE'] + '\\'

  def GetTempDirectory(self):
    """Returns the file system's base temp directory, or the filesystem root
    if the standard temp directory can't be determined.

    Note that this does not use a random subdirectory, so it's not
    intrinsically secure.  If you need a secure subdir, use the tempfile
    package.
    """
    return os.environ.get('TEMP', self.GetFilesystemRoot())

  def FilenameToUri(self, path, use_http=False, use_ssl=False, port=8000):
    """Convert a Windows style path to a URI.

    Args:
      path: For an http URI, the path relative to the httpd server's
          DocumentRoot; for a file URI, the full path to the file.
      use_http: if True, returns a URI of the form http://127.0.0.1:8000/.
          If False, returns a file:/// URI.
      use_ssl: if True, returns HTTPS URL (https://127.0.0.1:8000/).
          This parameter is ignored if use_http=False.
      port: The port number to append when returning an HTTP URI
    """
    if use_http:
      protocol = 'http'
      if use_ssl:
        protocol = 'https'
      path = path.replace("\\", "/")
      return "%s://127.0.0.1:%s/%s" % (protocol, str(port), path)
    return "file:///" + self.GetAbsolutePath(path)

  def GetStartHttpdCommand(self, output_dir,
                           httpd_conf_path, mime_types_path,
                           document_root=None, apache2=False):
    """Prepares the config file and output directory to start an httpd server.
    Returns a list of strings containing the server's command line+args.

    Args:
      output_dir: the path to the server's output directory, for log files.
          It will be created if necessary.
      httpd_conf_path: full path to the httpd.conf file to be used.
      mime_types_path: full path to the mime.types file to be used.
      document_root: full path to the DocumentRoot.  If None, the DocumentRoot
          from the httpd.conf file will be used.  Note that the httpd.conf
          file alongside this script does not specify any DocumentRoot, so if
          you're using that one, be sure to specify a document_root here.
      apache2: boolean if true will cause this function to return start
               command for Apache 2.x as opposed to Apache 1.3.x
    """

    if document_root:
      document_root = GetCygwinPath(document_root)
    exe_name = "httpd"
    cert_file = ""
    if apache2:
      exe_name = "httpd2"
      cert_file = google.path_utils.FindUpward(self._base_dir, 'tools',
                                               'python', 'google',
                                               'httpd_config', 'httpd2.pem')
    httpd_vars = {
      "httpd_executable_path": GetCygwinPath(
          os.path.join(self._CygwinRoot(), "usr", "sbin", exe_name)),
      "httpd_conf_path": GetCygwinPath(httpd_conf_path),
      "ssl_certificate_file": GetCygwinPath(cert_file),
      "document_root" : document_root,
      "server_root": GetCygwinPath(os.path.join(self._CygwinRoot(), "usr")),
      "mime_types_path": GetCygwinPath(mime_types_path),
      "output_dir": GetCygwinPath(output_dir),
      "bindir": GetCygwinPath(os.path.join(self._CygwinRoot(), "bin")),
      "user": os.environ.get("USERNAME", os.environ.get("USER", "")),
    }
    if not httpd_vars["user"]:
      # Failed to get the username from the environment; use whoami.exe
      # instead.
      proc = subprocess.Popen(self._PathToExecutable("whoami.exe"),
                              stdout=subprocess.PIPE)
      httpd_vars["user"] = proc.stdout.read().strip()

    if not httpd_vars["user"]:
      raise Exception("Failed to get username.")

    google.path_utils.MaybeMakeDirectory(output_dir)

    # We have to wrap the command in bash because the cygwin environment
    # is required for httpd to run.
    # -C: process directive before reading config files
    # -c: process directive after reading config files
    # Apache wouldn't run CGIs with permissions==700 unless we add
    # -c User "<username>"
    bash = self._PathToExecutable("bash.exe")
    httpd_cmd_string = (
      ' PATH=%(bindir)s %(httpd_executable_path)s'
      ' -f %(httpd_conf_path)s'
      ' -c \'TypesConfig "%(mime_types_path)s"\''
      ' -c \'CustomLog "%(output_dir)s/access_log.txt" common\''
      ' -c \'ErrorLog "%(output_dir)s/error_log.txt"\''
      ' -c \'PidFile "%(output_dir)s/httpd.pid"\''
      ' -C \'User "%(user)s"\''
      ' -C \'ServerRoot "%(server_root)s"\''
    )
    if apache2:
      httpd_cmd_string = ('export CYGWIN=server;' + httpd_cmd_string +
          ' -c \'SSLCertificateFile "%(ssl_certificate_file)s"\'')
    if document_root:
      httpd_cmd_string += ' -C \'DocumentRoot "%(document_root)s"\''

    httpd_cmd = [bash, "-c", httpd_cmd_string % httpd_vars]
    return httpd_cmd

  def GetStopHttpdCommand(self):
    """Returns a list of strings that contains the command line+args needed to
    stop the http server used in the http tests.
    """
    # Force kill (/f) *all* httpd processes.  This has the side effect of
    # killing httpd processes that we didn't start.
    return ["taskkill.exe", "/f", "/im", "httpd*"]

###########################################################################
# This method is specific to windows, expected to be used only by *_win.py
# files.

def GetCygwinPath(path):
  """Convert a Windows path to a cygwin path.

  The cygpath utility insists on converting paths that it thinks are Cygwin
  root paths to what it thinks the correct roots are.  So paths such as
  "C:\b\slave\webkit-release-kjs\build\third_party\cygwin\bin" are converted to
  plain "/usr/bin".  To avoid this, we do the conversion manually.

  The path is expected to be an absolute path, on any drive.
  """
  drive_regexp = re.compile(r'([a-z]):[/\\]', re.IGNORECASE)
  def LowerDrive(matchobj):
    return '/cygdrive/%s/' % matchobj.group(1).lower()
  path = drive_regexp.sub(LowerDrive, path)
  return path.replace('\\', '/')
