#!/usr/bin/python3

from argparse import ArgumentParser
import fcntl
import heapq
import logging
import logging.handlers
import os
import pwd
import pyinotify
import shutil
import signal
import sys

from contextlib import contextmanager
from urllib.parse import quote_plus

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

LOCKFILE = 'slug_watch.lock'
PROJECTS_LIST = 'projects.list'
PROJECTS_LIST_NEW = PROJECTS_LIST + '.new'
PROJECTS_LIST_HEAD = PROJECTS_LIST + '.head'
PROJECTS_LIST_GITWEB = PROJECTS_LIST + ".gitweb"
REFFILE_NEW = REFFILE + '.new'
REFREPO_WDIR = 'Refs'


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

    if os.path.isfile(PROJECTS_LIST_HEAD):
        try:
            shutil.copyfile(PROJECTS_LIST_HEAD, PROJECTS_LIST_NEW)
        except (OSError, shutil.Error):
            logger.error('Cannot write {}'.format(PROJECTS_LIST_NEW))

    with open(os.path.join(REFREPO_WDIR, REFFILE),'w') as headfile_new, open(pathname, 'r') as newfile, \
            open(PROJECTS_LIST_NEW,'a') as projects:
        committer = newfile.readline().strip()
        oldtuple = (None, None)
        refrepo = GitRepo(git_dir=REFREPO_GDIR)
        process = refrepo.showfile(REFFILE, 'master')
        headfile = process.stdout
        try:
            for (repo, ref, number, sha1) in heapq.merge(sorted(processnewfile(newfile)), convertstream(headfile)):
                if (repo, ref) == oldtuple:
                    continue
                if sha1 != EMPTYSHA1:
                    print(sha1, ref, repo, file=headfile_new)
                    if repo != oldtuple[0]:
                        print('packages/'+repo+'.git', file=projects)
                oldtuple = (repo, ref)
        except ValueError:
            logger.error("Problem with file: {}".format(pathname))
            return
        process.wait()

    os.rename(PROJECTS_LIST_NEW, PROJECTS_LIST)
    with open(PROJECTS_LIST, 'r') as projects, open(PROJECTS_LIST_GITWEB, 'w') as output:
        for line in projects:
            print(quote_plus(line, safe='/\n'), end='', file=output)

    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):
    logger.info("Starting")
    try:
        if user is not None:
            uid = pwd.getpwnam(user).pw_uid
            gid = pwd.getpwnam(user).pw_gid
            os.setgid(gid)
            os.setuid(uid)
            os.putenv('HOME', pwd.getpwnam(user).pw_dir)

        os.chdir(pwd.getpwuid(os.getuid()).pw_dir)
        for directory in (WATCHDIR, REFREPO_WDIR):
            if not os.path.isdir(directory):
                logger.info('Creating {}'.format(directory))
                os.mkdir(directory)

        refrepo = GitRepo(git_dir=REFREPO_GDIR)
        if not os.path.isdir(REFREPO_GDIR):
            refrepo.init_gitdir()
        refrepo.commandexc(['config', 'daemon.uploadarch', 'true'])


        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()
    except SystemExit:
        logger.info("Stopped")
    except:
        logger.exception('Got exception')
        raise


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('-m', '--maillogs', action='append', default=None)
parser.add_argument('-u', '--user')
parser.add_argument('-r', '--refrepodir', required=True)
parser.add_argument('-s', '--sender')
parser.add_argument('-w', '--watchdir', required=True)
options = parser.parse_args()

REFREPO_GDIR = os.path.join(options.refrepodir, REFREPO+'.git')
WATCHDIR = options.watchdir

logger = logging.getLogger('slug_watch')
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s')
if options.maillogs is not None:
    if options.sender is None:
        parser.error("Sender of logs is required with -m/--maillogs option")
    handler_email = logging.handlers.SMTPHandler("localhost", options.sender, options.maillogs, "slug_watch status")
    handler_email.setFormatter(formatter)
    logger.addHandler(handler_email)
if options.daemon  is not None:
    handler = logging.handlers.SysLogHandler(address="/dev/log", facility="daemon")
else:
    handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(name)s: %(levelname)s %(message)s'))
logger.addHandler(handler)

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)
