#!/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>

Group module.
Group submissions.
"""

import sys
import time
import json

from collections import defaultdict
from StringIO import StringIO

from repa.main import sub_main
from repa.obs import OBS
from repa.common import RepaException, get_project_by_name, OBS_PROJECT_PREFIX


def check_target_prj(submissions):
    """Check if target projects are the same for all submissions"""
    result = defaultdict(list)
    for submission, data in submissions.iteritems():
        result[data['meta']['obs_target_prj']].append(submission)
    if len(result) > 1:
        message = '\n'.join('%s: %s' % (project, ' '.join(subms)) \
                                for project, subms in result.iteritems())
        raise RepaException('Target projects differ:\n%s\n' % message)


def check_build_results(bresults):
    """Check if build targets are published."""
    for subm, _prj, results in bresults:
        for target, res in results.iteritems():
            if res['state'] != 'published' or res['code'] != 'published':
                if res['packages']:
                    raise RepaException("%s: target %s is not published yet" %
                                        (subm, target))
            # check if status of package builds is not worse than in
            # target project: for pkg, status in res['packages'] ...


def check_binary_pkgs(obs, submissions, force=False):
    """
    Check if submissions have common binary packages.
    Check if binary packages exist.
    """
    binaries = defaultdict(dict)
    for submission, data in submissions.iteritems():
        pkgs = list(obs.get_binary_packages(data['project']))
        # check if submission has binary packages
        for repo, bins in pkgs:
            # Check if binary packages exist
            if not force and not bins:
                raise RepaException('No binary packages found in %s %s/%s' % \
                                    (submission, repo[0], repo[1]))
            # check if submissions have common packages
            for subm, info in binaries.iteritems():
                if repo in info:
                    common = set(info[repo]).intersection(bins)
                    if common:
                        msg = '%s and %s have common packages: %s' % \
                              (subm, submission, ','.join(common))
                        raise RepaException(msg)
            binaries[submission][repo] = bins


def create_group_project(obs, submissions, meta, comment):
    """Create group project."""
    target_prj = str(meta['obs_target_prj'])
    timestamp = time.strftime('%Y%m%d.%H%M%S', time.gmtime())
    name = "submitgroup/%s/%s" % ('/'.join(meta['git_tag'].split('/')[1:-1]),
                                  timestamp)
    gmeta = {'name': name, 'obs_target_prj': target_prj,
             'submissions': submissions, 'comment': comment}
    project = '%s%s:%s-group' % (OBS_PROJECT_PREFIX, str(target_prj),
                           name.replace('/', ':'))

    saved = sys.stdout
    sys.stdout = StringIO()
    try:
        obs.create_project(project, target_prj, description=json.dumps(gmeta))
    finally:
        sys.stdout = saved
    return name, str(project)


def aggregate(obs, bresults, gproject):
    """Aggregate packages into group project."""
    aggregated = set()
    obs.set_global_flag('publish', 'disable', gproject)
    for subm, prj, results in bresults:
        for res in results.itervalues():
            for pkg, state in res['packages']:
                if state == 'succeeded' and pkg not in aggregated:
                    print 'aggregating %s/%s' % (subm, pkg)
                    obs.aggregate_package(prj, pkg, gproject, pkg)
                    aggregated.add(pkg)

    obs.set_global_flag('publish', 'enable', gproject)

    return aggregated


def group_submissions(obs, submissions, target, comment, force=False):
    """Group multiple submissions into one group."""
    # find correspondent prerelease projects
    info = {}
    for submission in submissions:
        project, meta, build_results = get_project_by_name(obs, submission,
                                                           target)
        info[submission] = {'project': project, 'meta': meta,
                            'build_results': build_results}

    # Validate submissions
    check_target_prj(info)

    bresults = [(subm, data['project'], data['build_results']) \
                     for subm, data in info.iteritems()]
    check_build_results(bresults)

    check_binary_pkgs(obs, info, force)

    # create group project
    name, gproject = create_group_project(obs, info.keys(),
                                          info.itervalues().next()['meta'],
                                          comment)
    print 'Created submit group %s\n' % name

    aggregated = aggregate(obs, bresults, gproject)

    print '\n%d submissions (%d packages) have been merged into %s' % \
          (len(info), len(aggregated), name)

class Group(object):
    """Subcommand: Manage group  submissions."""

    name = 'group'
    description = 'Group submissions'
    help = description

    @staticmethod
    def add_arguments(parser, _config):
        """Adds arguments to the parser. Called from [sub_]main."""
        parser.add_argument('submission', nargs='+',
                           help='space separated list of submissions')
        parser.add_argument('-c', '--comment', help='comment', default='')
        parser.add_argument('-f', '--force', action='store_true',
                            help='force group creation')

    @staticmethod
    def run(argv):
        """Command line entry point. Called from [sub_]main."""
        obs = OBS(argv.apiurl, argv.apiuser, argv.apipasswd)
        return group_submissions(obs, argv.submission, argv.project,
                                 argv.comment, argv.force)


if __name__ == '__main__':
    sys.exit(sub_main(sys.argv[1:], Group()))
