#!/usr/bin/python3
#
# Operate all on-boot and periodic functions in the database.
#

import errno
import optparse
import pscheduler
import select
import threading
import time


pscheduler.set_graceful_exit()

#
# Gargle the arguments
#

opt_parser = optparse.OptionParser()

# Program options

opt_parser.add_option("-d", "--dsn",
                      help="Database connection string, prefix with @ to read from file",
                      action="store", type="string", dest="dsn",
                      default="")
opt_parser.add_option("-r", "--retry",
                      help="No-rows-returned retry interval (ISO8601)",
                      action="store", type="string", dest="retry",
                      default="PT15S")
opt_parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False)
opt_parser.add_option("--debug", action="store_true", dest="debug", default=False)


(options, args) = opt_parser.parse_args()

retry = pscheduler.iso8601_as_timedelta(options.retry)
if retry is None:
    opt_parser.error('Invalid retry interval "' + options.retry + '"')
if pscheduler.timedelta_as_seconds(retry) == 0:
    opt_parser.error("Retryinterval must be calculable as seconds.")


dsn = options.dsn


#
# Maintainer for http_queue.  This has to be run separately because it
# does things that take time.
#

def http_queue_maintainer(log):
    """Maintain the http_queue table in the database"""

    log.debug("QM: Started")

    conn = pscheduler.pg_connection(dsn)

    # Start listenig

    with conn as db:
        with db.cursor() as cursor:
            # Don't catch anything here; if it dies, it dies.
            cursor.execute("LISTEN http_queue_new")
            log.debug("QM: Listening")

    # Work forever

    while True:

        # Process what needs processing

        log.debug("QM: Processing")
        with conn as db:
            with db.cursor() as cursor:
                try:
                    cursor.execute("""SELECT http_queue_process_all()""")
                except Exception as ex:
                    log.warning("Queue maintainer got exception %s", str(ex))

        # Wait for the next notification or our usual delay.

        try:
            if pscheduler.polled_select([db], [], [], 15) != ([], [], []):
                # Notified
                db.poll()
                del db.notifies[:]
                log.debug("QM: Queue change.")
        except select.error as ex:
            err_no, message = ex.args
            if err_no != errno.EINTR:
                log.exception()
                raise ex





#
# Main Program
#

def main_program():

    log = pscheduler.Log(verbose=options.verbose, debug=options.debug)

    http_queue_worker = threading.Thread(
        target=lambda: http_queue_maintainer(log))
    http_queue_worker.setDaemon(True)
    http_queue_worker.start()

    # TODO: Bulletproof the SQL queries

    db = pscheduler.pg_connection(dsn)


    log.debug("Booting")
    with db.cursor() as cursor:
        cursor.execute("SELECT cold_boot()")
    log.debug("Booted")

    with db.cursor() as cursor:
        cursor.execute("SELECT heartbeat_boot('ticker')")


    while True:

        log.debug("Tick")

        with db.cursor() as cursor:
            cursor.execute("SELECT heartbeat('ticker')")

        with db.cursor() as cursor:
            cursor.execute("SELECT ticker()")

            if cursor.rowcount == 0:
                log.debug("Got no rows back from the database, retrying in"
                          + str(options.retry) + "\n")
                time.sleep(pscheduler.timedelta_as_seconds(options.retry))
                continue
            sleep_time = cursor.fetchone()[0]

        with db.cursor() as cursor:
            cursor.execute("SELECT heartbeat('ticker', %s)", [ sleep_time ])

        seconds = pscheduler.timedelta_as_seconds(sleep_time)
        log.debug("Next check in %d seconds", seconds)
        time.sleep(seconds)

    # Not that this will ever be reached...
    db.close()


main_program()
