# 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/.

from abc import ABCMeta, abstractmethod
import os
import shutil

import mozlog


class GaiaFileManager(object):
    """Abstract file manager for Gaia."""
    __metaclass__ = ABCMeta

    def __init__(self, device, log_level=mozlog.unstructured.ERROR):
        self._logger = mozlog.unstructured.getLogger('GaiaFileManager')
        self._logger.setLevel(log_level)
        self.device = device

    @abstractmethod
    def copy_file(self, source, destination):
        """Copy a file."""

    @abstractmethod
    def dir_exists(self, path):
        """Return true if path exists and is a directory."""
        return

    def duplicate_file(self, path, count):
        """Create duplicates of a file on the system and remove original."""
        original = path
        path, sep, filename = path.rpartition('/')
        # We copy the file we've just created rather than pushing it
        # multiple times, which would be much slower.
        for i in range(1, count + 1):
            # Make the remote filename unique by including an index
            if '.' in filename:
                indexed_filename = '_%d.'.join(iter(
                    filename.rsplit('.', 1))) % i
            else:
                indexed_filename = '%s_%d' % (filename, i)
            duplicate = '/'.join([path, indexed_filename])
            self.copy_file(original, duplicate)
        self.remove(original)

    @abstractmethod
    def file_exists(self, path):
        """Return true if path exists and is a file."""
        return

    @abstractmethod
    def list_items(self, path):
        """List items in path."""
        return

    @abstractmethod
    def make_dirs(self, filename):
        """Make directory structure."""

    @abstractmethod
    def pull_file(self, path):
        """Returns contents of file."""

    @abstractmethod
    def push_file(self, local_path, remote_path=None, count=1):
        """Push a file to the system."""

    @abstractmethod
    def remove(self, path):
        """Remove file or directory."""


class GaiaDeviceFileManager(GaiaFileManager):
    """File manager for Gaia instance running on a B2G device or emulator."""

    def copy_file(self, source, destination):
        self._logger.debug('Copying: %s to: %s' % (source, destination))
        self.device.manager.copyTree(source, destination)

    def dir_exists(self, path):
        self._logger.debug('Checking for existance of directory: %s' % path)
        return self.device.manager.dirExists(path)

    def file_exists(self, path):
        self._logger.debug('Checking for existance of file: %s' % path)
        return self.device.manager.fileExists(path)

    def list_items(self, path):
        self._logger.debug('Listing items in: %s' % path)
        return self.device.manager.listFiles(path)

    def make_dirs(self, filename):
        self.device.manager.mkDirs(filename)

    def pull_file(self, path):
        return self.device.manager.pullFile(path)

    def push_file(self, local_path, remote_path=None, count=1):
        # If remote path is not specified, use the storage path
        remote_path = remote_path or self.device.storage_path
        filename = local_path.rpartition(os.path.sep)[-1]
        remote_file = '/'.join([remote_path, filename])
        self.make_dirs(remote_file)
        self.device.manager.pushFile(local_path, remote_file)
        if count > 1:
            self.duplicate_file(remote_file, count)

    def remove(self, path):
        self._logger.debug('Removing: %s' % path)
        self.device.manager.removeDir(path)


class GaiaLocalFileManager(GaiaFileManager):
    """File manager for Gaia instance running locally such as desktop B2G."""

    def copy_file(self, source, destination):
        source = os.path.normpath(source)
        destination = os.path.normpath(destination)
        self._logger.debug('Copying: %s to: %s' % (source, destination))
        shutil.copy(source, destination)

    def dir_exists(self, path):
        path = os.path.normpath(path)
        self._logger.debug('Checking for existance of directory: %s' % path)
        return os.path.isdir(path)

    def file_exists(self, path):
        path = os.path.normpath(path)
        self._logger.debug('Checking for existance of file: %s' % path)
        return os.path.isfile(path)

    def list_items(self, path):
        path = os.path.normpath(path)
        self._logger.debug('Listing items in: %s' % path)
        return os.listdir(path)

    def make_dirs(self, filename):
        filename = os.path.normpath(filename)
        containing = os.path.dirname(filename)
        if not os.path.isdir(containing):
            self._logger.debug('Making path: %s' % containing)
            os.makedirs(containing)

    def pull_file(self, path):
        with open(path) as f:
            return f.read()

    def push_file(self, local_path, remote_path=None, count=1):
        # If remote path is not specified, use the storage path
        remote_path = remote_path or self.device.storage_path
        filename = local_path.rpartition(os.path.sep)[-1]
        remote_file = '/'.join([remote_path, filename])
        self.make_dirs(remote_file)

        path = os.path.normpath(remote_file)
        if os.path.isdir(path):
            raise Exception('Attempted to push a file (%s) to a directory '
                            '(%s)!' % (local_path, path))
        if not os.access(local_path, os.F_OK):
            raise Exception('File not found: %s' % local_path)
        self._logger.debug('Pushing: %s to: %s' % (local_path, path))
        self.copy_file(local_path, path)
        if count > 1:
            self.duplicate_file(remote_file, count)

    def remove(self, path):
        path = os.path.normpath(path)
        if os.path.isfile(path):
            self._logger.debug('Removing file: %s' % path)
            os.remove(path)
        elif os.path.isdir(path):
            self._logger.debug('Removing directory: %s' % path)
            shutil.rmtree(path)
