#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Sofian Brabez <sbz@FreeBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#
# $FreeBSD: Tools/scripts/getpatch 317750 2013-05-09 22:02:15Z sbz $
#
# MAINTAINER=   sbz@FreeBSD.org

import argparse
import codecs
import re
import sys
if sys.version_info.major == 3:
    import urllib.request as urllib2
else:
    import urllib2

"""
FreeBSD getpatch handles Gnats and Bugzilla patch attachments
"""

class GetPatch(object):

    def __init__(self, pr, category='ports'):
        self.pr = pr
        self.category = category
        self.patchs = []
        self.url = ""
        self.patch = ""
        self.output_stdout = False
        self.default_locale = sys.getdefaultencoding()

    def fetch(self, *largs, **kwargs):
        raise NotImplementedError()

    def write(self, filename, data):
        if filename.endswith(('.patch', '.txt')):
            filename = filename[:filename.rindex('.')]+'.diff'
        f=codecs.open(filename, encoding=self.default_locale, mode='w')
        f.write(data.decode(self.default_locale))
        f.close()
        self.out("[+] %s created" % filename)

    def get(self,only_last=False, output_stdout=False):
        self.output_stdout = output_stdout
        self.fetch(self.pr, category=self.category)

        if len(self.patchs) == 0:
            self.out("[-] No patch found")
            sys.exit(1)

        if only_last:
            self.patchs = [self.patchs.pop()]

        for patch in self.patchs:
            url = patch['url']
            p = patch['name']

            data = urllib2.urlopen(url).read()

            if self.output_stdout:
                sys.stdout.write(data.decode(self.default_locale))
            else:
                self.write(p, data)

    def add_patch(self, url, name):
        self.patchs.append({'url': url, 'name': name})

    def out(self, s):
        if not self.output_stdout:
            print(s)

class GnatsGetPatch(GetPatch):

    URL_BASE = 'http://www.freebsd.org/cgi'
    URL = '%s/query-pr.cgi?pr=' % URL_BASE
    REGEX = r'<b>Download <a href="([^"]*)">([^<]*)</a>'

    def __init__(self, pr, category):
        GetPatch.__init__(self, pr, category)

    def fetch(self, *largs, **kwargs):
        category = kwargs['category']
        target = ("%s/%s" % (category, self.pr), "%s" % self.pr)[category=='']
        self.out("[+] Fetching patch for pr %s" % target)
        pattern = re.compile(self.REGEX)
        u = urllib2.urlopen(self.URL+'%s' % target)
        data = u.read()
        if data == None:
            self.out("[-] No patch found")
            sys.exit(1)

        for patchs in re.findall(pattern, str(data)):
            self.add_patch(patchs[0], patchs[1])

class BzGetPatch(GetPatch):

    URL_BASE='https://bugzilla.freebsd.org'
    URL_SHOW = '%s/show_bug.cgi?id=' % URL_BASE
    REGEX_URL = r'<a href="([^<]+)">Details</a>'
    REGEX = r'<div class="details">([^ ]+) \(text/plain\)'

    def __init__(self, pr, category):
        GetPatch.__init__(self, pr, category)

    def _extract_patchs_url(self, data):
        pattern = re.compile(self.REGEX_URL)
        return re.findall(pattern, data)

    def _extract_patchs_name(self, urls):
        names = []
        pattern = re.compile(self.REGEX)
        for url in urls:
            u = urllib2.urlopen('%s/%s' % (self.URL_BASE, url))
            data = u.read()
            names.append(re.findall(pattern, data)[0])

        return names

    def fetch(self, *largs, **kwargs):
        category = kwargs['category']
        self.out("[+] Fetching patch for pr %s/%s" % (category, self.pr))
        u = urllib2.urlopen(self.URL_SHOW+'%s' % self.pr)
        data = u.read()

        if data == None:
            self.out("[-] No patch found")
            sys.exit(1)

        urls = self._extract_patchs_url(data)
        nb_urls = len(urls)
        names = self._extract_patchs_name(urls)
        nb_names = len(names)

        urls = ['%s/%s' % (self.URL_BASE, u[:u.find('&')]) for u in urls]

        if nb_names == 0 or nb_urls == 0 or nb_names != nb_urls:
            self.out("[-] No patch found")
            sys.exit(1)

        for i in range(nb_urls):
            self.add_patch(urls[i], names[i])

def main():

    parser = argparse.ArgumentParser(
            description='Gets patch attachments from a Bug Tracking System'
    )
    parser.add_argument('pr', metavar='pr', type=str, nargs=1,
            help='Pr id number')
    parser.add_argument('--mode', type=str, choices=['gnats','bz'],
            default='gnats', help='available modes to retrieve patch')
    parser.add_argument('--last', action='store_true',
            help='only retrieve the latest iteration of a patch')
    parser.add_argument('--stdout', action='store_true',
            help='dump patch on stdout')

    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    args = parser.parse_args()

    category = ""
    pr = str(args.pr[0])

    if '/' in pr and pr is not None:
        category, pr = pr.split('/')

    Clazz = globals()['%sGetPatch' % args.mode.capitalize()]
    gp = Clazz(pr, category)
    gp.get(only_last=args.last, output_stdout=args.stdout)

if __name__ == '__main__':
    main()
