# devices/file.py
# Classes to represent various types of files and directories.
#
# Copyright (C) 2009-2014  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): David Lehman <dlehman@redhat.com>
#

import os
import stat

from .. import util
from ..storage_log import log_method_call
from ..size import Size

import logging
log = logging.getLogger("blivet")

from .storage import StorageDevice


class FileDevice(StorageDevice):

    """ A file on a filesystem.

        This exists because of swap files.
    """
    _type = "file"
    _dev_dir = ""

    def __init__(self, path, fmt=None, size=None,
                 exists=False, parents=None):
        """
            :param path: full path to the file
            :type path: str
            :keyword exists: does this device exist?
            :type exists: bool
            :keyword size: the device's size
            :type size: :class:`~.size.Size`
            :keyword parents: a list of parent devices
            :type parents: list of :class:`StorageDevice`
            :keyword fmt: this device's formatting
            :type fmt: :class:`~.formats.DeviceFormat` or a subclass of it
        """
        if not os.path.isabs(path):
            raise ValueError("FileDevice requires an absolute path")

        StorageDevice.__init__(self, path, fmt=fmt, size=size,
                               exists=exists, parents=parents)

    @property
    def fstab_spec(self):
        return self.name

    @property
    def path(self):
        try:
            root = self.parents[0].format.system_mountpoint
            mountpoint = self.parents[0].format.mountpoint
        except (AttributeError, IndexError):
            root = ""
        else:
            # trim the mountpoint down to the chroot since we already have
            # the otherwise fully-qualified path
            while mountpoint.endswith("/"):
                mountpoint = mountpoint[:-1]
            if mountpoint:
                root = root[:-len(mountpoint)]

        return os.path.normpath("%s%s" % (root, self.name))

    def read_current_size(self):
        size = Size(0)
        if self.exists and os.path.exists(self.path):
            st = os.stat(self.path)
            size = Size(st[stat.ST_SIZE])

        return size

    def _pre_setup(self, orig=False):
        if self.format and self.format.exists and not self.format.status:
            self.format.device = self.path

        return StorageDevice._pre_setup(self, orig=orig)

    def _pre_teardown(self, recursive=None):
        if self.format and self.format.exists and not self.format.status:
            self.format.device = self.path

        return StorageDevice._pre_teardown(self, recursive=recursive)

    def _create(self):
        """ Create the device. """
        log_method_call(self, self.name, status=self.status)
        fd = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
        # all this fuss is so we write the zeros 1MiB at a time
        zero = "\0"
        block_size = 1024 ** 2
        (count, rem) = divmod(int(self.size), block_size)

        zeros = zero * block_size
        for _n in range(count):
            os.write(fd, zeros.encode("utf-8"))

        if rem:
            # write out however many more zeros it takes to hit our size target
            size_target = zero * rem
            os.write(fd, size_target.encode("utf-8"))

        os.close(fd)

    def _destroy(self):
        """ Destroy the device. """
        log_method_call(self, self.name, status=self.status)
        os.unlink(self.path)

    def is_name_valid(self, name):
        # Override StorageDevice.is_name_valid to allow /
        return '\x00' not in name and name != '.' and name != '..'

    def update_sysfs_path(self):
        pass


class SparseFileDevice(FileDevice):

    """A sparse file on a filesystem.
    This exists for sparse disk images."""
    _type = "sparse file"

    def _create(self):
        """Create a sparse file."""
        log_method_call(self, self.name, status=self.status)
        util.create_sparse_file(self.path, self.size)


class DirectoryDevice(FileDevice):

    """ A directory on a filesystem.

        This exists because of bind mounts.
    """
    _type = "directory"

    def _create(self):
        """ Create the device. """
        log_method_call(self, self.name, status=self.status)
        util.makedirs(self.path)
