import json
import socket
import threading
import time
import ipc_constants
from ipc_util import our_eth_mac_addr, our_real_ip_addr
import os
from ipc_common import PEAK_READBACK_FILE

CHANNEL = "a"
USER_RMS_LIM = "b"
USER_PEAK_LIM = "c"
SPEAKER_RMS_LIM = "d"
SPEAKER_PEAK_LIM = "e"
AMP_LIM = "f"
AMP_ON = "g"
TEMP = 'h'
AVERAGE_POWER = "i"
OVERTEMP = "j"
VOLTAGE = "k"

class IPCUdpCommand:
    def __init__(self, svcobj):
        self.__port = svcobj.data["settings"]["udp_port"]
        self.__stop_event = threading.Event()

        self.listening_thread = threading.Thread(
            target=listening,
            name="UDP-L",
            kwargs={"svcobj": svcobj, "port": self.__port, "stop_event": self.__stop_event})
        self.listening_thread.start()

    def stop_service(self):
        self.__stop_event.set()
        try:
            self.listening_thread.join()
        except (RuntimeError, AttributeError):
            pass


def listening(svcobj, port: int, stop_event: threading.Event):
    # Initiate UDP listening socket
    in_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    in_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        in_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except AttributeError:
        print("FYI: SO_REUSERPORT not supported on this system.")
    in_sock.settimeout(1)
    in_sock.bind(('', port))  # Local listening port

    ipaddr = socket.gethostbyname(socket.gethostname())
    # in_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(ipaddr))

    print("UDP listening started.")

    while not stop_event.is_set():
        try:
            data, remote_addr = in_sock.recvfrom(1024)
            try:
                data_str = json.loads(data)
                answer = dict()

                if 'command' in data_str:
                    cmd = data_str['command']

                # GET
                # GET
                # GET
                # GET

                    # Get input gain for source "arg1"
                    if cmd == 'get_source_mixer_gain':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_SOURCE:
                                gain = []
                                for i in range (0, ipc_constants.NUM_OF_SOURCE_REDUCED, 1):
                                    gain.append(svcobj.data["channels"][channel]["mixer"][i]["gain"])
                                answer["response"] = gain
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Get channel label for source "arg1"
                    elif cmd == 'get_channel_label':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                answer["response"] = svcobj.data["channels"][channel]["name"]
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"


                    # Get channel gain for source "arg1"
                    elif cmd == 'get_channel_gain':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                answer["response"] = svcobj.data["channels"][channel]["gain"]
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Get channel mute for source "arg1"
                    elif cmd == 'get_channel_mute':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                answer["response"] = svcobj.data["channels"][channel]["mute"]
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Get group gain for source "arg1"
                    elif cmd == 'get_group_gain':
                        if 'arg1' in data_str :
                            group = int(data_str['arg1'])-1
                            if group >= 0 and group < ipc_constants.NUM_OF_GROUPS:
                                answer["response"] = svcobj.data["groups"][group]["gain"]
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Get group mute for source "arg1"
                    elif cmd == 'get_group_mute':
                        if 'arg1' in data_str :
                            group = int(data_str['arg1'])-1
                            if group >= 0 and group < ipc_constants.NUM_OF_GROUPS:
                                answer["response"] = svcobj.data["groups"][group]["mute"]
                                answer["status"] = "OK"
                            # Wrong arg 1 value
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Get standby status : false = ON, true = standby
                    elif cmd == 'get_standby_status':
                        answer["response"] = svcobj.data["settings"]["amp_stage_disabled"]
                        answer["status"] = "OK"

                    # Get amplifier status : Not yet implemented
                    elif cmd == 'get_amplifier_status':
                        with open(PEAK_READBACK_FILE, "r", os.O_NONBLOCK) as readback_file:
                            raw_readback_data = readback_file.read()
                        try:
                            # convert dictionary string to dictionary
                            readback_data = json.loads(raw_readback_data)
                        except Exception:
                            return
                        overtemp = []
                        for i in range (0, ipc_constants.NUM_OF_MODULES, 1):
                            overtemp.append(readback_data[OVERTEMP+str(i+1)])
                        if max(overtemp) == 1:
                            answer["response"] = False
                            answer["status"] = "KO"
                        else:
                            answer["response"] = True
                            answer["status"] = "OK"
                    
                    # Get amplifier output level : 
                    elif cmd == 'get_output_level':
                        with open(PEAK_READBACK_FILE, "r", os.O_NONBLOCK) as readback_file:
                            raw_readback_data = readback_file.read()
                        try:
                            # convert dictionary string to dictionary
                            readback_data = json.loads(raw_readback_data)
                        except Exception:
                            return
                        level = []
                        for i in range (0, ipc_constants.NUM_OF_CHANNELS, 1):
                            level.append(readback_data[CHANNEL+str(i+1)])
                        answer["response"] = level
                        answer["status"] = "OK"
                    
                    # Get amplifier module temperature : 
                    elif cmd == 'get_module_temperature':
                        with open(PEAK_READBACK_FILE, "r", os.O_NONBLOCK) as readback_file:
                            raw_readback_data = readback_file.read()
                        try:
                            # convert dictionary string to dictionary
                            readback_data = json.loads(raw_readback_data)
                        except Exception:
                            return
                        level = []
                        for i in range (0, ipc_constants.NUM_OF_MODULES, 1):
                            level.append(readback_data[TEMP+str(i+1)])
                        answer["response"] = max(level)
                        answer["status"] = "OK"

                    # Get amplifier module voltage : 
                    elif cmd == 'get_module_voltage':
                        with open(PEAK_READBACK_FILE, "r", os.O_NONBLOCK) as readback_file:
                            raw_readback_data = readback_file.read()
                        try:
                            # convert dictionary string to dictionary
                            readback_data = json.loads(raw_readback_data)
                        except Exception:
                            return
                        volt = []
                        for i in range (0, ipc_constants.NUM_OF_MODULES, 1):
                            volt.append(readback_data[VOLTAGE+str(i+1)])
                        answer["response"] = max(volt)
                        answer["status"] = "OK"

                # SET
                # SET
                # SET
                # SET

                    # Set mixer input gain for channel "arg1" input "arg2" mixer "arg3" => arg3 one value
                    elif cmd == 'set_source_mixer_gain':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    source = int(data_str['arg2'])-1
                                    if source >= 0 and source <= ipc_constants.NUM_OF_SOURCE_REDUCED:
                                        if 'arg3' in data_str:
                                            gain = float(data_str['arg3'])
                                            if gain >= ipc_constants.UDP_GAIN_MIN and gain <= ipc_constants.UDP_GAIN_MAX:
                                                svcobj.udp_set_input_mixer_gain(channel,source,gain)
                                                answer["response"] = svcobj.data["channels"][channel]["mixer"][source]["gain"]
                                                answer["status"] = "OK"
                                            else:
                                                answer["response"] = "arg3 wrong value"
                                                answer["status"] = "KO"
                                        else:
                                            answer["response"] = "arg3 missing"
                                            answer["status"] = "KO" 
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set mixer all gains for channel "arg1" input "arg2" => arg2 array of values
                    elif cmd == 'set_all_mixer_gain':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    if len(data_str['arg2']) == ipc_constants.NUM_OF_SOURCE_REDUCED:
                                        gains = data_str['arg2']
                                        check = 100
                                        for i_gain in range (0, ipc_constants.NUM_OF_SOURCE_REDUCED):
                                            gain = float(gains[i_gain])
                                            if gain < ipc_constants.UDP_GAIN_MIN and gain > ipc_constants.UDP_GAIN_MAX:
                                                check = i_gain
                                        if check == 100:
                                            svcobj.udp_set_all_mixer_gain(channel, gains)
                                            answer["response"] = gains
                                            answer["status"] = "KO" 
                                        else: 
                                            answer["response"] = "arg2 item " + check + " out of range"
                                            answer["status"] = "KO" 
                                    else:
                                        answer["response"] = "arg2 wrong length"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set channel mute for source "arg1" and muted value "arg2"
                    elif cmd == 'set_channel_mute':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    muted = bool(data_str['arg2'])
                                    svcobj.udp_set_channel_mute(channel, muted)
                                    mute_status = bool(muted)
                                    for i_group in range(0, ipc_constants.NUM_OF_GROUPS):  # calculate mute groups impact
                                        if 1 == svcobj.data["channels"][channel]["assigned_to_group"][i_group]:
                                            mute_status = mute_status or svcobj.data["groups"][i_group]["mute"]
                                    answer["response"] = mute_status
                                    answer["status"] = "OK"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set channel gain for source "arg1"
                    elif cmd == 'set_channel_gain':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    gain = float(data_str['arg2'])
                                    if gain >= ipc_constants.UDP_GAIN_MIN and gain <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_channel_gain(channel,gain)
                                        answer["response"] = svcobj.data["channels"][channel]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set gain up for source "arg1"
                    elif cmd == 'set_channel_gain_up':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    gain = svcobj.data["channels"][channel]["gain"]
                                    step = float(data_str['arg2'])
                                    if (gain + step) >= ipc_constants.UDP_GAIN_MIN and (gain + step) <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_channel_gain(channel,(gain+step))
                                        answer["response"] = svcobj.data["channels"][channel]["gain"]
                                        answer["status"] = "OK"
                                    elif (gain + step) > ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_channel_gain(channel,ipc_constants.UDP_GAIN_MAX)
                                        answer["response"] = svcobj.data["channels"][channel]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set gain down for source "arg1"
                    elif cmd == 'set_channel_gain_down':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    gain = svcobj.data["channels"][channel]["gain"]
                                    step = float(data_str['arg2'])
                                    if (gain - step) >= ipc_constants.UDP_GAIN_MIN and (gain - step) <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_channel_gain(channel,(gain-step))
                                        answer["response"] = svcobj.data["channels"][channel]["gain"]
                                        answer["status"] = "OK"
                                    elif (gain - step) < ipc_constants.UDP_GAIN_MIN:
                                        svcobj.udp_set_channel_gain(channel,ipc_constants.UDP_GAIN_MIN)
                                        answer["response"] = svcobj.data["channels"][channel]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set group mute for source "arg1" and muted value "arg2"
                    elif cmd == 'set_group_mute':
                        if 'arg1' in data_str :
                            group_number = int(data_str['arg1'])-1
                            if group_number >= 0 and group_number < ipc_constants.NUM_OF_GROUPS:
                                if 'arg2' in data_str:
                                    muted = bool(data_str['arg2'])
                                    svcobj.udp_set_group_mute(group_number, muted)
                                    answer["response"] = bool(muted)
                                    answer["status"] = "OK"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set group gain for source "arg1"
                    elif cmd == 'set_group_gain':
                        if 'arg1' in data_str :
                            group = int(data_str['arg1'])-1
                            if group >= 0 and group < ipc_constants.NUM_OF_GROUPS:
                                if 'arg2' in data_str:
                                    gain = float(data_str['arg2'])
                                    if gain >= ipc_constants.UDP_GAIN_MIN and gain <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_group_gain(group,gain)
                                        answer["response"] = svcobj.data["groups"][group]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set gain up for group "arg1"
                    elif cmd == 'set_group_gain_up':
                        if 'arg1' in data_str :
                            group = int(data_str['arg1'])-1
                            if group >= 0 and group < ipc_constants.NUM_OF_GROUPS:
                                if 'arg2' in data_str:
                                    gain = svcobj.data["groups"][group]["gain"]
                                    step = float(data_str['arg2'])
                                    if (gain + step) >= ipc_constants.UDP_GAIN_MIN and (gain + step) <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_group_gain(group,(gain+step))
                                        answer["response"] = svcobj.data["groups"][group]["gain"]
                                        answer["status"] = "OK"
                                    elif (gain + step) > ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_group_gain(group,ipc_constants.UDP_GAIN_MAX)
                                        answer["response"] = svcobj.data["groups"][group]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set gain down for group "arg1"
                    elif cmd == 'set_group_gain_down':
                        if 'arg1' in data_str :
                            group = int(data_str['arg1'])-1
                            if group >= 0 and group < ipc_constants.NUM_OF_GROUPS:
                                if 'arg2' in data_str:
                                    gain = svcobj.data["groups"][group]["gain"]
                                    step = float(data_str['arg2'])
                                    if (gain - step) >= ipc_constants.UDP_GAIN_MIN and (gain - step) <= ipc_constants.UDP_GAIN_MAX:
                                        svcobj.udp_set_group_gain(group,(gain-step))
                                        answer["response"] = svcobj.data["groups"][group]["gain"]
                                        answer["status"] = "OK"
                                    elif (gain - step) < ipc_constants.UDP_GAIN_MIN:
                                        svcobj.udp_set_group_gain(group,ipc_constants.UDP_GAIN_MIN)
                                        answer["response"] = svcobj.data["groups"][group]["gain"]
                                        answer["status"] = "OK"
                                    else:
                                        answer["response"] = "arg2 wrong value"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set all channel mute for muted value "arg1"
                    elif cmd == 'set_mute_all':
                        if 'arg1' in data_str :
                            muted = bool(data_str['arg1'])
                            svcobj.udp_set_all_mute(muted)
                            answer["response"] = muted
                            answer["status"] = "OK"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set speaker preset on channel "arg1" with path "arg2"
                    elif cmd == 'set_speaker_preset':
                        if 'arg1' in data_str :
                            channel = int(data_str['arg1'])-1
                            if channel >= 0 and channel < ipc_constants.NUM_OF_CHANNELS:
                                if 'arg2' in data_str:
                                    path = data_str['arg2']
                                    if len(path) > 0:
                                        preset_found = svcobj.udp_set_speaker_preset(channel, path)
                                        if preset_found:
                                            answer["response"] = path
                                            answer["status"] = "OK"
                                        else:
                                            answer["response"] = "unknown path"
                                            answer["status"] = "KO"
                                    else:
                                        answer["response"] = "arg2 length is null"
                                        answer["status"] = "KO"
                                else:
                                    answer["response"] = "arg2 missing"
                                    answer["status"] = "KO" 
                            else:
                                answer["response"] = "arg1 wrong value"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"

                    # Set global preset "arg1"
                    elif cmd == 'set_global_preset':
                        if 'arg1' in data_str :
                            path = data_str['arg1']
                            if len(path) > 0:
                                preset_found = svcobj.udp_set_global_preset(path)
                                if preset_found:
                                    answer["response"] = path
                                    answer["status"] = "OK"
                                else:
                                    answer["response"] = "unknown path"
                                    answer["status"] = "KO"
                            else:
                                answer["response"] = "arg1 length is null"
                                answer["status"] = "KO"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"
                            
                    # else: 
                    #     answer["response"] = "Unknown command"
                    #     answer["status"] = "KO"

                    # Set standby status : Not yet implemented
                    elif cmd == 'set_standby_status':
                        if 'arg1' in data_str :
                            muted = bool(data_str['arg1'])
                            svcobj.amp_stage_disable_set(muted)
                            answer["response"] = muted
                            answer["status"] = "OK"
                        else:
                            answer["response"] = "arg1 missing"
                            answer["status"] = "KO"
                        # answer["response"] = "Not yet implemented"
                        # answer["status"] = "OK"

                    # # Set amplifier status : Not yet implemented
                    # elif cmd == 'set_amplifier_status':
                    #     answer["response"] = "Not yet implemented"
                    #     answer["status"] = "OK"

                # Check command key on packet
                else: 
                        answer["response"] = "Command key missing"
                        answer["status"] = "KO"

                answer = json.dumps(answer).encode()
                in_sock.sendto(answer, (remote_addr[0], port))

            # Check json load on packet
            except ValueError as e:
                errorAnswer = "Wrong packet format"
                errorAnswer = errorAnswer.encode()
                in_sock.sendto(errorAnswer, (remote_addr[0], port))

        except (socket.timeout, ValueError, TypeError):
            # print("UDP command error")
            continue

    print("UDP listening ends.")
    in_sock.close()
