#!/usr/bin/python3.6 -s
# GPL. (C) 2020 Paolo Patruno.

# This program is free software; you can redistribute it and/or modify 
# it under the terms of the GNU General Public License as published by 
# the Free Software Foundation; either version 2 of the License, or 
# (at your option) any later version. 
# 
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
# GNU General Public License for more details. 
# 
# You should have received a copy of the GNU General Public License 
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
# 

import os,signal,logging,traceback,time,queue
os.environ['DJANGO_SETTINGS_MODULE'] = 'rmap.settings'
import django
django.setup()

from rmap import daemon
import pika,dballe,io
import rmap.settings
from rmap.rmap_core import amqpConsumerProducer
from rmap.rmap_core import Message


amqp2amqp_identvalidationd = daemon.Daemon(
        stdin="/dev/null",
        stdout=rmap.settings.logfileamqp2amqp_identvalidationd,
        stderr=rmap.settings.errfileamqp2amqp_identvalidationd,
        pidfile=rmap.settings.lockfileamqp2amqp_identvalidationd,
        user=rmap.settings.useramqp2amqp_identvalidationd,
        group=rmap.settings.groupamqp2amqp_identvalidationd
)

# catch signal to terminate the process
class GracefulKiller:
    kill_now = False
    def __init__(self):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

    def exit_gracefully(self,signum, frame):
        self.kill_now = True

    def keyboard_interrupt(self):
        self.kill_now = True

    def terminate(self):
        self.kill_now = True


def identvalidation(amqp,basic_deliver, properties, body):

    logging.debug(" [x] Received message")

    if properties.user_id is None:
        logging.info("Ignore anonymous message")
        logging.info(" [x] Done")

        if (amqp.receive_queue is not None):
            amqp.receive_queue.put_nowait(Message(True,basic_deliver.delivery_tag))
        else:
            amqp.acknowledge_message(basic_deliver.delivery_tag)
        return
  
    #At this point we can check if we trust this authenticated user... 
    ident=properties.user_id
    logging.debug("Received from user: %r", ident) 

    bodyfile = io.BytesIO(body) 
    importer = dballe.Importer("BUFR")
    exporter = dballe.Exporter("BUFR")
    outbody=b""
    with importer.from_file(bodyfile) as f:
        for msgs in f:
            for msg in msgs:
                if msg.ident == ident:
                    outbody+=exporter.to_binary(msg)
                else:
                    logging.info("message rejected by ident validation: %s : %s ",msg.ident,ident)

    if (outbody == b""):
        logging.debug("skip empty output message")
        if (amqp.receive_queue is not None):
            amqp.receive_queue.put_nowait(Message(True,basic_deliver.delivery_tag))
        else:
            amqp.acknowledge_message(basic_deliver.delivery_tag)
    else:
        logging.debug("publish message")
        # Send a message
        if (amqp.send_queue is not None):
            amqp.send_queue.put_nowait(Message(outbody,basic_deliver.delivery_tag))
        else:                    
            if ( amqp.publish(outbody,[basic_deliver.delivery_tag])):
                logging.debug('message sended')
                logging.info(" [x] Done")
            else:
                logging.info(" [ ] NOT Done")
                logging.error('message not sended')
                amqp.reject_message(basic_deliver.delivery_tag)


def main(self):

    #arm the signal handler
    killer = GracefulKiller()

    # configure the logger
    formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S")
    handler = logging.handlers.RotatingFileHandler(self.options.stdout, maxBytes=5000000, backupCount=10)
    handler.setFormatter(formatter)
    
    # Add the log message handler to the root logger
    logging.getLogger().addHandler(handler)
    logging.getLogger().setLevel(logging.INFO)

    logging.info('Starting up mqtt2amqpd')

    receive_queue = queue.Queue(maxsize=100)
    send_queue = queue.Queue(maxsize=100)

    user=rmap.settings.amqpuser
    password=rmap.settings.amqppassword
    host=rmap.settings.amqphost
    inqueue="..bufr.report_fixed"
    outexchange="rmap.dae.bufr.report_fixed_validated"

    
    amqp=amqpConsumerProducer(host,inqueue,outexchange,user,password,
                              receive_queue=receive_queue,send_queue=send_queue,
                              pipefunction=identvalidation)
    amqp.start()
    
    # infinite loop
    while True:
        try:
            time.sleep(3) # do nothing
        except Exception as exception:
            logging.error('Exception occured: ' + str(exception))
            logging.error(traceback.format_exc())
            killer.terminate()
            
        # terminate on keyboard interrupt
        except KeyboardInterrupt:
            sys.stdout.write("keyboard interrupt\n")
            logging.info("keyboard interrupt\n")
            killer.keyboard_interrupt()

        if (not amqp.is_alive()):
            logging.info("amqp aborted\n")
            killer.terminate()            
            
        # check if we have to terminate together with other exceptions
        if killer.kill_now:
            logging.info("killed by signal\n")

            amqp.terminate()
            amqp.join()
            logging.debug("AMQP terminated")

            return False
    

if __name__ == '__main__':

    import sys, os
    amqp2amqp_identvalidationd.cwd=os.getcwd()

    if amqp2amqp_identvalidationd.service():

        sys.stdout.write("Daemon started with pid %d\n" % os.getpid())
        sys.stdout.write("Daemon stdout output\n")
        sys.stderr.write("Daemon stderr output\n")

        main(amqp2amqp_identvalidationd)  # (this code was run as script)

        for proc in amqp2amqp_identvalidationd.procs:
            proc.wait()

        sys.exit(0)
