# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import abc
import logging
import os
import plistlib
import subprocess
import time
import typing

import utils


class BrowserDriver(abc.ABC):
    """Abstract Base Class encapsulating browser setup and tear down."""

    def __init__(self, browser_name: str, process_name: str):
        self.name = browser_name
        self.process_name = process_name
        self.browser_process = None

        # LaunchServices can get confused when an application is launched from
        # more than one location and break AppleScript commands. Always launch
        # browsers from /Applications to avoid such problems.
        self.executable_path = os.path.join(
            "/Applications", f"{self.process_name}.app"
        )

        if not os.path.exists(self.executable_path):
            raise ValueError(f"Application doesn't exist for {browser_name}.")

    @abc.abstractmethod
    def Launch(self):
        """Starts the browser and ensures it is started before returning."""
        pass

    def TearDown(self):
        """Terminates the browser and ensures it's cleaned up before returning."""
        logging.debug(f"Tearing down {self.process_name}")
        if self.browser_process:
            utils.TerminateProcess(self.browser_process)

    def GetApplicationInfo(self) -> typing.Dict:
        """Returns the Info.plist data in the application folder."""
        plist_path = os.path.join(
            self.executable_path, "Contents", "Info.plist"
        )
        with open(plist_path, 'rb') as plist_file:
            return plistlib.load(plist_file)

    def Summary(self):
        """Returns a dictionary describing the browser."""
        info = self.GetApplicationInfo()
        return {
            'name': self.name,
            'version': info['CFBundleShortVersionString'],
            'identifier': info['CFBundleIdentifier'],
        }

    def _EnsureStarted(self):
        """Waits until a browser with the given `process_name` is running."""
        while not self.browser_process:
            self.browser_process = utils.FindProcess(self.process_name)
            time.sleep(0.100)
            logging.debug(f"Waiting for {self.process_name} to start")
        logging.debug(f"{self.process_name} started")


class SafariDriver(BrowserDriver):
    def __init__(self, extra_args=[]):
        super().__init__("safari", "Safari")
        self.extra_args = extra_args

    def Launch(self):
        subprocess.call(["open", "-a", "Safari"])
        # Call prep_safari.scpt to make sure the run starts clean. See file
        # comment for details.
        subprocess.call(
            [
                "osascript",
                os.path.join(
                    os.path.dirname(__file__),
                    "driver_scripts_templates",
                    "prep_safari.scpt",
                ),
            ]
        )
        subprocess.call(["open", "-a", "Safari", "--args"] + self.extra_args)

        self._EnsureStarted()


class ChromiumDriver(BrowserDriver):
    def __init__(
        self,
        browser_name: str,
        variation: str,
        process_name: str,
        extra_args=[],
    ):
        if variation != "":
            browser_name += f"_{variation}"
        super().__init__(browser_name, process_name)
        self.extra_args = extra_args

    def Launch(self):
        open_args = ["-a", self.process_name]
        subprocess.call(
            ["open"]
            + open_args
            + ["--args"]
            + [
                "--enable-benchmarking",
                "--disable-stack-profiler",
                "--no-first-run",
                "--no-default-browser-check",
            ]
            + self.extra_args
        )

        self._EnsureStarted()

    def Summary(self):
        """Returns a dictionary describing the browser."""
        info = self.GetApplicationInfo()
        return {
            'name': self.name,
            'identifier': info['CFBundleIdentifier'],
            'version': info['CFBundleShortVersionString'],
            'commit': info['SCMRevision'],
            'extra_args': self.extra_args,
        }


# Helper functions to get default browser configurations.


def Safari():
    return SafariDriver()


def Chrome(variation, extra_args=[]):
    return ChromiumDriver(
        "chrome", variation, "Google Chrome", extra_args=extra_args
    )


def Canary(variation, extra_args=[]):
    return ChromiumDriver(
        "canary", variation, "Google Chrome Canary", extra_args=extra_args
    )


def Chromium(variation, extra_args=[]):
    return ChromiumDriver(
        "chromium", variation, "Chromium", extra_args=extra_args
    )


def Edge(variation, extra_args=[]):
    return ChromiumDriver(
        "edge", variation, "Microsoft Edge", extra_args=extra_args
    )


PROCESS_NAMES = [
    "Safari",
    "Google Chrome",
    "Google Chrome Canary",
    "Chromium",
    "Microsoft Edge",
]


def MakeBrowserDriver(
    browser_name: str,
    variation: str,
    chrome_user_dir=None,
    output_dir=None,
    tracing_mode=False,
    extra_command_line=None,
) -> BrowserDriver:
    """Creates browser driver by name.

    Args:
      browser_name: Identifier for the browser to create. Supported browsers
        are: safari, chrome and chromium.
      chrome_user_dir: Optional user directory path to use for chrome.
    """

    if "safari" == browser_name:
        return Safari()
    if browser_name in ["chrome", "chromium", "canary", "edge"]:
        if chrome_user_dir:
            chrome_extra_arg = [f"--user-data-dir={chrome_user_dir}"]
        else:
            chrome_extra_arg = ["--guest"]
        if variation == 'AlignWakeUps':
            chrome_extra_arg += ['--enable-features=AlignWakeUps']

        if tracing_mode:
            chrome_extra_arg += [
                '--enable-tracing=toplevel,toplevel.flow,mojom,navigation'
            ]
            trace_path = os.path.join(output_dir, "chrometrace.log")
            chrome_extra_arg += [
                f'--trace-startup-file={os.path.abspath(trace_path)}'
            ]

        if extra_command_line:
            for command in extra_command_line:
                # Quotes are needed to avoid to avoid cli replacement.
                command = command.replace('"', '')
                chrome_extra_arg += [command]

        if browser_name == "chrome":
            return Chrome(variation, extra_args=chrome_extra_arg)
        if browser_name == "canary":
            return Canary(variation, extra_args=chrome_extra_arg)
        elif browser_name == "chromium":
            return Chromium(variation, extra_args=chrome_extra_arg)
        elif browser_name == "edge":
            return Edge(variation, extra_args=chrome_extra_arg)
    return None
