#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright (C) 2015 Intel, Inc.
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; version 2
#    of the License.
#
#    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
"""
This code is called by gerrit-plugin to sync obs configuration from
scm/obs git tree to OBS server.
"""
import os
import sys
import re

from common.buildservice import BuildService


class Error(Exception):
    """Local exception."""
    pass

def diff_obs(directory, projects, build):
    """
    Collect the difference between local copy of scm/meta/obs and OBS projects
    (links, aggregates, meta, configs, etc).

    Returns: {"created": <list of new (path, content)>,
              "updated": <list of updated (path, content)>,
              "deleted": <list of deleted paths>}
    """
    empty_meta = '<package name="%s" project="%s"><title></title>'\
                 '<description></description></package>'
    paths = []
    result = {"created": [], "updated":[], "deleted": []}
    for project in projects:
        # Add project config and meta
        for fname in ('_config', '_meta'):
            paths.append("%s/%s" % (project, fname))
        # Collect _meta, _aggregate and _link paths from OBS
        for package in build.get_package_list(project):
            flist = build.get_src_file_list(project, package)
            meta = build.get_src_file_content(project, package, '_meta')
            empty = empty_meta % (package, project)
            if re.sub('[\r\n\t ]+', '', meta) != re.sub('[ ]+', '', empty):
                paths.append("%s/%s/_meta" % (project, package))
            for fname in ("_aggregate", "_link"):
                if fname in flist:
                    paths.append("%s/%s/%s" % (project, package, fname))
        # Collect new and updated paths
        for dirname, _dirnames, filenames in \
                                os.walk(os.path.join(directory, project)):
            for fname in filenames:
                dir_slash = os.path.normpath(directory) + os.path.sep
                path = os.path.join(dirname.split(dir_slash)[-1], fname)
                with open(os.path.join(dirname, fname)) as metaf:
                    content = metaf.read()
                    if path in paths:
                        if build.get_source_path(path) != content:
                            result["updated"].append((path, content))
                        paths.remove(path)
                    else:
                        result["created"].append((path, content))

    # Collect deleted paths
    for path in paths:
        splitted = path.split('/')
        if len(splitted) == 3:
            project, package, fname = splitted
            content = empty_meta % (package, project) \
                           if fname == '_meta' else ''
        else:
            project, fname = splitted
            content = ''
        result["deleted"].append((path, content))

    return result

def main():
    """Entry point. Sanity checks and call of sync_obs."""
    # check mandatory environment variables
    for var in ("ACTIVE_PROJECTS", "WORKSPACE", "OBS_API_URL",
                "OBS_API_USERNAME", "OBS_API_PASSWD"):
        if not os.getenv(var):
            raise Error("%s env variable is not set" % var)

    active_projects = os.getenv('ACTIVE_PROJECTS').split(',')
    prjdir = os.getenv('WORKSPACE')

    # check if all project directories exist in the clone
    for project in active_projects:
        pdir = os.path.join(prjdir, project)
        if not os.path.exists(pdir):
            raise Error("Project directory %s doesn't exist in the "
                        "cloned tree" % project)

    build = BuildService(os.getenv("OBS_API_URL"),
                         os.getenv("OBS_API_USERNAME"),
                         os.getenv("OBS_API_PASSWD"))
    # get the diff
    diff = diff_obs(prjdir, active_projects, build)
    # rint it
    titles = [('created', 'New paths in the git tree:'),
              ('updated', 'Updated paths in the git tree:'),
              ('deleted', 'Missing paths in the git tree:')]
    for key, message in titles:
        print message
        for path, content in diff[key]:
            print '    ', path
        print

    # apply it to OBS
    if os.getenv('SYNCOBS_DRYRUN') == '1':
        print 'Dry-run mode, not applying to OBS'
    else:
        print 'Applying the difference to OBS:'
        for action in diff:
            for path, content in diff[action]:
                build.put_source_path(path, content)
                print '    ', action, path

if __name__ == '__main__':
    try:
        sys.exit(main())
    except Error as error:
        print >> sys.stderr, "Error: %s" % str(error)
        sys.exit(1)
