#!/usr/bin/python3
# -*- coding: utf-8 -*-
import base64
import logging
# import urllib
import random
import string
# import tempfile
import sys
import os
import json
import datetime
import decimal
import traceback
import simplejson

from libraries.base.calendar import AuraCalendarService
# from libraries.utils.parsexml import parsexml
from libraries.base.schedulerconfig import AuraSchedulerConfig
from libraries.reporting.messenger import RedisMessenger
from libraries.security.user import AuraUser
from libraries.exceptions.auraexceptions import NoProgrammeLoadedException
from libraries.database.broadcasts import ScheduleEntry
#from modules.communication.liquidsoap.liquidcontroller import LiquidController
from modules.communication.liquidsoap.LiquidSoapCommunicator import LiquidSoapCommunicator
from modules.scheduling.scheduler import AuraScheduler



"""
    AuraController Class
    Communicates with the liquidsoap server and the scheduler
"""

class AuraController():
    messenger = RedisMessenger()
    liquidsoapcommunicator = None
    scheduler = None
    userdb = AuraUser()

    is_intern = False
    lq_error = ''
    config = None
    sender = None
    debug = False

    # Constructor
    def __init__(self, config, debug):
        """
        Constructor
        @type    sender: object
        @param   sender: Der Communicator Adapter - z-B. zmq
        @type    lqs_socket: string
        @param   lqs_socket: Liquidsoap Player Socket
        @type    lqs_recsocket: string
        @param   lqs_recsocket: Liquidsoap Recorder Socket
        """
        self.liquidsoapcommunicator = LiquidSoapCommunicator(self, config, debug)
        self.debug = debug

        # Felder die Liquidsoap fuer einen Track (rid) zurueckliefert
        self.knownfields = ["status", "album", "time", "title", "artist", "comment", "filename", "on_air", "source", "rid", "genre"]
        self.job_result = ['', '', '', '', '', '']

        self.messenger.set_channel('controller')
        self.messenger.set_section('execjob')

        errors_file = config.get("install_dir")+"/errormessages/controller_error.js"
        self.errorData = simplejson.load(open(errors_file))

        self.scheduler = AuraScheduler(config, self.liquidsoapcommunicator, self.debug)
        self.config = config

    # ------------------------------------------------------------------------------------------ #
    def set_sender(self, sender):
        """

        :param sender: ServerZMQAdapter
        :return: None
        """
        self.sender = sender

    # ------------------------------------------------------------------------------------------ #
    def message(self, message, log=False, warning=False):
        """
        Daten an einen Client senden oder bei einer internen Aktion loggen
        @type     message: string
        @param    message: String, der gesendet wird
        @type     log: boolean
        @param    log: Wenn falsch, wird die message an den Client gesendet
        @type     warning: boolean
        @param    warning: Wenn falsch, ist der logging typ info, andernfalls warning
        @rtype:   string/None
        @return:  Die Message, falls log false ist
        """
        if log:
            if warning:
                logging.warning(message)
            else:
                logging.info(message)

        if self.is_intern:
            return message

        self.sender.send(message)

    # ------------------------------------------------------------------------------------------ #
    def fetch_new_programme(self):
        acs = AuraCalendarService(self.config, "", "", False)
        queue = acs.get_queue()

        # start fetching thread
        acs.start()

        # wait for the end
        response = queue.get()

        if response == "fetching_finished":
            programme = acs.get_calendar_data()
            self.scheduler.reload_programme()
            return programme
        else:
            print("Got an unknown response from AuraCalendarService: "+response)


    # ------------------------------------------------------------------------------------------ #
    def get_act_programme(self):
        try:
            programme = self.scheduler.get_act_programme()
        except NoProgrammeLoadedException as e:
            print("WARNING: no programme in memory. i have to reload it!")
            # refetch the programme from pv and importer
            self.fetch_new_programme()

            # is the recursion here really needed, or an additional error source?
            return self.get_act_programme()
        except Exception as e:
            traceback.print_exc()
#            exc_type, exc_obj, exc_tb = sys.exc_info()
#            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
#            print(e.with_traceback(True)) # exc_type, fname, exc_tb.tb_lineno)
            self.fatal("01")
        else:
