#!/usr/bin/env python3
# encoding: utf-8
"""
release.py

Created by Thomas Mangin on 2011-01-24.
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
"""

import os
import sys
import fileinput

CHANGELOG = 'CHANGELOG.rst'


class Path:
    root = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))
    changelog = os.path.join(root, CHANGELOG)
    lib_exa = os.path.join(root, 'lib/exabgp')
    version = os.path.join(root, 'lib/exabgp/version.py')
    debian = os.path.join(root, 'debian/changelog')
    egg = os.path.join(root, 'lib/exabgp.egg-info')
    build_exabgp = os.path.join(root, 'build/lib/exabgp')
    build_root = os.path.join(root, 'build')

    @classmethod
    def remove_egg(cls):
        from shutil import rmtree

        print('removing left-over egg')
        if os.path.exists(cls.egg):
            rmtree(cls.egg)
        if os.path.exists(cls.build_exabgp):
            rmtree(cls.build_root)
        return 0


class Version:
    JSON = '4.0.1'
    TEXT = '4.0.1'

    template = """\
import os

commit = "%s"
release = "%s"
json = "%s"
text = "%s"
version = os.environ.get('EXABGP_VERSION',release)

# Do not change the first line as it is parsed by scripts

if __name__ == '__main__':
	import sys
	sys.stdout.write(version)
"""

    @staticmethod
    def current():
        sys.path.append(Path.lib_exa)
        from version import version as release

        # transitional fix
        if "-" in release:
            release = release.split("-")[0]

        return release

    @staticmethod
    def changelog():
        with open(Path.changelog) as f:
            f.readline()
            for line in f:
                if line.lower().startswith('version '):
                    return line.split()[1].rstrip().rstrip(':').strip()
        return ''

    @staticmethod
    def set(tag, commit):
        with open(Path.version, 'w') as f:
            f.write(Version.template % (commit, tag, Version.JSON, Version.TEXT))
        return Version.current() == tag

    @staticmethod
    def latest(tags):
        valid = [[int(_) for _ in tag.split('.')] for tag in tags if Version.valid(tag)]
        return '.'.join(str(_) for _ in sorted(valid)[-1])

    @staticmethod
    def valid(tag):
        parts = tag.split('.')
        return len(parts) == 3 and parts[0].isdigit() and parts[1].isdigit() and parts[2].isdigit()

    @staticmethod
    def candidates(tag):
        latest = [int(_) for _ in tag.split('.')]
        return [
            '.'.join([str(_) for _ in (latest[0], latest[1], latest[2] + 1)]),
            '.'.join([str(_) for _ in (latest[0], latest[1] + 1, 0)]),
            '.'.join([str(_) for _ in (latest[0] + 1, 0, 0)]),
        ]


class Debian:
    template = """\
exabgp (%s-0) unstable; urgency=low

  * Latest ExaBGP release.

 -- Vincent Bernat <bernat@debian.org>  %s

"""

    @staticmethod
    def set(version):
        from email.utils import formatdate

        with open(Path.debian, 'w') as w:
            w.write(Debian.template % (version, formatdate()))
        print('updated debian/changelog')


class Command:
    dryrun = 'dry-run' if os.environ.get('DRY', os.environ.get('DRYRUN', os.environ.get('DRY_RUN', False))) else ''

    @staticmethod
    def run(cmd):
        print('>', cmd)
        return Git.dryrun or os.system(cmd)


class Git(Command):
    @staticmethod
    def commit(comment):
        return Git.run('git commit -a -m "%s"' % comment)

    @staticmethod
    def push(tag=False, repo=''):
        command = 'git push'
        if tag:
            command += ' --tags'
        if repo:
            command += ' %s' % repo
        return Git.run(command)

    @staticmethod
    def head_commit():
        return os.popen('git rev-parse --short HEAD').read().strip()

    @staticmethod
    def tags():
        return os.popen('git tag').read().split('-')[0].strip().split('\n')

    @staticmethod
    def tag(release):
        return Git.run('git tag -a %s -m "release %s"' % (release, release))

    @staticmethod
    def pending():
        # XXX: terrible please rewrite
        commit = None
        for line in os.popen('git status').read().split('\n'):
            if 'modified:' in line:
                if 'lib/exabgp/version.py' in line or 'debian/changelog' in line or 'README' in line:
                    if commit is not False:
                        commit = True
                else:
                    return False
            elif 'renamed:' in line:
                return False
        return commit


