#!/usr/bin/env python
# encoding: utf-8
"""
Empfängt offene Kommissionierbelege aus einer message queue, legt einen Lieferscheinsatz
(==Komissionierbeleg) in Django an und schreibt die Daten in den kernelE.

Created by Lars Ronge, Maximillian Dornseif on 2007-12-07, 2007-12-19.
Rewritten by Maximillian Dornseif 2008-12 and 2010-01
Copyright (c) 2007, 2008, 2010 HUDORA GmbH.
"""

# From http://superjared.com/entry/django-and-crontab-best-friends/
from django.core.management import setup_environ
import settings
setup_environ(settings)

from django.db import transaction
from mypl.models import Lieferschein, LieferscheinPosition
import cs.masterdata.article
import cs.messaging as messaging
import cs.printing
import cs.zwitscher
import huTools.NetStringIO as NetStringIO
import husoftm.lieferscheine
import mypl.kernel
import mypl.models
import optparse
import signal
import simplejson as json
import sys
import time


process_messages = True
message_count = 0


def display(msg):
    """Display messages based on command line options."""
    if options.debug or (not options.quiet):
        print (msg)
        sys.stdout.flush()


def write_journal(doc):
    """Write journal entrys of incoming messages."""
    logdata = json.dumps(doc, sort_keys=True, separators=(',', ':'))
    if options.debug:
        print logdata
    nstring = NetStringIO.NetStringIO(open(options.log, 'a'))
    nstring.write(logdata)
    nstring.file.close()


@transaction.commit_on_success # this SHOULd ensure that we never hafe a half-saved record
def softm2django(kommiauftragsnr, doc):
    """Schreibt einen Kommiauftrag in die (bald abgeschaffte) Django Datenbank."""

    if Lieferschein.objects.filter(kommissionierbelegnr=kommiauftragsnr).count() > 0:
        msg = ("Kommibeleg %s existiert bereits in Django") % (kommiauftragsnr)
        display(msg)
        cs.zwitscher.zwitscher(msg, username='mypl')
        return True # aus der Queue entfernen

    # generate Lieferschein object in Django
    kommibeleg = Lieferschein()

    kommibeleg.kommissionierbelegnr = kommiauftragsnr
    kommibeleg.liefertermin = doc.get('anlieferdatum', '') # needs conversion?
    kommibeleg.liefer_date = doc.get('anliefertermin', '') # needs conversion?
    kommibeleg.versandtermin = doc.get('anliefertermin', '') # needs conversion?
    kommibeleg.versanddatum = doc.get('anliefertermin', '') # needs conversion?
    kommibeleg.kundennr = doc.get('kundennr', '')
    kommibeleg.auftragsnr = doc.get('auftragsnr', '')
    kommibeleg.auftragsnr_kunde = doc.get('info_kunde', '')
    kommibeleg.fixtermin = doc.get('fixtermin', False)
    kommibeleg.warenempfaenger = ''
    kommibeleg.lager = '100'
    kommibeleg.satznummer = '0'
    kommibeleg.name1 = doc.get('name1', '')
    kommibeleg.name2 = doc.get('name2', '')
    kommibeleg.name3 = doc.get('name3', '')
    kommibeleg.strasse = doc.get('strasse', '')
    kommibeleg.land = doc.get('land', '')
    kommibeleg.plz = doc.get('plz', '')
    kommibeleg.ort = doc.get('ort', '')
    kommibeleg.tel = doc.get('tel', '')
    kommibeleg.fax = doc.get('fax', '')
    kommibeleg.mail = doc.get('mail', '')
    kommibeleg.mobil = doc.get('mobil', '')
    kommibeleg.versandart = 'nor'
    kommibeleg.auftragsart = ''
    kommibeleg.total_weight = 0
    kommibeleg.total_volume = 0
    print vars(kommibeleg)
    kommibeleg.save()

    for position in doc.get('positionen', []):
        position_object = LieferscheinPosition()
        position_object.menge_komissionierbeleg = position['menge']
        position_object.menge = position['menge']
        position_object.menge_offen = position['menge']
        position_object.artnr = position['artnr']
        position_object.auftrags_position = position['posnr']
        position_object.setartikel = position.get('setartikel', False)
        position_object.kommissionierbeleg_position = position.get('kommissionierbeleg_position', '')
        position_object.kommissionierbelegnr = kommiauftragsnr
        position_object.satznummer = ''
        if not options.dry_run:
            kommibeleg.lieferscheinposition_set.add(position_object)

    # kommibeleg.status = 'new'
    kommibeleg.status = 'ready_for_kernel'
    if not options.dry_run:
        kommibeleg.save()
    return kommibeleg


