#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2014 Gauvain Pocentek <gauvain@pocentek.net>
#
# This program 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 3 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function, division, absolute_import
import argparse
import os
import sys
import re
import gitlab
from inspect import getmro
try:
    from ConfigParser import ConfigParser
except:
    from configparser import ConfigParser

camel_re = re.compile('(.)([A-Z])')
LIST = 'list'
GET = 'get'
CREATE = 'create'
UPDATE = 'update'
DELETE = 'delete'
PROTECT = 'protect'
UNPROTECT = 'unprotect'
SEARCH = 'search'
OWNED = 'owned'
ALL = 'all'
ACTIONS = [LIST, GET, CREATE, UPDATE, DELETE]
EXTRA_ACTION = [PROTECT, UNPROTECT, SEARCH, OWNED, ALL]

extra_actions = {
    gitlab.ProjectBranch: {PROTECT: {'requiredAttrs': ['id', 'project-id']},
                           UNPROTECT: {'requiredAttrs': ['id', 'project-id']}
    },
    gitlab.Project: {SEARCH: {'requiredAttrs': ['query']},
                     OWNED: {'requiredAttrs': []},
                     ALL: {'requiredAttrs': []}
    },
}


def die(msg):
    sys.stderr.write(msg + "\n")
    sys.exit(1)


def whatToCls(what):
    return "".join([s.capitalize() for s in what.split("-")])


def clsToWhat(cls):
    return camel_re.sub(r'\1-\2', cls.__name__).lower()


def populate_sub_parser_by_class(cls, sub_parser):
    sub_parser_class = sub_parser.add_subparsers(
        dest='action',
        title="positional argument",
        description='action with %s' % cls.__name__,
        help='action to do'
    )
    for action_name in ACTIONS:
        attr = 'can' + action_name.capitalize()
        try:
            y = cls.__dict__[attr]
        except:
            y = gitlab.GitlabObject.__dict__[attr]
        if not y:
            continue
        sub_parser_action = sub_parser_class.add_parser(action_name)
        [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in cls.requiredUrlAttrs]
        if action_name == LIST:
            [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in cls.requiredListAttrs]
            sub_parser_action.add_argument("--page", required=False)
            sub_parser_action.add_argument("--per-page", required=False)
        elif action_name in [GET, DELETE]:
            if cls not in [gitlab.CurrentUser]:
                sub_parser_action.add_argument("--id", required=True)
                [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in cls.requiredGetAttrs]
        elif action_name == CREATE:
            [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in
             cls.requiredCreateAttrs]
            [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=False) for x in
             cls.optionalCreateAttrs]
        elif action_name == UPDATE:
            [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in
             cls.requiredCreateAttrs]
            [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=False) for x in
             cls.optionalCreateAttrs]

    if cls in extra_actions:
        for action_name in sorted(extra_actions[cls]):
            sub_parser_action = sub_parser_class.add_parser(action_name)
            d = extra_actions[cls][action_name]
            [sub_parser_action.add_argument("--%s" % arg, required=True) for arg in d['requiredAttrs']]


def do_auth():
    try:
        gl = gitlab.Gitlab(gitlab_url, private_token=gitlab_token,
                           ssl_verify=ssl_verify, timeout=timeout)
        gl.auth()
        return gl
    except:
        die("Could not connect to GitLab (%s)" % gitlab_url)


def get_id():
    try:
        id = d.pop('id')
    except:
        die("Missing --id argument")

    return id


def do_create(cls, d):
    if not cls.canCreate:
        die("%s objects can't be created" % what)

    try:
        o = cls(gl, d)
        o.save()
    except Exception as e:
        die("Impossible to create object (%s)" % str(e))

    return o


def do_list(cls, d):
    if not cls.canList:
        die("%s objects can't be listed" % what)

    try:
        l = cls.list(gl, **d)
    except Exception as e:
        die("Impossible to list objects (%s)" % str(e))

    return l


def do_get(cls, d):
    if not cls.canGet:
        die("%s objects can't be retrieved" % what)

    id = None
    if cls not in [gitlab.CurrentUser]:
        id = get_id()

    try:
        o = cls(gl, id, **d)
    except Exception as e:
        die("Impossible to get object (%s)" % str(e))

    return o


