# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import posixpath
from abc import ABCMeta, abstractmethod
from shutil import which

import six
from mozdevice import ADBDeviceFactory
from mozprofile import (
    ChromeProfile,
    ChromiumProfile,
    FirefoxProfile,
    Profile,
    ThunderbirdProfile,
)

here = os.path.abspath(os.path.dirname(__file__))


def get_app_context(appname):
    context_map = {
        "chrome": ChromeContext,
        "chromium": ChromiumContext,
        "default": DefaultContext,
        "fennec": FennecContext,
        "firefox": FirefoxContext,
        "thunderbird": ThunderbirdContext,
    }
    if appname not in context_map:
        raise KeyError("Application '%s' not supported!" % appname)
    return context_map[appname]


class DefaultContext(object):
    profile_class = Profile


@six.add_metaclass(ABCMeta)
class RemoteContext(object):
    device = None
    _remote_profile = None
    _adb = None
    profile_class = Profile
    _bindir = None
    remote_test_root = ""
    remote_process = None

    @property
    def bindir(self):
        if self._bindir is None:
            paths = [which("emulator")]
            paths = [p for p in paths if p is not None if os.path.isfile(p)]
            if not paths:
                self._bindir = ""
            else:
                self._bindir = os.path.dirname(paths[0])
        return self._bindir

    @property
    def adb(self):
        if not self._adb:
            paths = [
                os.environ.get("ADB"),
                os.environ.get("ADB_PATH"),
                self.which("adb"),
            ]
            paths = [p for p in paths if p is not None if os.path.isfile(p)]
            if not paths:
                raise OSError(
                    "Could not find the adb binary, make sure it is on your"
                    "path or set the $ADB_PATH environment variable."
                )
            self._adb = paths[0]
        return self._adb

    @property
    def remote_profile(self):
        if not self._remote_profile:
            self._remote_profile = posixpath.join(self.remote_test_root, "profile")
        return self._remote_profile

    def which(self, binary):
        paths = os.environ.get("PATH", {}).split(os.pathsep)
        if self.bindir is not None and os.path.abspath(self.bindir) not in paths:
            paths.insert(0, os.path.abspath(self.bindir))
            os.environ["PATH"] = os.pathsep.join(paths)

        return which(binary)

    @abstractmethod
    def stop_application(self):
        """Run (device manager) command to stop application."""
        pass


devices = {}


class FennecContext(RemoteContext):
    _remote_profiles_ini = None
    _remote_test_root = None

    def __init__(self, app=None, adb_path=None, avd_home=None, device_serial=None):
        self._adb = adb_path
        self.avd_home = avd_home
        self.remote_process = app
        self.device_serial = device_serial
        self.device = self.get_device(self.adb, device_serial)

    def get_device(self, adb_path, device_serial):
        # Create a mozdevice.ADBDevice object for the specified device_serial
        # and cache it for future use. If the same device_serial is subsequently
        # requested, retrieve it from the cache to avoid costly re-initialization.
        global devices
        if device_serial in devices:
            device = devices[device_serial]
        else:
            device = ADBDeviceFactory(adb=adb_path, device=device_serial)
            devices[device_serial] = device
        return device

    def stop_application(self):
        self.device.stop_application(self.remote_process)

    @property
    def remote_test_root(self):
        if not self._remote_test_root:
            self._remote_test_root = self.device.test_root
        return self._remote_test_root

    @property
    def remote_profiles_ini(self):
        if not self._remote_profiles_ini:
            self._remote_profiles_ini = posixpath.join(
                "/data", "data", self.remote_process, "files", "mozilla", "profiles.ini"
            )
        return self._remote_profiles_ini


class FirefoxContext(object):
    profile_class = FirefoxProfile


class ThunderbirdContext(object):
    profile_class = ThunderbirdProfile


class ChromeContext(object):
    profile_class = ChromeProfile


class ChromiumContext(object):
    profile_class = ChromiumProfile
