from __future__ import absolute_import, division, print_function

__metaclass__ = type

import argparse
import os
import re
import sys

from packaging.version import Version, VERSION_PATTERN


class AnsibleVersionMunger(object):
    tag_offsets = dict(
        dev=0,
        a=100,
        b=200,
        rc=1000
    )

    # TODO: allow overrides here for packaging bump etc
    def __init__(self, raw_version, revision=None, codename=None):
        self._raw_version = raw_version
        self._revision = revision
        self._parsed_version = Version(raw_version)
        self._codename = codename
        self._parsed_regex_match = \
            re.match(VERSION_PATTERN, raw_version, re.VERBOSE | re.IGNORECASE)

    @property
    def deb_version(self):
        v = self._parsed_version

        match = self._parsed_regex_match

        # treat dev/post as prerelease for now; treat
        # dev/post as equivalent and disallow together
        if v.is_prerelease or match.group('dev') or match.group('post'):
            if match.group('dev') and match.group('post'):
                raise Exception(
                    "dev and post may not currently be used together",
                )
            if match.group('pre'):
                tag_value = match.group('pre')
                tag_type = match.group('pre_l')
                if match.group('dev'):
                    tag_value += ('~%s' % match.group('dev').strip('.'))
                if match.group('post'):
                    tag_value += ('~%s' % match.group('post').strip('.'))
            elif match.group('dev'):
                tag_type = "dev"
                tag_value = match.group('dev').strip('.')
            elif match.group('post'):
                tag_type = "dev"
                tag_value = match.group('post').strip('.')
            else:
                raise Exception(
                    "unknown prerelease type for "
                    "version {0}".format(self._raw_version))
        else:
            tag_type = None
            tag_value = ''

        # not a pre/post/dev release, just return base version
        if not tag_type:
            return '{base_version}'.format(base_version=self.base_version)

        # it is a pre/dev release, include the tag value with a ~
        return '{base_version}~{tag_value}'.format(
            base_version=self.base_version, tag_value=tag_value)

    @property
    def deb_release(self):
        return '1' if self._revision is None else str(self._revision)

    @property
    def rpm_release(self):
        v = self._parsed_version
        match = self._parsed_regex_match

        # treat presence of dev/post as prerelease for now;
        # treat dev/post the same and disallow together
        if v.is_prerelease or match.group('dev') or match.group('post'):
            if match.group('dev') and match.group('post'):
                raise Exception(
                    "dev and post may not currently be used together"
                )
            if match.group('pre'):
                tag_value = match.group('pre')
                tag_type = match.group('pre_l')
                tag_ver = match.group('pre_n')
                if match.group('dev'):
                    tag_value += match.group('dev')
                if match.group('post'):
                    tag_value += match.group('post')
            elif match.group('dev'):
                tag_type = "dev"
                tag_value = match.group('dev')
                tag_ver = match.group('dev_n')
            elif match.group('post'):
                tag_type = "dev"
                tag_value = match.group('post')
                tag_ver = match.group('post_n')
            else:
                raise Exception(
                    "unknown prerelease type for"
                    " version {0}".format(self._raw_version))
        else:
            tag_type = None
            tag_value = ''
            tag_ver = 0

        # not a pre/post/dev release, just append revision (default 1)
        if not tag_type:
            if self._revision is None:
                self._revision = 1
            return '{revision}'.format(revision=self._revision)

        # cleanse tag value in case it starts with .
        tag_value = tag_value.strip('.')

        # coerce to int and None == 0
        tag_ver = int(tag_ver if tag_ver else 0)

        if self._revision is None:
            tag_offset = self.tag_offsets.get(tag_type)
            if tag_offset is None:
                raise Exception(
                    'no tag offset defined for'
                    ' tag {0}'.format(tag_type))
            pkgrel = '0.{0}'.format(tag_offset + tag_ver)
        else:
            pkgrel = self._revision

        return '{pkgrel}.{tag_value}'.format(
            pkgrel=pkgrel,
            tag_value=tag_value,
        )

    @property
    def raw(self):
        return self._raw_version

    # return the x.y.z version without any other modifiers present
    @property
    def base_version(self):
        return self._parsed_version.base_version

    # return the x.y version without any other modifiers present
    @property
    def major_version(self):
        return re.match(r'^(\d+.\d+)', self._raw_version).group(1)

    @property
    def codename(self):
        return self._codename if self._codename else "UNKNOWN"


def main():
    parser = argparse.ArgumentParser(
        description='Extract/transform Ansible versions to '
                    'various packaging formats')

    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('--raw', action='store_true')
    group.add_argument('--majorversion', action='store_true')
    group.add_argument('--baseversion', action='store_true')
    group.add_argument('--debversion', action='store_true')
    group.add_argument('--debrelease', action='store_true')
    group.add_argument('--rpmrelease', action='store_true')
    group.add_argument('--codename', action='store_true')
    group.add_argument('--all', action='store_true')

    parser.add_argument('--revision', action='store', default='auto')

    args = parser.parse_args()

    mydir = os.path.dirname(__file__)
    release_loc = os.path.normpath(mydir + '/../../../lib')

    sys.path.insert(0, release_loc)

    from ansible import release

    rev = None
    if args.revision != 'auto':
        rev = args.revision

    v_raw = release.__version__
    codename = release.__codename__
    v = AnsibleVersionMunger(v_raw, revision=rev, codename=codename)

    if args.raw:
        print(v.raw)
    elif args.baseversion:
        print(v.base_version)
    elif args.majorversion:
        print(v.major_version)
    elif args.debversion:
        print(v.deb_version)
    elif args.debrelease:
        print(v.deb_release)
    elif args.rpmrelease:
        print(v.rpm_release)
    elif args.codename:
        print(v.codename)
    elif args.all:
        props = [name for (name, impl) in vars(AnsibleVersionMunger).items()
                 if isinstance(impl, property)]

        for propname in props:
            print('{0}: {1}'.format(propname, getattr(v, propname)))


if __name__ == '__main__':
    main()
