#!/usr/bin/python
# -*- coding: utf-8 -*-

import argparse
import os
import sys
import logging
import subprocess
import time
import socket
import pwd
import urlparse
import shutil

# This is stolen from tar_scm OBS service
def run_cmd(cmd, cwd, raisesysexit=False, extra_env={}):
    """Execute the command cmd in the working directory cwd and check return
    value. If the command returns non-zero and raisesysexit is True raise a
    SystemExit exception otherwise return a tuple of return code and command
    output.
    """
    logging.debug("COMMAND: %s", cmd)

    # Ensure we get predictable results when parsing the output of commands
    # like 'git branch'
    env = os.environ.copy()
    env['LANG'] = 'C'

    for key in extra_env:
        env[key] = extra_env[key]

    proc = subprocess.Popen(cmd,
                            shell=False,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            cwd=cwd,
                            env=env)

    output = proc.communicate()[0]

    if proc.returncode and raisesysexit:
        logging.info("ERROR(%d): %s", proc.returncode, repr(output))
        sys.exit("Command failed(%d): %s" % (proc.returncode, repr(output)))
    else:
        logging.debug("RESULT(%d): %s", proc.returncode, repr(output))
    return (proc.returncode, output)

class GitRepo:
    def __init__(self, args, conf):
        self.args = args
        self.conf = conf
        self.clone_dir = None
        self.version = None
        self.cache = False

    def init(self):
        self.__get_clone_dir()
#        if self.cache and not os.path.exists(self.cache_dir):
#            os.makedirs(self.cache_dir)