def do_delete(cls, d):
    if not cls.canDelete:
        die("%s objects can't be deleted" % what)

    o = do_get(cls, d)
    try:
        o.delete()
    except Exception as e:
        die("Impossible to destroy object (%s)" % str(e))


def do_update(cls, d):
    if not cls.canUpdate:
        die("%s objects can't be updated" % what)

    o = do_get(cls, d)
    try:
        for k, v in d.items():
            o.__dict__[k] = v
        o.save()
    except Exception as e:
        die("Impossible to update object (%s)" % str(e))

    return o


def do_project_search(d):
    try:
        return gl.search_projects(d['query'])
    except Exception as e:
        die("Impossible to search projects (%s)" % str(e))


def do_project_all():
    try:
        return gl.all_projects()
    except Exception as e:
        die("Impossible to list all projects (%s)" % str(e))


def do_project_owned():
    try:
        return gl.owned_projects()
    except Exception as e:
        die("Impossible to list owned projects (%s)" % str(e))


if __name__ == "__main__":
    ssl_verify = True
    timeout = 60
    parser = argparse.ArgumentParser(description='Useful GitLab Command Line Interface')
    parser.add_argument("-v", "--verbosity", "--fancy", help="increase output verbosity", action="store_true")
    parser.add_argument("--gitlab", metavar='gitlab',
                        help="Specifies which python-gitlab.cfg configuration section should be used. "
                             "If not defined, the default selection will be used.", required=False)
    subparsers = parser.add_subparsers(
        dest='what',
        title="positional argument",
        description='GitLab object',
        help='GitLab object'
    )
    #populate argparse for all Gitlab Object
    for cls in gitlab.__dict__.values():
        try:
            if gitlab.GitlabObject in getmro(cls):
                sub_parser = subparsers.add_parser(clsToWhat(cls))
                populate_sub_parser_by_class(cls, sub_parser)
        except:
            pass

    arg = parser.parse_args()
    d = arg.__dict__
    # read the config
    config = ConfigParser()
    config.read(['/etc/python-gitlab.cfg',
                 os.path.expanduser('~/.python-gitlab.cfg')])
    gitlab_id = arg.gitlab
    #conflicts with "gitlab" attribute with GitlabObject class
    d.pop("gitlab")
    verbose = arg.verbosity
    action = arg.action
    what = arg.what

    if gitlab_id is None:
        try:
            gitlab_id = config.get('global', 'default')
        except:
            die("Impossible to get the gitlab id (not specified in config file)")

    try:
        gitlab_url = config.get(gitlab_id, 'url')
        gitlab_token = config.get(gitlab_id, 'private_token')
    except:
        die("Impossible to get gitlab informations from configuration (%s)" %
            gitlab_id)

    try:
        ssl_verify = config.getboolean('global', 'ssl_verify')
    except:
        pass
    try:
        ssl_verify = config.getboolean(gitlab_id, 'ssl_verify')
    except:
        pass

    try:
        timeout = config.getint('global', 'timeout')
    except:
        pass
    try:
        timeout = config.getint(gitlab_id, 'timeout')
    except:
        pass

    cls = None
    try:
        cls = gitlab.__dict__[whatToCls(what)]
    except:
        die("Unknown object: %s" % what)

    gl = do_auth()

    if action == CREATE or action == GET:
        o = globals()['do_%s' % action.lower()](cls, d)
        o.display(verbose)

    elif action == LIST:
        for o in do_list(cls, d):
            o.display(verbose)
            print("")

    elif action == DELETE or action == UPDATE:
        o = globals()['do_%s' % action.lower()](cls, d)

    elif action == PROTECT or action == UNPROTECT:
        if cls != gitlab.ProjectBranch:
            die("%s objects can't be protected" % what)

        o = do_get(cls, d)
        getattr(o, action)()

    elif action == SEARCH:
        if cls != gitlab.Project:
            die("%s objects don't support this request" % what)

        for o in do_project_search(d):
            o.display(verbose)

    elif action == OWNED:
        if cls != gitlab.Project:
            die("%s objects don't support this request" % what)

        for o in do_project_owned():
            o.display(verbose)

    elif action == ALL:
        if cls != gitlab.Project:
            die("%s objects don't support this request" % what)

        for o in do_project_all():
            o.display(verbose)

    else:
        die("Unknown action: %s. Use \"gitlab -h %s\" to get details." %
            (action, what))

    sys.exit(0)