#!/usr/bin/env python

# ---------------------------------------------------------------------
# Print a CHANGES file in GitHub Flavored Markdown
#
# Sample call:
#    ghchanges -m 3.2 -o GENI-NSF -r geni-portal > changes.md
#
# ---------------------------------------------------------------------

import argparse
import json
import re
import sys
import time
import urllib
import urllib2
from distutils.version import StrictVersion


class GitHubService(object):

    base_url = 'https://api.github.com/repos'

    def __init__(self):
        pass

    def get_next_link(self, response):
        """Extract the link to the next set of issues.

        The GitHub API returns 30 issues at a time. The response
        headers include a link to the next 'page' of issues. Extract
        it and return it. Return None if no next link is present.

        The link header is a comma-separated list of links:

            '<https://api.github.com/...>; rel="next", <URL>; rel="last"'
        """
        result = None
        link_header = response.headers.getheader('link')
        if link_header:
            links = [l.strip() for l in link_header.split(',')]
            p = re.compile('^<(.*)>; rel="(.*)"$')
            for link in links:
                m = p.match(link)
                if m and m.group(2) == 'next':
                    result = m.group(1)
                    break
        return result


class GitHubMilestoneService(GitHubService):
    def __init__(self):
        pass

    def get(self, owner, repo, milestone, **kwargs):
        """Get all milestones matching parameters. Specify parameters
        using arbitrary keyword arguments. For instance, state=all or
        state=closed.
        """
        url = '%s/%s/%s/milestones' % (self.base_url, owner, repo)
        if kwargs:
            query_string = urllib.urlencode(kwargs)
            url = url + '?' + query_string
        while url:
            response = urllib2.urlopen(url)
            url = self.get_next_link(response)
            raw = response.read()
            milestones = json.loads(raw)
            found = None
            for m in milestones:
                title = m['title']
                if title == milestone:
                    found = Milestone(owner, repo, m)
                    return found

    def all(self, owner, repo, **kwargs):
        """Get all milestones matching parameters. Specify parameters
        using arbitrary keyword arguments. For instance, state=all or
        state=closed.
        """
        url = '%s/%s/%s/milestones' % (self.base_url, owner, repo)
        if kwargs:
            query_string = urllib.urlencode(kwargs)
            url = url + '?' + query_string
        result = []
        while url:
            response = urllib2.urlopen(url)
            url = self.get_next_link(response)
            raw = response.read()
            milestones = json.loads(raw)
            result.extend([Milestone(owner, repo, m)
                          for m in milestones])
        return result


class GitHubIssueService(GitHubService):
    def __init__(self):
        pass

    def get(self, owner, repo, milestone, **kwargs):
        """Get all issues for the given milestone. Specify parameters
        using arbitrary keyword arguments. For instance, state=all or
        state=closed.
        """
        url = '%s/%s/%s/issues' % (self.base_url, owner, repo)
        kwargs['milestone'] = milestone.number
        query_string = urllib.urlencode(kwargs)
        url = url + '?' + query_string
        result = []
        while url:
            response = urllib2.urlopen(url)
            url = self.get_next_link(response)
            raw = response.read()
            issues = json.loads(raw)
            result.extend([Issue(owner, repo, json_data)
                           for json_data in issues])
        return result


class GitHubObject(object):
    def __init__(self, owner, repo, json_data):
        self.data = json_data
        self.owner = owner
        self.repo = repo


class Issue(GitHubObject):
    @property
    def title(self):
        return self.data['title']

    @property
    def number(self):
        return self.data['number']

    @property
    def html_url(self):
        return self.data['html_url']

    @property
    def is_pull_request(self):
        return 'pull_request' in self.data

    def md_string(self):
        prefix = ''
        if self.is_pull_request:
            prefix = 'PR '
        format = '%s (%s[#%d](%s))'
        return format % (self.title, prefix, self.number, self.html_url)


class Milestone(GitHubObject):

    @property
    def number(self):
        return self.data['number']

    @property
    def title(self):
        return self.data['title']

    def issues(self):
        ghis = GitHubIssueService()
        return ghis.get(self.owner, self.repo, self, state='all')

    def __cmp__(self, other):
        if hasattr(self, 'version') and hasattr(other, 'version'):
            return self.version.__cmp__(other.version)
        else:
            return self.title.__cmp__(other.title)


def print_header(hdr, level=1):
    print '#' * level,
    print hdr


def print_changes_1(milestone, all_issues):
    # print header
    # print issues
    # print pull requests
    issues = []
    pull_requests = []
    for i in all_issues:
        if i.is_pull_request:
            pull_requests.append(i)
        else:
            issues.append(i)

    hdr = "Release %s" % (milestone.title)
    print_header(hdr, 1)
    print
    print_header("Issues Closed", 2)
    print
    if issues:
        for i in issues:
            print "* %s" % (i.md_string())
    else:
        print "* None"
    if pull_requests:
        print
        print_header("Pull Requests Merged", 2)
        print
        for p in pull_requests:
            print "* %s" % (p.md_string())


def do_stuff(milestone, owner, repo):
    # Find the milestone
    ghms = GitHubMilestoneService()
    m = ghms.get(owner, repo, milestone, state='all')

    if not m:
        print 'Milestone %s not found' % (milestone)
        sys.exit(1)

    print_changes_1(m, m.issues())
    return

    issues = m.issues()

    # Print milestone header
    print milestone
    print '-' * len(milestone)
    print

    # Print bullets for each issue and pull request
    for i in issues:
        if i.is_pull_request:
            continue
        print ' * %s' % (i.md_string())


def parse_args(args):
    parser = argparse.ArgumentParser(description='Generate changes file.')
    parser.add_argument('-m', '--milestone', metavar='MILESTONE',
                        help='a milestone name')
    parser.add_argument('-o', '--owner', metavar='OWNER',
                        help='GitHub repository owner')
    parser.add_argument('-r', '--repository', metavar='REPO',
                        help='GitHub repository')
    return parser.parse_args()


def sem_ver_filter(milestone):
    try:
        milestone.version = StrictVersion(milestone.title)
        return True
    except:
        return False


def full_changes(args):
    ghms = GitHubMilestoneService()
    milestones = ghms.all(args.owner, args.repository, state='closed')
    milestones = [m for m in milestones if sem_ver_filter(m)]
    milestones = sorted(milestones)
    milestones.reverse()
    for m in milestones:
        print_changes_1(m, m.issues())
        print ""
        time.sleep(300)


def main(argv=None):
    if argv is None:
        argv = sys.argv
    args = parse_args(argv)
    # Need to validate arguments or put good defaults in place
    # Actually, since they are required, make them so in argparse
    # by not making them "options".
    if True:
        do_stuff(args.milestone, args.owner, args.repository)
    else:
        full_changes(args)

if __name__ == "__main__":
    sys.exit(main())