#        if not os.path.exists(self.clone_dir):
#            os.makedirs(self.clone_dir)

    def clone(self, cache_only=False):
        if self.cache:
            if os.path.exists(self.cache_dir):
                run_cmd(['git', 'fetch', '-q', '--prune', '--all', '--force'],
                        self.cache_dir, True)
            else:
                run_cmd(['git', 'clone', '-q', '--mirror', self.args.url, self.cache_dir],
                        self.args.outdir, True)

            if not cache_only:
                run_cmd(['git', 'clone', '-q', self.cache_dir, self.clone_dir],
                        self.args.outdir, True)
        else:
            run_cmd(['git', 'clone', '-q', self.args.url, self.clone_dir], self.args.outdir, True)

    def parse_url(self, url):
        if url.startswith('http://') or url.startswith('https://'):
            return self.__parse_http_url(url)
        elif 'github.com' in url:
            return self.__parse_github_url(url)
        elif url.startswith('git://'):
            return self.__parse_git_url(url)
        else:
            sys.exit('Cannot parse URL %s' % url)

    def __get_clone_dir(self):
        url = self.args.url
        host, path = self.parse_url(url)
        logging.debug('parsed url %s to %s %s' % (url, host, path))

        cache_dir = self.conf.cache_dir
        if cache_dir:
            self.cache = True
            self.cache_dir = os.path.join(cache_dir, host, path)
            logging.debug('cache dir %s' % self.cache_dir)

        self.clone_dir = os.path.join(self.args.outdir, 'git', path)
        logging.debug('clone dir %s' % self.clone_dir)

    def checkout_branch(self):
        run_cmd(['git', 'checkout', self.args.branch], self.clone_dir, True)

    def reset_revision(self):
        run_cmd(['git', 'reset', '--hard', self.args.revision], self.clone_dir, True)

    def __get_submodule_namess(self, file):
        names = []

        ret, out = run_cmd(['git', 'config', '-f', file, '-l'], self.clone_dir, True)

        for line in out.split('\n'):
            parts = line.split('=')
            if len(parts) != 2:
                continue
            name = parts[0].split('.')[1]
            if not name in names:
                names.append(name)

        return names

    def __get_submodule_config(self, submodules_path, name, what):
        what = '.'.join(["submodule", name, what])
        ret, out = run_cmd(['git', 'config', '-f', submodules_path, '--get', what],
                           self.clone_dir, True)
        return out.split('\n')[0]

    def process_submodules(self):
        submodules_path=os.path.join(self.clone_dir, '.gitmodules')
        if not os.path.exists(submodules_path):
            logging.debug('repos %s does not have submodules' % self.clone_dir)
            return

        names = self.__get_submodule_namess(submodules_path)

        for name in names:
            url = self.__get_submodule_config(submodules_path, name, 'url')
            path = self.__get_submodule_config(submodules_path, name, 'path')
            path = os.path.join(self.clone_dir, path)

            args = Data()
            args.url = url
            args.outdir = self.args.outdir
            git=GitRepo(args, self.conf)
            git.init()
            git.clone(True)
            ref = git.clone_dir
            if git.cache:
                ref = git.cache_dir
                logging.debug('Activating cache %s for %s' % (ref, path))

            run_cmd(['git', 'submodule', '--quiet', 'update', '--init', '--force',
                     '--reference', ref, '--', path],
                    self.clone_dir, True)

            git.clone_dir = path
            git.process_submodules()

    def set_version(self):
        # Get the last version:
        ret, ver = run_cmd(["dpkg-parsechangelog", '-S', 'Version'],
                           self.clone_dir, True)
        ver=ver.rstrip()

        if ver == self.args.revision:
            # We are happy
            self.version = ver
            return

        # revision can be either a tag or a sha or HEAD
        # If it's a tag then it has to have a corresponding debian/changelog entry
        if self.revision_is_tag():
            sys.exit("revision %s does not have a corresponding debian/changelog entry"
                     % self.args.revision)

        sha = self.args.revision
        if sha == 'HEAD':
            ret, out = run_cmd(['git', 'show'], self.clone_dir, True)
            out = out.split('\n')[0].split(" ")
            if out[0] != 'commit':
                sys.exit('malformed output %s' % " ".join(out))
            sha = out[1]

        # If we are dealing a non-debian native package then we extract the debian
        # revision and append it to the end
        i=ver.rfind('-')
        debian_rev = ''
        if i > -1:
            debian_rev = ver[i:]
            ver = ver[:i]

        self.version='+'.join([ver, "%s" % int(time.time()), "git.%s" % sha[:7]])
        self.version += debian_rev

        text="Automatic entry by git_dsc service"
        self.__run_dch(['-v', self.version, text], self.clone_dir)
        self.__run_dch(['-r', text], self.clone_dir)

    def revision_is_tag(self):
        ret, out = run_cmd(['git', 'describe', '--tags', '--exact-match'], self.clone_dir, False)
        return ret == 0

    def cleanup(self):
        # We don't use clone_dir because it contains a path and we need to nuke everything
        shutil.rmtree(os.path.join(self.args.outdir, 'git'))

    def __run_dch(self, cmd, cwd):
        env = {'DEBFULLNAME': self.conf.deb_name,
               'DEBEMAIL': self.conf.deb_email}
        command = ['dch', '--noconf']
        command.extend(cmd)
        return run_cmd(command, cwd, True, env)

    def __parse_http_url(self, url):
        parts = urlparse.urlparse(url)

        host = parts[1]
        path = parts[2]

        if path.endswith('.git'):
            path = path[:-4]
        if path.startswith('/'):
            path = path[1:]

        return (host, path)

    def __parse_github_url(self, url):
        # git@github.com:owner/repos.git
        # git://github.com/owner/repo.git
        idx=url.find('github.com')
        idx += 10 # len github.com
        idx += 1 # extra separator. either : or /
        path = url[idx:]

        while path.startswith('/'):
            path=path[1:]

        if path.endswith('.git'):
            path = path[:-4]

        return ('git@github.com', path)

    def __parse_git_url(self, url):
        url = url[6:]
        idx = url.find('/')
        host = url[:idx]
        path = url[idx + 1:]
        if path.endswith('.git'):
            path = path[:-4]
        return host, path