#            entries = []
#            for p in programme:
#                entries.append(p["playlist"]["entries"])
#                print(p)
#            for p in programme["entries"]:
#                print(p)

            #retstring = json.dumps([p._asdict() for p in programme], default=alchemyencoder)
            #retstring = json.dumps([p._asdict() for p in programme["entries"]], default=alchemyencoder)
            #retstring = json.dumps(programme["entries"], default=alchemyencoder)
            #retstring = json.dumps([p["playlist"]["entries"] for p in programme], default=alchemyencoder)
            #print("WANNA SEND")
            #print(retstring)
            self.success("00")
            return programme

    # ------------------------------------------------------------------------------------------ #
    def liquid_startup(self):
        return self.liquidsoapcommunicator.liquid_startup()

    # ------------------------------------------------------------------------------------------ #
    def scheduler_data(self):
        """
        Scheduler Config ausliefern
        """
        jobs = []

        try:
            # Das scheduler.xml laden
            schedulerconfig = AuraSchedulerConfig(self.sender.schedule_config)
            jobs = schedulerconfig.getJobs()
        except:
            # Das scheint kein gültiges XML zu sein
            self.warning('01', False)

        self.success('00', simplejson.dumps(jobs))
        self.notify_client()

    # ------------------------------------------------------------------------------------------ #
    def scheduler_store(self, adminuser, adminpassword, json):
        """
        Scheduler Config zurückschreiben
        """
        if not self.userdb.hasAdminRights(adminuser, adminpassword):
            self.warning('01', False)
            self.notify_client()
            return
        try:
            schedulerconfig = AuraSchedulerConfig(self.sender.schedule_config)
        except:
            self.warning('02', False)
            self.notify_client()
        try:
            schedulerconfig.storeJsonToXml( base64.b64decode(json))
        except:
            self.warning('02', False)
            self.notify_client()
        else:
            if self.scheduler_reload():
                self.success('00', True)
            else:
                self.warning('02', False)
        self.notify_client()

    # ------------------------------------------------------------------------------------------ #
    def set_password(self, adminuser, adminpassword, username, password):
        """
        Ein Userpasswort setzen
        TODO: Passwörter verschlüsselt übertragen
        """
        if self.userdb.hasAdminRights(adminuser, adminpassword):
            self.userdb.setPassword(username, password)
            self.success('00', password)
        else:
            self.warning('01', False)

        self.notify_client()
        self.sender.reload_config()

    # ------------------------------------------------------------------------------------------ #
    def add_user(self, adminuser, adminpassword, username):
        """
        Einen User hinzufügen
        TODO: Passwort verschlüsselt übertragen
        """
        if self.userdb.hasAdminRights(adminuser, adminpassword):
            password = ''.join(random.sample(string.lowercase+string.uppercase+string.digits,14))
            self.userdb.insertUser(username, password, 'user')
            self.success('00', password)
        # TODO admin rechte checken user und passwort setzen, passwort zurückgeben
        else:
            self.warning('01', False)

        self.notify_client()
        self.sender.reload_config()

    # ------------------------------------------------------------------------------------------ #
    def del_user(self, adminuser, adminpassword, username):
        """
        Einen User löschen
        TODO: Passwort verschlüsselt übertragen
        """
        # TODO admin rechte checken user löschen
        if self.userdb.hasAdminRights(adminuser, adminpassword):
            self.userdb.delete(username)
            self.success('00', True)
        else:
            self.warning('01', False)

        self.notify_client()
        self.sender.reload_config()

    # ------------------------------------------------------------------------------------------ #
    def get_user_list(self, adminuser, adminpassword):
        """
        Einen User löschen
        TODO: Passwort verschlüsselt übertragen
        """
        # TODO admin rechte checken user löschen
        if self.userdb.hasAdminRights(adminuser, adminpassword):
            userlist = self.userdb.getUserlist()
            self.success('00', simplejson.dumps(userlist))
        else:
            self.warning('01', False)

        self.notify_client()


    # ------------------------------------------------------------------------------------------ #
    def __metadata_format__(self, metadata):
        """
        Private: Vereinheitlicht die Metadaten von Playlist und anderen Kanälen und entfernt Anführungszeichen in den Feldern
        @rtype:   boolean/dict
        @return:  False/Metadaten
        """
        mdata = {}
        try:
            for key,val in metadata.items('root'):
                if key in self.knownfields:
                    mdata[key] = val.strip().replace('"', '')
            return mdata
        except:
            return False

    # ------------------------------------------------------------------------------------------ #
    def __get_error__(self, errornumber):
        """
        Privat: Ermittelt Fehlermeldung, Job-Name (Klassenmethode) und Fehlercode für den Job aus error/controller_error.js
        @type errornumber:  string
        @param errornumber: Die interne Fehlernummer der aufrufenden Methode
        """
        f = sys._getframe(2)

        job = f.f_code.co_name
        data = {'message':'', 'job':job, 'code':'unknown'}
        if job in self.errorData:
            errMsg = self.errorData[job][errornumber]
            errID = self.errorData[job]['id'] + str(errornumber)
            args = {x:f.f_locals[x] if not x == 'self' else '' for x in f.f_code.co_varnames[:f.f_code.co_argcount]}

            for key in args.keys():
                errMsg = errMsg.replace('::' + key + '::', str(args[key]))

            data['message'] = errMsg
            data['job'] = job
            data['code'] = errID

        return data

    # ------------------------------------------------------------------------------------------ #
    def success(self, errnum='00', value='', section='main'):
        """
        Erfolgsmeldung loggen
        @type errnum:    string
        @param errnum:   Errornummer der aufrufenden Funktion
        @type value:     string
        @param value:    Optionaler Wert
        @type section:   string
        @param section:  Gültigkeitsbereich
        """
        error = self.__get_error__(errnum)
        self.job_result = {'message':error['message'], 'code':error['code'], 'success':'success', 'job':error['job'], 'value':value, 'section':section}
        self.messenger.send(error['message'], error['code'], 'success', error['job'], value, section)

    # ------------------------------------------------------------------------------------------ #
    def info(self, errnum='01', value='', section='main'):
        """
        Info loggen
        @type errnum:    string
        @param errnum:   Errornummer der aufrufenden Funktion
        @type value:     string
        @param value:    Optionaler Wert
        @type section:   string
        @param section:  Gültigkeitsbereich
        """
        error = self.__get_error__(errnum)
        self.job_result = {'message':error['message'], 'code':error['code'], 'success':'info', 'job':error['job'], 'value':value, 'section':section}
        self.messenger.send(error['message'], error['code'], 'info', error['job'], value, section)

    # ------------------------------------------------------------------------------------------ #
    def warning(self, errnum='01', value='', section='main'):
        """
        Warnung loggen
        @type errnum:    string
        @param errnum:   Errornummer der aufrufenden Funktion
        @type value:     string
        @param value:    Optionaler Wert
        @type section:   string
        @param section:  Gültigkeitsbereich
        """
        error = self.__get_error__(errnum)
        self.job_result = {'message':error['message'], 'code':error['code'], 'success':'warning', 'job':error['job'], 'value':value, 'section':section}
        self.messenger.send(error['message'], error['code'], 'warning', error['job'], value, section)

    # ------------------------------------------------------------------------------------------ #
    def error(self, errnum='01', value='', section='main'):
        """
        Error loggen
        @type errnum:    string
        @param errnum:   Errornummer der aufrufenden Funktion
        @type value:     string
        @param value:    Optionaler Wert
        @type section:   string
        @param section:  Gültigkeitsbereich
        """
        error = self.__get_error__(errnum)
        self.job_result = {'message':error['message'], 'code':error['code'], 'success':'error', 'job':error['job'], 'value':value, 'section':section}
        self.messenger.send(error['message'], error['code'], 'error', error['job'], value, section)

    # ------------------------------------------------------------------------------------------ #
    def fatal(self, errnum='01', value='', section='main'):
        """
        Fatal error loggen
        @type errnum:    string
        @param errnum:   Errornummer der aufrufenden Funktion
        @type value:     string
        @param value:    Optionaler Wert
        @type section:   string
        @param section:  Gültigkeitsbereich
        """
        error = self.__get_error__(errnum)
        self.job_result = {'message':error['message'], 'code':error['code'], 'success':'fatal', 'job':error['job'], 'value':value, 'section':section}
        self.messenger.send(error['message'], error['code'], 'fatal', error['job'], value, section)

    # ------------------------------------------------------------------------------------------ #
    def notify_client(self):
        """
        Eine Nachricht als JSON-String an den Client senden
        """
        if not self.is_intern:
            self.message(simplejson.dumps(self.job_result))



    # ------------------------------------------------------------------------------------------ #
    def __check_result__(self, result):
        """
        Fehlerbehandlung
        @type     result: string
        @param    result: Ein Json-String
        """
        self.lq_error = simplejson.loads(result)

        try:
            if self.lq_error['success'] == 'true':
                return True
            else:
                return False
        except:
            return False

    # ------------------------------------------------------------------------------------------ #
    def __update_event_queue__(self, playlist):
        """
        Playlist Eventqueue updaten
        @type playlist: dict
        @param playlist: Playlist
        """
        # eventuell noch bestehende Events im Queue löschen
        self.messenger.queue_remove_events('playtrack', 'player')
        # Für jeden Tack einen Event ankündigen
        for track in playlist['playlist']['trackList']['track']:

            if 'time' in track and 'start' in track:
                starts = str(track['start'] + 'T' + track['time'])
                event = {'job':'play', 'location': track['location'],'length': track['length'], 'date': track['start'], 'time': track['time']}
                self.messenger.queue_add_event('playtrack', starts, event, 'player')