# -*- encoding: utf-8 -*-
# Software Management Providers
#
# Copyright (C) 2012-2013 Red Hat, Inc.  All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#
# Authors: Michal Minar <miminar@redhat.com>
#
"""
Module holding an abstraction for YUM repository.
"""

from datetime import datetime
import logging
import yum
import yum.Errors

# maps names of Repository properties to their corresponding property
# names in YumRepository object
PROPERTY_NAME_MAP = {
    "repoid"         : "id",
    "base_urls"      : "baseurl",
    "config_file"    : "repofile",
    "cost"           : "cost",
    "enabled"        : "enabled",
    "gpg_check"      : "gpgcheck",
    "last_edit"      : "repo_config_age",
    "metalink"       : "metalink",
    "mirror_list"    : "mirrorlist",
    "mirror_urls"    : "mirrorurls",
    "name"           : "name",
    "pkg_dir"        : "pkgdir",
    "ready"          : "ready",
    "repo_gpg_check" : "repo_gpgcheck",
    "timeout"        : "timeout"
}

def get_prop_from_yum_repo(repo, prop_name):
    """
    @param prop_name is one Repository properties
    @return value of property from object of YumRepository
    """
    if not isinstance(repo, yum.yumRepo.YumRepository):
        raise TypeError("repo must be in instance of yum.yumRepo.YumRepository")
    if prop_name in PROPERTY_NAME_MAP:
        val = getattr(repo, PROPERTY_NAME_MAP[prop_name])
        if prop_name == "last_edit":
            val = datetime.fromtimestamp(val)
        elif prop_name == "mirror_urls" and not repo.mirrorlist:
            val = None
        elif prop_name == "ready":
            val = val()
    elif prop_name in {"arch", "basearch", "releasever"}:
        val = repo.yumvar[prop_name]
    elif prop_name in {"revision", "last_update"}:
        if repo.enabled and repo.repoXML:
            md = repo.repoXML
            if prop_name == "last_update":
                val = datetime.fromtimestamp(md.timestamp)
            elif prop_name == "revision":
                val = long(md.revision)
            else:
                val = getattr(repo.repoXML, prop_name)
        else:
            val = None
    elif prop_name == "pkg_count":
        # this needs populated sack: ydb.repos.populateSack(repo.id)
        val = len(repo.sack)
    else:
        raise ValueError('Unknown repository property: "%s"' % prop_name)
    return val

class Repository(object):
    """
    Container for repository metadata. It represents yum repository.
    It's supposed to be passed from YumWorker to YumDB client and
    vice-versa.
    """
    __slots__ = (
          "objid",             # [int] id of python object on server process
          "repoid",               # [string] repository id name
                                # (name of config file)

          "arch",               # [string] architecture of packages
          "basearch",           # [string] base system architecture
          "base_urls",          # [list] base urls as strings
          #"cache",
          #"cache_dir",
          "name",            # [string] repository descriptive name
          "config_file",        # [string] file path to corresponding
                                # config file
          "cost",               # [int] cost of repository
          "enabled",            # [boolean] whether repo is enabled
          "gpg_check",          # [boolean] whether to check gpg signature
          #"metadata_expire",    # how long are metadata considered valid
          "last_edit",          # datetime of last config modification
          "last_update",        # datetime of last change of repository
                                # on server
          "metalink",           # Specifies a URL to a metalink file for the
            # repomd.xml, a list of mirrors for the entire repository are
            # generated by converting the mirrors for the repomd.xml file to a
            # baseurl.
          "mirror_list",        # url of mirrorlist, or None
          "mirror_urls",        # list of urls obtained from mirrorlist or None
          #"persist_dir",       #
          #"pkg_count",          # number of packages in directory
          "pkg_dir",            # directory with packages
          #"proxy",              # boolean saying whether this is a proxy
          "ready",              # boolean saying, whether it's ready for use
          "releasever",         # version of targeted distribution
          "repo_gpg_check",     # [boolean] whether to check gpg
                                # signarues of data
          "revision",
          "timeout",            # timeout for requests
    )

    def __init__(self, objid, repoid, arch, basearch, base_urls,
            config_file, cost, enabled, gpg_check, last_edit, last_update,
            name, pkg_dir, ready, releasever, repo_gpg_check, revision,
            timeout, metalink=None, mirror_list=None, mirror_urls=None):
        for arg in ('last_edit', 'last_update'):
            if (   locals()[arg] is not None
               and not isinstance(locals()[arg], datetime)):
                raise TypeError("%s must be an instance of datetime" % arg)
        if not isinstance(timeout, float):
            raise TypeError("timeout must be a float")
        for arg in ('cost', 'revision'):
            if (   locals()[arg] is not None
               and not isinstance(locals()[arg], (int, long))):
                raise TypeError("%s must be an integer" % arg)
        self.objid = objid
        self.repoid = repoid
        self.arch = arch
        self.basearch = basearch
        self.base_urls = list(base_urls)
        self.config_file = config_file
        self.cost = cost
        self.enabled = bool(enabled)
        self.gpg_check = bool(gpg_check)
        self.last_edit = last_edit
        self.last_update = last_update
        self.metalink = "" if not metalink else metalink
        self.mirror_list = "" if not mirror_list else mirror_list
        self.mirror_urls = [] if not mirror_urls else list(mirror_urls)
        self.name = name
        #self.pkg_count = pkg_count
        self.pkg_dir = pkg_dir
        self.ready = bool(ready)
        self.releasever = releasever
        self.repo_gpg_check = bool(repo_gpg_check)
        self.revision = revision
        self.timeout = timeout

    def __str__(self):
        return self.repoid

    def __getstate__(self):
        """
        Used for serialization with pickle.
        @return container content that will be serialized
        """
        return dict((k, getattr(self, k)) for k in self.__slots__)

    def __setstate__(self, state):
        """
        Used for deserialization with pickle.
        Restores the object from serialized form.
        @param state is an object created by __setstate__() method
        """
        for k, value in state.items():
            setattr(self, k, value)

def make_repository_from_db(repo):
    """
    Create instance of Repository from instance of yum.yumRepo.YumRepository
    @return instance of Repository
    """
    if not isinstance(repo, yum.yumRepo.YumRepository):
        raise TypeError("repo must be in instance of yum.yumRepo.YumRepository")
    metadata = {}
    for prop_name in Repository.__slots__[1:]:
        try:
            metadata[prop_name] = get_prop_from_yum_repo(repo, prop_name)
        except yum.Errors.RepoError as exc:
            # some properties can cause error (like requesting ready)
            logging.getLogger(__name__).warn(
                    'failed to get property "%s" of repo "%s": %s' % (
                        prop_name, repo.name, exc))
            if prop_name == "ready":
                metadata[prop_name] = False
            else:
                metadata[prop_name] = None

    res = Repository(id(repo), **metadata)
    return res