class Config:
    def __init__(self):
        self.conf = Data()
        self.conf.cache_dir = None
        self.conf.deb_name = 'Git DSC service'
        self.conf.deb_email = '%s@%s' % (pwd.getpwuid(os.getuid()).pw_name, socket.getfqdn())

    def read(self, file):
        for line in file:
            line = line.rstrip().lstrip()
            if line.startswith('#'):
                continue
            key, val = line.split('=')
            key = key.rstrip()
            val = val.lstrip()
            if len(key) == 0:
                continue

            if len(val) == 0:
                logging.debug("Removing configuration key %s" % key)
                delattr(self.conf, key)
                continue
            setattr(self.conf, key, val)
            logging.debug("Setting configuration %s=%s" % (key, val))

class Data(object):
    pass

def parse_args():
    parser = argparse.ArgumentParser(description='Git to DSC')
    parser.add_argument('-v', '--verbose',
                        action='store_true', default=False, help='Enable verbose output')
    parser.add_argument('-u', '--url', required=True,
                        help='URL of git repo to clone')
    parser.add_argument('-o', '--outdir', required=True,
                        help='OBS service parameter. Path to output directory')
    parser.add_argument('-b', '--branch', help='name of branch to use. Defaults to master')
    parser.add_argument('-r', '--revision', help='commit SHA1 or tag to use. Defaults to HEAD')
    args = parser.parse_args()

    if not args.branch:
        args.branch = "master"

    if not args.revision:
        args.revision = "HEAD"

    if not os.path.isdir(args.outdir):
        sys.exit("%s: No such directory" % args.outdir)

    if os.getenv('DEBUG_GIT_DSC'):
        args.verbose = True

    return args

def read_conf():
    conf = Config()
    for name in ['/etc/obs/services/git_dsc', os.path.expanduser('~/.obs/git_dsc')]:
        if os.path.isfile(name):
            logging.debug("Reading configuration file %s" % name)
            conf.read(open(name))

    if not conf.conf.deb_name:
        sys.exit('deb_name is not configured')

    if not conf.conf.deb_email:
        sys.exit('deb_email is not configured')

    if conf.conf.cache_dir and not os.path.isdir(conf.conf.cache_dir):
        sys.exit('cache dir %s does not exist' % conf.conf.cache_dir)

    return conf.conf

def main():
    args = parse_args()

    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    conf = read_conf()

    git=GitRepo(args, conf)
    git.init()
    git.clone()
    git.checkout_branch()
    git.reset_revision()
    git.process_submodules()
    git.set_version()
    logging.debug("Version will be %s" % git.version)

    ret, out = run_cmd(['dpkg-source', '-b', git.clone_dir],
                       args.outdir, True)

    git.cleanup()

    # Now the build script
    f=open(os.path.join(args.outdir, "build.script"), 'w')
    f.write('dpkg-buildpackage -us -uc -b')
    f.close()

    for i in out.split("\n"):
        if i.endswith(".dsc"):
            name=os.path.join(args.outdir, i.split(" ")[-1])
            logging.debug("DSC file is %s" % name)
            f=open(name, 'a')
            f.write("OBS-DCH-RELEASE: 1")
            return

    sys.exit("Cannot find generated dsc file")

def test():
    data = [
        ('git@github.com:foo/bar.git', 'git@github.com', 'foo/bar'),
        ('git@github.com:foo/bar', 'git@github.com', 'foo/bar'),
        ('http://foo.com/foo/bar', 'foo.com', 'foo/bar'),
        ('https://foo.com/foo/bar', 'foo.com', 'foo/bar'),
        ('http://foo.com/foo/bar.git', 'foo.com', 'foo/bar'),
        ('https://foo.com/foo/bar.git', 'foo.com', 'foo/bar'),
        ('git://git.service.com/foo/bar.git', 'git.service.com', 'foo/bar'),
        ('git://git.service.com/foo/bar', 'git.service.com', 'foo/bar'),
        ]
    r=GitRepo(None, None)
    for d in data:
        out = r.parse_url(d[0])

        if out[0] != d[1]:
            sys.exit("%s parse output %s is not expected %s" % (d[0], out[0], d[1]))
        if out[1] != d[2]:
            sys.exit("%s parse output %s is not expected %s" % (d[0], out[1], d[2]))

if __name__ == '__main__':
    if '--test' in sys.argv:
        test()
    else:
        main()
