
from __future__ import absolute_import

import logging
import time

import six

from vdsm.storage import constants as sc
from vdsm.storage import exception


_SIZE = "SIZE"


class VolumeMetadata(object):

    log = logging.getLogger('storage.VolumeMetadata')

    def __init__(self, domain, image, puuid, capacity, format, type, voltype,
                 disktype, description="", legality=sc.ILLEGAL_VOL, ctime=None,
                 generation=sc.DEFAULT_GENERATION):
        self.domain = domain
        self.image = image
        self.puuid = puuid
        self.capacity = capacity
        self.format = format
        self.type = type
        self.voltype = voltype
        self.disktype = disktype
        self.description = description
        self.legality = legality
        self.ctime = int(time.time()) if ctime is None else ctime
        self.generation = generation

    @classmethod
    def from_lines(cls, lines):
        md = {}
        for line in lines:
            if line.startswith("EOF"):
                break
            if '=' not in line:
                continue
            key, value = line.split('=', 1)
            md[key.strip()] = value.strip()

        try:
            if sc.CAPACITY in md:
                capacity = int(md[sc.CAPACITY])
            else:
                capacity = int(md[_SIZE]) * sc.BLOCK_SIZE_512

            return cls(domain=md[sc.DOMAIN],
                       image=md[sc.IMAGE],
                       puuid=md[sc.PUUID],
                       capacity=capacity,
                       format=md[sc.FORMAT],
                       type=md[sc.TYPE],
                       voltype=md[sc.VOLTYPE],
                       disktype=md[sc.DISKTYPE],
                       description=md[sc.DESCRIPTION],
                       legality=md[sc.LEGALITY],
                       ctime=int(md[sc.CTIME]),
                       generation=int(md.get(sc.GENERATION,
                                             sc.DEFAULT_GENERATION)))
        except KeyError as e:
            if "NONE" in md:
                raise exception.MetadataCleared("lines={}".format(lines))

            raise exception.MetaDataKeyNotFoundError(
                "key={} lines={}".format(e, lines))

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, desc):
        self._description = self.validate_description(desc)

    @property
    def capacity(self):
        return self._capacity

    @capacity.setter
    def capacity(self, value):
        self._capacity = self._validate_integer("capacity", value)

    @property
    def ctime(self):
        return self._ctime

    @ctime.setter
    def ctime(self, value):
        self._ctime = self._validate_integer("ctime", value)

    @property
    def generation(self):
        return self._generation

    @generation.setter
    def generation(self, value):
        self._generation = self._validate_integer("generation", value)

    @classmethod
    def _validate_integer(cls, property, value):
        if not isinstance(value, six.integer_types):
            raise AssertionError(
                "Invalid value for metadata property {!r}: {!r}".format(
                    property, value))
        return value

    @classmethod
    def validate_description(cls, desc):
        desc = str(desc)
        if len(desc) > sc.DESCRIPTION_SIZE:
            cls.log.warning("Description is too long, truncating to %d bytes",
                            sc.DESCRIPTION_SIZE)
            desc = desc[:sc.DESCRIPTION_SIZE]
        return desc

    def storage_format(self, domain_version, **overrides):
        info = {
            sc.CTIME: str(self.ctime),
            sc.DESCRIPTION: self.description,
            sc.DISKTYPE: self.disktype,
            sc.DOMAIN: self.domain,
            sc.FORMAT: self.format,
            sc.GENERATION: self.generation,
            sc.IMAGE: self.image,
            sc.LEGALITY: self.legality,
            sc.PUUID: self.puuid,
            sc.TYPE: self.type,
            sc.VOLTYPE: self.voltype,
        }
        if domain_version < 5:
            info[sc.MTIME] = 0

            info[_SIZE] = self.capacity // sc.BLOCK_SIZE_512
        else:
            info[sc.CAPACITY] = self.capacity

        info.update(overrides)

        keys = sorted(info.keys())
        lines = ["%s=%s\n" % (key, info[key]) for key in keys]
        lines.append("EOF\n")
        data = "".join(lines)
        if len(data) > sc.METADATA_SIZE:
            raise exception.MetadataOverflowError(data)
        return data


    _fieldmap = {
        sc.FORMAT: 'format',
        sc.TYPE: 'type',
        sc.VOLTYPE: 'voltype',
        sc.DISKTYPE: 'disktype',
        sc.CAPACITY: 'capacity',
        sc.CTIME: 'ctime',
        sc.DOMAIN: 'domain',
        sc.IMAGE: 'image',
        sc.DESCRIPTION: 'description',
        sc.PUUID: 'puuid',
        sc.LEGALITY: 'legality',
        sc.GENERATION: 'generation',
    }

    def __getitem__(self, item):
        try:
            value = getattr(self, self._fieldmap[item])
        except AttributeError:
            raise KeyError(item)

        if item in (sc.CAPACITY, sc.CTIME):
            value = str(value)
        return value

    def __setitem__(self, item, value):
        setattr(self, self._fieldmap[item], value)

    def get(self, item, default=None):
        try:
            return self[item]
        except KeyError:
            return default
