#!/usr/bin/python3

from argparse import ArgumentParser
import fcntl
import heapq
import os
import pwd
import pyinotify
import signal

from contextlib import contextmanager

import Daemon.daemon
from git_slug.gitconst import EMPTYSHA1, REFREPO, REFFILE
from git_slug.serverconst import WATCHDIR
from git_slug.gitrepo import GitRepo

LOCKFILE = 'slug_watch.lock'
REFFILE_NEW = REFFILE + '.new'
REFREPO_WDIR = 'Refs'
REFREPO_GDIR = os.path.join('repositories', REFREPO+'.git')


def sigtermhandler(no, stack):
    raise SystemExit

@contextmanager
def lock(path=LOCKFILE):
    signal.signal(signal.SIGTERM, sigtermhandler)
    f = open(path, 'a')
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        raise SystemExit('Already running: file {} locked'.format(path))
    else:
        try:
            yield
        finally:
            f.close()
            os.remove(path)


def convertstream(stream):
    for line in stream:
        (sha1, ref, repo) = line.decode('utf-8').split()
        yield (repo, ref, 1, sha1)

def processnewfile(stream):
    repo = stream.readline().strip()
    for line in stream:
        (sha1old, sha1, ref) = line.split()
        if ref.startswith('refs/heads/'):
            yield (repo, ref, 0, sha1)


def process_file(pathname):
    if not os.path.isfile(pathname):
        print('{} is not an ordinary file'.format(pathname))
        return
    with open(os.path.join(REFREPO_WDIR, REFFILE),'w') as headfile_new, open(pathname, 'r') as newfile:
        committer = newfile.readline().strip()
        oldtuple = None
        refrepo = GitRepo(git_dir=REFREPO_GDIR)
        process = refrepo.showfile(REFFILE, 'master')
        headfile = process.stdout
        for (repo, ref, number, sha1) in heapq.merge(sorted(processnewfile(newfile)), convertstream(headfile)):
            if (repo, ref) == oldtuple:
                continue
            oldtuple = (repo, ref)
            if sha1 != EMPTYSHA1:
                print(sha1, ref, repo, file=headfile_new)
        process.wait()

    headrepo = GitRepo(REFREPO_WDIR, REFREPO_GDIR)
    headrepo.commitfile(REFFILE, 'Changes by {}'.format(committer))
    os.remove(pathname)

class EventHandler(pyinotify.ProcessEvent):
    def process_IN_CLOSE_WRITE(self, event):
        process_file(event.pathname)

def runwatch(user=None):
    if user is not None:
        uid = pwd.getpwnam(user).pw_uid
        gid = pwd.getpwnam(user).pw_gid
        os.setgid(gid)
        os.setuid(uid)

    os.chdir(pwd.getpwuid(os.getuid()).pw_dir)
    for directory in (WATCHDIR, REFREPO_WDIR):
        if not os.path.isdir(directory):
            print('Creating {}'.format(directory))
            os.mkdir(directory)
    if not os.path.isdir(REFREPO_GDIR):
        GitRepo(git_dir=REFREPO_GDIR).init_gitdir()

    with lock(LOCKFILE):
        wm = pyinotify.WatchManager()  # Watch Manager
        mask = pyinotify.IN_CLOSE_WRITE # watched events
        notifier = pyinotify.Notifier(wm, EventHandler())
        wm.add_watch(WATCHDIR, mask, rec=False)
        for filename in sorted(os.listdir(WATCHDIR), key=lambda f: os.stat(os.path.join(WATCHDIR, f)).st_mtime):
            process_file(os.path.join(WATCHDIR, filename))
        notifier.loop()


parser = ArgumentParser(description='daemon to register changes in PLD repositories')
parser.add_argument('-d', '--daemon', nargs='?', choices=['start', 'stop'], default=None, const='start')
parser.add_argument('-u', '--user')
options = parser.parse_args()

class SlugWatch(Daemon.daemon.daemon):
    def __init__(self, user, pidfile):
        super().__init__(pidfile)
        self.user = user
    def run(self):
        runwatch(self.user)

if options.daemon is not None:
    daemon = SlugWatch(options.user, "/var/run/slug_watch.pid")
    getattr(daemon, options.daemon)()
else:
    runwatch(options.user)