#
# Check that that there is no version inconsistancy before any pypi action
#


def release_github():

    print()
    print('updating Github')

    current = Version.current()
    print('current version is %s (from version.py)' % current)

    release = Version.changelog()
    print('release version is %s (from %s)' % (release, CHANGELOG))

    if not Version.valid(release):
        print('invalid new version in %s' % CHANGELOG)
        return 1

    tags = Git.tags()
    if release in tags:
        print('this version/tag was already released/used')
        return 1

    candidates = Version.candidates(Version.latest(tags))
    if release not in candidates:
        print('valid versions are:', ', '.join(candidates))
        print('this release is not one of the candidates')

    print('updating lib/exabgp/version.py')
    Version.set(release, Git.head_commit())
    print('updating debian/changelog')
    Debian.set(release)

    print('updating ', end='')
    for readme in ('README.md', 'README.rst'):
        print(readme + ' ', end='')
        with fileinput.FileInput(readme, inplace=True) as replacer:
            for line in replacer:
                print(line.replace(current, release), end='')
    print()

    print('checking if we need to commit a version.py change')
    status = Git.pending()
    if status is None:
        print('all is already set for release')
    elif status is False:
        print('more than one file is modified and need updating, aborting')
        return 1
    else:
        if Git.commit('updating version to %s' % release):
            print('could not commit version change (%s)' % release)
            return 1
        print('version was updated')

    print('tagging the new version')
    if Git.tag(release):
        print('could not tag version (%s)' % release)
        return 1

    print('pushing the new tag')
    if Git.push(tag=True, repo='origin'):
        print('could not push release tag to origin')
        return 1

    if Git.push(tag=False, repo='origin'):
        print('could not push release version to origin')
        return 1

    if Git.push(tag=True, repo='upstream'):
        print('could not push release tag to upstream')
        return 1

    if Git.push(tag=False, repo='upstream'):
        print('could not push release version to upstream')
        return 1
    return 0


def release_pypi(test):
    print()
    print('updating PyPI')

    Path.remove_egg()

    if Command.run('python setup.py sdist bdist_wheel'):
        print('could not generate egg')
        return 1

    # keyring used to save credential
    # https://pypi.org/project/twine/

    release = Version.latest(Git.tags())

    server = ''
    if test:
        server = '--repository-url https://test.pypi.org/legacy/'

    if Command.run('twine upload %s dist/exabgp-%s.tar.gz' % (server, release)):
        print('could not upload with twine')
        return 1

    print('all done.')
    return 0


def help():
    print(
        """\
release help     this help
release cleanup  delete left-over file from release
release github   tag a new version on github, and update pypi
release pypi     create egg/wheel
release install  local installation
"""
    )


def main():
    if os.environ.get("SCRUTINIZER", "") == "true":
        sys.exit(0)

    if len(sys.argv) < 2:
        help()
        sys.exit(1)

    if sys.argv[1] == 'cleanup':
        sys.exit(Path.remove_egg())

    if sys.argv[1] == 'github':
        sys.exit(release_github())

    if sys.argv[1] == 'pypi':
        sys.exit(release_pypi('--test' in sys.argv or 'test' in sys.argv))

    # "internal" commands

    if sys.argv[1] == 'current':
        sys.stdout.write("%s\n" % Version.current())
        sys.exit(0)

    if sys.argv[1] == 'release':
        sys.stdout.write("%s\n" % Version.changelog())
        sys.exit(0)

    if '--help' in sys.argv or 'help' in sys.argv:
        help()
        sys.exit(1)

    if sys.argv[1] == 'debian':
        release = Version.changelog()
        Debian.set(release)
        sys.exit(0)

    sys.exit(1)


if __name__ == '__main__':
    main()
