
# 
#   Copyright 2016 RIFT.IO Inc
#
#   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.
#

import os
import shutil

from . import package


class PackageStoreError(Exception):
    pass


class PackageExistsError(PackageStoreError):
    pass


class PackageNotFoundError(PackageStoreError):
    pass


class PackageFilesystemStore(object):
    """ This class is able to store/retreive/delete DescriptorPackages on disk

    To insulate components from having to deal with accessing the filesystem directly
    to deal with onboarded packages, this class provides a convenient interface for
    storing, retreiving, deleting packages stored on disk.  The interfaces deal directly
    with DescriptorPackages so clients are abstracted from the actual location on disk.
    """

    def __init__(self, log, root_dir):
        self._log = log
        self._root_dir = root_dir
        self._package_dirs = {}

        self.refresh()

    def _get_package_dir(self, package_id):
        return os.path.join(self._root_dir, package_id)

    def _get_package_files(self, package_id):
        package_files = {}

        package_dir = self._get_package_dir(package_id)

        for dirpath, dirnames, filenames in os.walk(package_dir):
            for name in filenames:
                file_path = os.path.join(dirpath, name)
                file_rel_path = os.path.relpath(file_path, package_dir)
                package_files[file_rel_path] = file_path

        return package_files

    def refresh(self):
        """ Refresh the package index from disk  """
        if not os.path.exists(self._root_dir):
            self._package_dirs = {}
            return

        package_dirs = {}
        for package_id_dir in os.listdir(self._root_dir):
            try:
                package_dir_path = os.path.join(self._root_dir, package_id_dir)
                if not os.path.isdir(package_dir_path):
                    self._log.warning("Unknown file in package store: %s", package_dir_path)
                    continue

                files = os.listdir(package_dir_path)
                if len(files) == 0:
                    self._log.warning("Package directory %s is empty", package_dir_path)
                    continue

                package_id = os.path.basename(package_id_dir)
                package_dirs[package_id] = package_id_dir

            except OSError as e:
                self._log.warning("Failed to read packages from %s: %s",
                                  package_dir_path, str(e))

        self._package_dirs = package_dirs

    def get_package(self, package_id):
        """ Get a DescriptorPackage on disk from the package descriptor id

        Arguments:
            package_id - The DescriptorPackage.descriptor_id

        Returns:
            A DescriptorPackage instance of the correct type

        Raises:
            PackageStoreError- The package could not be retrieved
        """
        if package_id not in self._package_dirs:
            msg = "Package %s not found in %s" % (package_id, self._root_dir)
            raise PackageNotFoundError(msg)

        package_files = self._get_package_files(package_id)
        package_dir = self._get_package_dir(package_id)

        def do_open(pkg_file):
            pkg_path = os.path.join(package_dir, pkg_file)
            return open(pkg_path, "rb")

        pkg = package.DescriptorPackage.from_package_files(self._log, do_open, package_files)
        for pkg_file in package_files:
            pkg.add_file(pkg_file)

        return pkg

    def store_package(self, pkg):
        """ Store a DescriptorPackage to disk

        Arguments:
            pkg - A DescriptorPackage

        Raises:
            PackageStoreError - The package could not be stored
        """
        if pkg.descriptor_id in self._package_dirs:
            raise PackageExistsError("Package %s already exists", pkg.descriptor_id)

        package_dir = self._get_package_dir(pkg.descriptor_id)

        try:
            os.makedirs(package_dir, exist_ok=True)
        except OSError as e:
            raise PackageStoreError("Failed to create package dir: %s", package_dir) from e

        try:
            self._log.debug("Storing package in dir %s", package_dir)
            pkg.extract(package_dir)
            self._log.debug("Package stored in dir %s", package_dir)
        except pkg.PackageError as e:
            raise PackageStoreError("Failed to extract package to package store") from e

        self._package_dirs[pkg.descriptor_id] = package_dir

    def delete_package(self, descriptor_id):
        """ Delete a stored DescriptorPackage

        Arguments:
            descriptor_id - The DescriptorPackage.descriptor_id

        Raises:
            PackageNotFoundError - The package could not be found
            PackageStoreError - The package could not be deleted
        """

        if descriptor_id not in self._package_dirs:
            raise PackageNotFoundError("Package %s does not exists", descriptor_id)

        package_dir = self._get_package_dir(descriptor_id)
        try:
            if os.path.exists(package_dir):
                self._log.debug("Removing stored package directory: %s", package_dir)
                shutil.rmtree(package_dir)
        except OSError as e:
            raise PackageStoreError(
                    "Failed to remove stored package directory: %s", package_dir
                    ) from e

        del self._package_dirs[descriptor_id]

    def update_package(self, pkg):
        """ Update a stored DescriptorPackage

        Arguments:
            pkg - A DescriptorPackage

        Raises:
            PackageNotFoundError - The package could not be found
            PackageStoreError - The package could not be deleted
        """
        self.delete_package(pkg.descriptor_id)
        self.store_package(pkg)


class NsdPackageFilesystemStore(PackageFilesystemStore):
    DEFAULT_ROOT_DIR = os.path.join(
            os.environ["RIFT_ARTIFACTS"],
            "launchpad", "packages", "nsd"
            )

    def __init__(self, log, root_dir=DEFAULT_ROOT_DIR):
        super().__init__(log, root_dir)


class VnfdPackageFilesystemStore(PackageFilesystemStore):
    DEFAULT_ROOT_DIR = os.path.join(
            os.environ["RIFT_ARTIFACTS"],
            "launchpad", "packages", "vnfd"
            )

    def __init__(self, log, root_dir=DEFAULT_ROOT_DIR):
        super().__init__(log, root_dir)