def _export_kommi_to_kernel():
    """Pushes unprocessed Lieferschein objects into kernel."""
    for kommibeleg in Lieferschein.objects.filter(status='ready_for_kernel'):
        if options.debug:
            print "transfering to kernel", kommibeleg
        kommiauftragsnr = kommibeleg.kommissionierbelegnr
        softmschein = husoftm.lieferscheine.Kommibeleg(kommiauftragsnr)

        mypldict = {'auftragsnummer': kommibeleg.auftragsnr,
                    # TODO: remove liefertermin from Kernel
                    'liefertermin': kommibeleg.liefer_date.strftime("%Y-%m-%d"),
                    # FIXME diese attribute scheint es nicht zu geben im SoftM Lieferschein/Kommibeleg
                    #'anliefertermin_min': softmschein.anliefertermin_min.strftime("%Y-%m-%d"),
                    #'anliefertermin_max': softmschein.anliefertermin_max.strftime("%Y-%m-%d"),
                    'versandtermin': kommibeleg.get_versandtermin().strftime("%Y-%m-%d"),
                    'kundenname': softmschein.name1,
                    'land': softmschein.land,
                    'plz': softmschein.plz,
                    'art': softmschein.art,
                    'fixtermin': softmschein.fixtermin,
                    #'anbruch': kommibeleg.anbruch,
                    #'kep': kommibeleg.kep,
                    #'packstuecke': kommibeleg.packstuecke,
                    #'paletten': kommibeleg.paletten,
                    #'picks': kommibeleg.picks,
                    #'versandpaletten': kommibeleg.versandpaletten,
                    #'max_packstueck_gewicht': kommibeleg.max_packstueck_gewicht,
                   }
        orderlines = _get_orderlines_for_kernel(kommibeleg)
        if options.debug:
            print mypldict
            print orderlines
        if not options.dry_run:
            mypl.kernel.Kerneladapter().insert_pipeline(
                kommiauftragsnr,
                orderlines,
                5, # We always start with Priority 5
                kommibeleg.warenempfaenger,
                kommibeleg.total_weight,
                kommibeleg.total_volume,
                mypldict)
            kommibeleg.status = 'in_mypl'
            kommibeleg.save()
        return kommibeleg


def _get_orderlines_for_kernel(kommibeleg):
    """Gets the orderline-tuple to submit it via insert_pipeline"""
    orderlines = []
    for position in kommibeleg.lieferscheinposition_set.all():
        pos_params = {"auftragsposition": position.kommissionierbeleg_position,
                      "gewicht": 0}
        orderlines.append((position.menge_komissionierbeleg, position.artnr, pos_params))
    return orderlines


def handle_message(message):
    """Callback function used by channel.basic_consume for adding 'Warenzugang' to myPL."""
    global message_count

    if options.maxnum and (message_count > options.maxnum):
        cancel_processing()
        return

    body = json.loads(message.body)
    #if options.debug:
    #    print body
    djangokommischein = softm2django(body['kommiauftragsnr'], body)
    # umlagerung_to_unit() macht die wirkliche arbeit und überträgt in den kernel.
    if djangokommischein:
        channel.basic_ack(message.delivery_tag)
        message_count += 1

    # move stuff from Django to myPL
    _export_kommi_to_kernel()


# Might be used as a signal hander
def cancel_processing():
    """ Stop consuming messages from RabbitMQ """
    global process_messages

    # Do this so we exit our main loop
    process_messages = False
    display("Exiting")
    # Tell the channel you dont want to consume anymore
    for consumer_tag in consumer_tags:
        channel.basic_cancel(consumer_tag)


def main():
    """Setup message consuming."""
    global channel, consumer_tags, options
    consumer_tags = []

    parser = optparse.OptionParser()
    parser.version = "%%prog"
    parser.description = ('Uebernimmt Warenzugangsmeldungen aus einer Warteschlange und lagert diese im'
                         + 'myPL ein (EINLAG). Erstellt ausserdem eine Palettenbewegung an einen geeigneten'
                         + 'Platz und  erzeugt eine Palettenfahne.')
    parser.set_usage('usage: %prog [options]')
    parser.add_option('--lager', type="int", help='Lager, das bearbeitet werden soll')
    parser.add_option('--in', dest="in_queues", action='append',
                      help='Name der Queue, aus der gelesen werden soll. Wird diese Parameter mehrfach '
                      +'angegeben, wird aus mehreren Queues gelesen. Beispiel --in=erp.ic-wms.warenzugang.')
    parser.add_option('--log', default='/var/log/wms2mypl_kommiauftrag.log',
                      help='Wo sollen die Nachrichten protokolliert werden? [%default]')
    parser.add_option('--quiet', action='store_true', help='Uebliche Ausgaben unterdruecken')
    parser.add_option('--debug', action='store_true', help='Debugging Informationen ausgeben')
    parser.add_option('--dry-run', action='store_true', help='Keine Nachrichten absenden und loeschen')
    parser.add_option('--maxnum', type='int',
                      help='Maximale Anzahl der zu transferierenden Datensaetze [default: unlimited]')
    options, args = parser.parse_args()
    if args:
        parser.error("incorrect number of arguments")
    if not options.lager:
        parser.error("'--lager' nicht gesetzt")
    if not options.in_queues:
        parser.error("'--in' nicht gesetzt")

    channel = None
    for qname in options.in_queues:
        inqueuename = "%s#%s" % (qname, options.lager)
        channel = messaging.setup_queue(inqueuename, channel=channel, durable=True)
        consumer_tags.append(channel.basic_consume(queue=inqueuename, callback=handle_message))

    # catch Signals
    signal.signal(signal.SIGTERM, lambda signum, stack_frame: cancel_processing())
    signal.signal(signal.SIGINT, lambda signum, stack_frame: cancel_processing())

    display("using %s" % settings.__file__)
    display("waiting for messages ...")

    # Loop while process_messages is True - could be set to False by signal handlers
    last_requeue = 0
    while process_messages:
        # Wait for a message and wait for `handle_message` callback beeing called
        channel.wait()
        if time.time() - last_requeue > 3600:
            # every 3600 seconds Request non-acknowledged messages to be re-delivered
            channel.basic_recover(requeue=True)
            last_requeue = time.time()
            display("%d messages processed, recovering channel" % message_count)

    channel.close()
    return 0


if __name__ == '__main__':
    sys.exit(main())
