#!/usr/bin/env python
#
# This file is part of REPA: Release Engineering Process Assistant.
#
# Copyright (C) 2013 Intel Corporation
#
# REPA is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 2 as published by the Free Software Foundation.
#
# This program 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 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.

"""
REPA: Release Engineering Process Assistant.

Copyright (C) Intel Corporation 2013
Licence: GPL version 2
Author: Ed Bartosh <eduard.bartosh@intel.com>

OBS module.
Interface module to access Open Build System.

This module is a intermediate step for new APIs
to oscapi. As soon as APIs are mature enough they
should be contributed to oscapi.
"""

import sys
import re
import cgi

from base64 import b64encode
from xml.etree import cElementTree as ET
from StringIO import StringIO
from urllib2 import HTTPError

from osc import core

from gitbuildsys.oscapi import OSC, OSCError
from gitbuildsys.utils import Temp

from repa.common import RepaException, retry


OSCRC_TEMPLATE = """[general]
apiurl = %(apiurl)s
plaintext_passwd=0
use_keyring=0
http_debug = %(http_debug)s
debug = %(debug)s
gnome_keyring=0
[%(apiurl)s]
user=%(user)s
passx=%(passwdx)s
"""

class OBS(OSC):
    """Interface to OBS API."""

    def __init__(self, apiurl, apiuser, apipasswd):
        oscrc = OSCRC_TEMPLATE % {
            'http_debug': 0,
            "debug": 0,
            "apiurl": apiurl,
            "user": apiuser,
            "passwdx": b64encode(apipasswd.encode('bz2'))}

        self.apiurl = apiurl
        tmpf = Temp(prefix='.oscrc', content=oscrc)
        self.oscrcpath = tmpf.path
        OSC.__init__(self, apiurl, self.oscrcpath)


    @retry((OSCError, HTTPError))
    def get_descr(self, project):
        """Wrapper around get_description to be able to use @retry."""
        return self.get_description(project)

    def get_project_list(self, regexp=''):
        """Get list of projects matching regexp."""
        try:
            projects = core.meta_get_project_list(self.apiurl)
        except OSCError as err:
            raise RepaException("cat't get list of projects from %s: %s" %
                                (self.apiurl, err))

        for proj in projects:
            if regexp and re.match(regexp, proj):
                yield proj


    def get_projects(self, regexp='', processes=0):
        """List projects with attributes."""
        projects = list(self.get_project_list(regexp))

        if processes > 1:
            from multiprocessing.pool import ThreadPool
            pool = ThreadPool(processes=processes)
            processes = {}
            for project in projects:
                processes[project] = (
                    pool.apply_async(self.get_descr, [project]),
                    pool.apply_async(self.get_build_results, [project]))

        for project in projects:
            if processes > 1:
                yield (project, processes[project][0].get(),
                                processes[project][1].get())
            else:
                yield (project, self.get_descr(project),
                                self.get_build_results(project))


    @retry((OSCError, HTTPError))
    def get_build_results(self, prj):
        """Get project build results."""
        meta = core.show_prj_results_meta(self.apiurl, prj)
        root = ET.fromstring(''.join(meta))
        buildres = {}
        if not root.find('result'):
            return buildres
        for results in root.findall('result'):
            key = (results.get('repository'), results.get('arch'))
            buildres[key] = dict([(field, results.get(field)) \
                                      for field in ('code', 'state')])
            pkgresults = []
            for node in results:
                code = node.get('code')
                if node.get('code') != 'excluded':
                    pkgresults.append((node.get('package'), code))
            buildres[key]['packages'] = pkgresults

        return buildres


    def get_source_packages(self, prj):
        """Get list of binary packages in the project."""
        return iter(core.meta_get_packagelist(self.apiurl, prj))


    def get_binary_packages(self, prj):
        """Get list of binary packages in the project."""
        for repo in core.get_repos_of_project(self.apiurl, prj):
            yield (repo.name, repo.arch), core.get_binarylist(self.apiurl,
                                                              prj,
                                                              repo.name,
                                                              repo.arch)

    @staticmethod
    def aggregate_package(src_project, src_package, dst_project,
                          dst_package):
        """Aggregate package. Wraps core.aggregate_pack."""
        saved = sys.stdout
        sys.stdout = StringIO()
        try:
            core.aggregate_pac(src_project, src_package,
                               dst_project, dst_package)
        finally:
            sys.stdout = saved


    def create_sr(self, src_project, packages, tgt_project, message=''):
        """Create submit request for the project."""

        content = '<request><description>%s</description>' % \
                  cgi.escape(str(message))
        for package in packages:
            content += '<action type="submit">'
            content += '<source project="%s" package="%s"/>' % \
                       (src_project, package)
            content += '<target project="%s" package="%s" />' % \
                       (tgt_project, package)
            content += '</action>'
        content += '</request>\n'
        url = core.makeurl(self.apiurl, ['request'], query='cmd=create')
        reply = self.core_http(core.http_POST, url, data=content)
        return ET.parse(reply).getroot().get('id')

    def set_sr_state(self, reqid, state, message='', force=False):
        """Set SR state."""
        return core.change_request_state(self.apiurl, reqid, state,
                                         message=message, force=force)

    def get_srs(self, prj, states=None, pkg=None):
        """
        Get SRs for the project (and package).

        Args:
            prj (str): OBS project name
            states (str): comma-separated list of states, e.g. 'new,accepted'
            pkg (str): package name

        Yields:
            id, state, description of found SRs
        """
        query = 'view=collection&types=submit&project=%s' % prj
        if states:
            query += '&states=%s' % states
        if pkg:
            query += '&package=%s' % pkg
        url = core.makeurl(self.apiurl, ['request'], query)

        root = ET.parse(self.core_http(core.http_GET, url))
        for sr in root.findall('request'):
            yield sr.get('id'), sr.find('state').get('name'), \
                  sr.find('description').text

    def set_global_flag(self, flag, value, prj, pkg=None):
        """
        Set global flag in meta
        Supported flag: publish,build,useforbuild,debuginfo
        Supported values: enable,disable
        """
        supported_flags = ('publish', 'build', 'useforbuild', 'debuginfo')
        if flag not in supported_flags:
            raise RepaException("flag %s is not supported. "
                                "supported flags: %s" % \
                                    (flag, ', '.join(supported_flags)))
        supported_vals = ('enable', 'disable')
        if value not in supported_vals:
            raise RepaException("value %s is not supported. "
                                "supported values: %s" % \
                                (value, ', '.join(supported_vals)))
        meta = self.get_meta(prj, pkg)
        root = ET.fromstring(meta)
        elem = root.find(flag)
        if elem is None:
            elem = ET.SubElement(root, flag)
        elem.clear()
        ET.SubElement(elem, value)
        self.set_meta(ET.tostring(root), prj, pkg)
