# Copyright (c) 2014 Cloudbase Solutions SRL
# All Rights Reserved.
#
#    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 re
import sys

from oslo.utils import units
from oslo_config import cfg

from cinder import exception
from cinder.i18n import _
from cinder.image import image_utils
from cinder.openstack.common import fileutils
from cinder.openstack.common import log as logging
from cinder import utils
from cinder.volume.drivers import smbfs
from cinder.volume.drivers.windows import remotefs
from cinder.volume.drivers.windows import vhdutils

VERSION = '1.0.0'

LOG = logging.getLogger(__name__)

CONF = cfg.CONF
CONF.set_default('smbfs_shares_config', r'C:\OpenStack\smbfs_shares.txt')
CONF.set_default('smbfs_mount_point_base', r'C:\OpenStack\_mnt')
CONF.set_default('smbfs_default_volume_format', 'vhd')


class WindowsSmbfsDriver(smbfs.SmbfsDriver):
    VERSION = VERSION

    def __init__(self, *args, **kwargs):
        super(WindowsSmbfsDriver, self).__init__(*args, **kwargs)
        self.base = getattr(self.configuration,
                            'smbfs_mount_point_base',
                            CONF.smbfs_mount_point_base)
        opts = getattr(self.configuration,
                       'smbfs_mount_options',
                       CONF.smbfs_mount_options)
        self._remotefsclient = remotefs.WindowsRemoteFsClient(
            'cifs', root_helper=None, smbfs_mount_point_base=self.base,
            smbfs_mount_options=opts)
        self.vhdutils = vhdutils.VHDUtils()

    def do_setup(self, context):
        self._check_os_platform()
        super(WindowsSmbfsDriver, self).do_setup(context)

    def _check_os_platform(self):
        if sys.platform != 'win32':
            _msg = _("This system platform (%s) is not supported. This "
                     "driver supports only Win32 platforms.") % sys.platform
            raise exception.SmbfsException(_msg)

    def _do_create_volume(self, volume):
        volume_path = self.local_path(volume)
        volume_format = self.get_volume_format(volume)
        volume_size_bytes = volume['size'] * units.Gi

        if os.path.exists(volume_path):
            err_msg = _('File already exists at: %s') % volume_path
            raise exception.InvalidVolume(err_msg)

        if volume_format not in (self._DISK_FORMAT_VHD,
                                 self._DISK_FORMAT_VHDX):
            err_msg = _("Unsupported volume format: %s ") % volume_format
            raise exception.InvalidVolume(err_msg)

        self.vhdutils.create_dynamic_vhd(volume_path, volume_size_bytes)

    def _ensure_share_mounted(self, smbfs_share):
        mnt_options = {}
        if self.shares.get(smbfs_share) is not None:
            mnt_flags = self.shares[smbfs_share]
            mnt_options = self.parse_options(mnt_flags)[1]
        self._remotefsclient.mount(smbfs_share, mnt_options)

    def _delete(self, path):
        fileutils.delete_if_exists(path)

    def _get_capacity_info(self, smbfs_share):
        """Calculate available space on the SMBFS share.

        :param smbfs_share: example //172.18.194.100/var/smbfs
        """
        total_size, total_available = self._remotefsclient.get_capacity_info(
            smbfs_share)
        total_allocated = self._get_total_allocated(smbfs_share)
        return_value = [total_size, total_available, total_allocated]
        LOG.info('Smb share %s Total size %s Total allocated %s'
                 % (smbfs_share, total_size, total_allocated))
        return [float(x) for x in return_value]

    def _get_total_allocated(self, smbfs_share):
        elements = os.listdir(smbfs_share)
        total_allocated = 0
        for element in elements:
            element_path = os.path.join(smbfs_share, element)
            if not self._remotefsclient.is_symlink(element_path):
                if "snapshot" in element:
                    continue
                if re.search(r'\.vhdx?$', element):
                    total_allocated += self.vhdutils.get_vhd_size(
                        element_path)['VirtualSize']
                    continue
                if os.path.isdir(element_path):
                    total_allocated += self._get_total_allocated(element_path)
                    continue
            total_allocated += os.path.getsize(element_path)

        return total_allocated

    def _img_commit(self, snapshot_path):
        self.vhdutils.merge_vhd(snapshot_path)
        self._delete(snapshot_path)

    def _rebase_img(self, image, backing_file, volume_format):
        # Relative path names are not supported in this case.
        image_dir = os.path.dirname(image)
        backing_file_path = os.path.join(image_dir, backing_file)
        self.vhdutils.reconnect_parent(image, backing_file_path)

    def _qemu_img_info(self, path, volume_name=None):
        # This code expects to deal only with relative filenames.
        # As this method is needed by the upper class and qemu-img does
        # not fully support vhdx images, for the moment we'll use Win32 API
        # for retrieving image information.
        parent_path = self.vhdutils.get_vhd_parent_path(path)
        file_format = os.path.splitext(path)[1][1:].lower()

        if parent_path:
            backing_file_name = os.path.split(parent_path)[1].lower()
        else:
            backing_file_name = None

        class ImageInfo(object):
            def __init__(self, image, backing_file):
                self.image = image
                self.backing_file = backing_file
                self.file_format = file_format

        return ImageInfo(os.path.basename(path),
                         backing_file_name)

    def _do_create_snapshot(self, snapshot, backing_file, new_snap_path):
        backing_file_full_path = os.path.join(
            self._local_volume_dir(snapshot['volume']),
            backing_file)
        self.vhdutils.create_differencing_vhd(new_snap_path,
                                              backing_file_full_path)

    def _do_extend_volume(self, volume_path, size_gb):
        self.vhdutils.resize_vhd(volume_path, size_gb * units.Gi)

    @utils.synchronized('smbfs', external=False)
    def copy_volume_to_image(self, context, volume, image_service, image_meta):
        """Copy the volume to the specified image."""

        # If snapshots exist, flatten to a temporary image, and upload it

        active_file = self.get_active_image_from_info(volume)
        active_file_path = os.path.join(self._local_volume_dir(volume),
                                        active_file)
        backing_file = self.vhdutils.get_vhd_parent_path(active_file_path)
        root_file_fmt = self.get_volume_format(volume)

        temp_path = None

        try:
            if backing_file or root_file_fmt == self._DISK_FORMAT_VHDX:
                temp_file_name = '%s.temp_image.%s.%s' % (
                    volume['id'],
                    image_meta['id'],
                    self._DISK_FORMAT_VHD)
                temp_path = os.path.join(self._local_volume_dir(volume),
                                         temp_file_name)

                self.vhdutils.convert_vhd(active_file_path, temp_path)
                upload_path = temp_path
            else:
                upload_path = active_file_path

            image_utils.upload_volume(context,
                                      image_service,
                                      image_meta,
                                      upload_path,
                                      self._DISK_FORMAT_VHD)
        finally:
            if temp_path:
                self._delete(temp_path)

    def copy_image_to_volume(self, context, volume, image_service, image_id):
        """Fetch the image from image_service and write it to the volume."""
        volume_format = self.get_volume_format(volume, qemu_format=True)
        image_meta = image_service.show(context, image_id)

        fetch_format = volume_format
        fetch_path = self.local_path(volume)
        self._delete(fetch_path)
        qemu_version = self.get_qemu_version()

        needs_conversion = False

        if (qemu_version < [1, 7] and (
                volume_format == self._DISK_FORMAT_VHDX and
                image_meta['disk_format'] != self._DISK_FORMAT_VHDX)):
            needs_conversion = True
            fetch_format = 'vpc'
            temp_file_name = '%s.temp_image.%s.%s' % (
                volume['id'],
                image_meta['id'],
                self._DISK_FORMAT_VHD)
            fetch_path = os.path.join(self._local_volume_dir(volume),
                                      temp_file_name)

        image_utils.fetch_to_volume_format(
            context, image_service, image_id,
            fetch_path, fetch_format,
            self.configuration.volume_dd_blocksize)

        if needs_conversion:
            self.vhdutils.convert_vhd(fetch_path, self.local_path(volume))
            self._delete(fetch_path)

        self.vhdutils.resize_vhd(self.local_path(volume),
                                 volume['size'] * units.Gi)

    def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
        """Copy data from snapshot to destination volume."""

        LOG.debug("snapshot: %(snap)s, volume: %(vol)s, "
                  "volume_size: %(size)s" %
                  {'snap': snapshot['id'],
                   'vol': volume['id'],
                   'size': snapshot['volume_size']})

        info_path = self._local_path_volume_info(snapshot['volume'])
        snap_info = self._read_info_file(info_path)
        vol_dir = self._local_volume_dir(snapshot['volume'])

        forward_file = snap_info[snapshot['id']]
        forward_path = os.path.join(vol_dir, forward_file)

        # Find the file which backs this file, which represents the point
        # when this snapshot was created.
        img_info = self._qemu_img_info(forward_path)
        snapshot_path = os.path.join(vol_dir, img_info.backing_file)

        volume_path = self.local_path(volume)
        self._delete(volume_path)
        self.vhdutils.convert_vhd(snapshot_path,
                                  volume_path)
        self.vhdutils.resize_vhd(volume_path, volume_size * units.Gi)
