"""
This file is part of IDRB Project.

IDRB Project 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 3 of the License, or
(at your option) any later version.

IDRB Project 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 IDRB Project.  If not, see <https://www.gnu.org/licenses/>.
"""
import queue
import time
from datetime import datetime
import cv2
import dearpygui.dearpygui as dpg
import threading
import socket
import requests
import pickle
import pyaudio
import zmq
from pyogg import OpusDecoder
import configparser
import ctypes
import lz4.frame

from utils import *
import appcomponent


class App:
    def __init__(self):
        self.RDS = None
        self.config = configparser.ConfigParser()
        self.config.read("config.ini")
        self.device_name_output = self.config["audio"]["device"]
        self.buffersize = int(self.config["network"]["buffersize"])

        self.working = False
        self.readchannel = 1
        self.firstrun = True
        self.firststart = True
        self.device_index_output = 0
        self.ccdecryptpassword = None
        self.ccisencrypt = None
        self.ccisdecrypt = None
        self.ccisdecryptpassword = None
        self.paudio = pyaudio.PyAudio()
        self.cprotocol = None
        self.cciswaitlogoim = True
        self.ccthreadlogorecisworking = False
        self.ccserversecount = 0
        self.lsitem = None
        self.ccconwithpubselect = False
        self.buffer = queue.Queue(maxsize=self.buffersize)

    def connecttoserverwithpubselect(self, sender, data):
        self.ccconwithpubselect = True
        dpg.configure_item("pubserverselectwindow", show=False)
        dpg.configure_item("connectbuttonpubserverselect", show=False)
        self.connecttoserver(None, None)

    def connecttoserver(self, sender, data):
        dpg.configure_item("connectservergroup", show=False)
        dpg.configure_item("serverstatus", default_value='connecting...', color=(255, 255, 0))
        if self.ccconwithpubselect:
            serverlabel = str(dpg.get_item_configuration(self.lsitem)["label"])
            protocol = serverlabel.split("|")[0].strip()
            ip = serverlabel.split("|")[1].split(":")[0].strip()
            port = serverlabel.split("|")[1].split(":")[1].strip()

            self.ccconwithpubselect = False
        else:
            protocol = dpg.get_value("serverprotocol").strip()
            ip = dpg.get_value("serverip").strip()
            port = dpg.get_value("serverport")

        self.cprotocol = protocol

        if protocol == "TCP":
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                s.connect((ip, port))
            except:
                dpg.configure_item("connectbutton", show=True)
        elif protocol == "ZeroMQ":
            context = zmq.Context()
            s = context.socket(zmq.SUB)
            s.connect(f"tcp://{ip}:{port}")
            s.setsockopt_string(zmq.SUBSCRIBE, "")
        elif protocol == "ZeroMQ (WS)":
            context = zmq.Context()
            s = context.socket(zmq.SUB)
            s.connect(f"ws://{ip}:{port}")
            s.setsockopt_string(zmq.SUBSCRIBE, "")
            self.cprotocol = "ZeroMQ"
        self.working = True

        self.device_index_output = 0
        for i in range(self.paudio.get_device_count()):
            dev = self.paudio.get_device_info_by_index(i)
            if dev['name'] == self.device_name_output:
                self.device_index_output = dev['index']
                break

        thread = threading.Thread(target=self.stream, args=(s, ))
        thread.start()

    def disconnectserver(self, sender=None, data=None):
        dpg.configure_item("disconnectbutton", show=False)
        dpg.configure_item("serverstatus", default_value='disconnecting...', color=(255, 255, 0))
        self.working = False
        dpg.configure_item("serverinfobutton", show=False)
        dpg.configure_item("mediachannelselect", show=False)
        dpg.configure_item("morerdsbutton", show=False)
        dpg.configure_item("station_logo_config", show=False)
        dpg.configure_item("RDSinfo", show=False)
        dpg.configure_item("disconnectbutton", show=False)
        dpg.configure_item("logostatus", show=False)
        self.firstrun = True
        self.firststart = True
        self.ccdecryptpassword = None
        self.ccisencrypt = None
        self.ccisdecrypt = None
        self.ccisdecryptpassword = None
        self.cciswaitlogoim = True
        self.ccthreadlogorecisworking = False
        # clear buffer

    def RDSshow(self):
        try:
            dpg.configure_item("RDSinfo",
                               default_value=f'{self.RDS["PS"]} ({self.RDS["ContentInfo"]["Codec"].upper()} {self.RDS["ContentInfo"]["bitrate"] / 1000}Kbps {self.RDS["AudioMode"]})',
                               show=True)

            dpg.configure_item("RDSPS", default_value="PS: " + self.RDS["PS"])
            dpg.configure_item("RDSRT", default_value="RT: " + limit_string_in_line(self.RDS["RT"], 120))
            dpg.configure_item("RDSCTlocal", default_value="Time Local: " + datetime.fromtimestamp(self.RDS["CT"]["Local"]).strftime('%H:%M:%S'))
            dpg.configure_item("RDSCTUTC", default_value="Time UTC: " + datetime.fromtimestamp(self.RDS["CT"]["UTC"]).strftime('%H:%M:%S'))
            try:
                if self.RDS["images"]["logo"]["lazy"] and not self.ccthreadlogorecisworking:
                    if not self.RDS["images"]["logo"]["contents"] == b'':
                        print(self.RDS["images"]["logo"]["contents"] == b'',
                            self.RDS["images"]["logo"]["part"]["total"] == \
                            self.RDS["images"]["logo"]["part"]["current"],
                            self.RDS["images"]["logo"]["part"]["current"] > 0)
                        self.ccthreadlogorecisworking = True
                        logoreciveprocessingthread = threading.Thread(target=self.RDSlogorecivelazy)
                        logoreciveprocessingthread.start()
                else:
                    if not self.RDS["images"]["logo"]["lazy"]:
                        dpg.set_value("station_logo", CV22DPG(cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"]["contents"], np.uint8), cv2.IMREAD_COLOR)))
                        dpg.configure_item("logostatus", show=False)
            except Exception as e:
                dpg.configure_item("station_logo_config", show=False)
                #print(e)

        except Exception as e:
            pass

    def RDSlogorecivelazy(self):
        try:
            received_data = b""
            received_data_current_past = b""
            received_data_current = b""
            try:
                print(self.RDS["images"]["logo"]["part"]["current"], self.RDS["images"]["logo"]["part"]["total"])
                while not self.RDS["images"]["logo"]["part"]["current"] == self.RDS["images"]["logo"]["part"]["total"]:
                    currentprocess = self.RDS["images"]["logo"]["part"]["current"]
                    totalprocess = self.RDS["images"]["logo"]["part"]["total"]

                    received_data_current = self.RDS["images"]["logo"]["contents"]

                    if received_data_current != received_data_current_past:
                        print(received_data_current)
                        received_data_current_past = received_data_current
                        received_data += received_data_current

                    dpg.configure_item("logostatus", color=(255, 255, 0), default_value=f"Receiving... ({currentprocess}/{totalprocess})")

                dpg.set_value("station_logo", CV22DPG(cv2.imdecode(np.frombuffer(received_data_current, np.uint8), cv2.IMREAD_COLOR)))
                dpg.configure_item("logostatus", color=(0, 255, 0), default_value=f"Received logo! waiting for new image...")
                dpg.configure_item("station_logo_config", show=True)
                self.ccthreadlogorecisworking = False
            except Exception as e:
                print("receive error", e)
                dpg.configure_item("logostatus", color=(255, 0, 0), default_value=f"Receive logo error! waiting for new image...")
                dpg.configure_item("station_logo_config", show=False)
                self.ccthreadlogorecisworking = False

        except Exception as e:
            pass

    def changechannel(self, sender, data):
        dpg.configure_item("serverstatus", default_value='please wait...', color=(255, 255, 0))
        dpg.configure_item("station_logo_config", show=False)
        self.readchannel = int(dpg.get_value(sender).split(" ")[0])
        self.firstrun = True
        self.ccdecryptpassword = None
        self.ccisencrypt = None
        self.ccisdecrypt = None
        self.ccisdecryptpassword = None

        self.device_index_output = 0
        for i in range(self.paudio.get_device_count()):
            dev = self.paudio.get_device_info_by_index(i)
            if dev['name'] == self.device_name_output:
                self.device_index_output = dev['index']
                break

    def submitpassworddecrypt(self, sender, data):
        dpg.configure_item("requestpasswordpopup", show=False)
        self.ccdecryptpassword = dpg.get_value("requestpasswordinputpopup")
        self.ccisdecryptpassword = True

    def changeaudiodevice(self, sender, data):
        self.device_name_output = dpg.get_value(sender)
        self.config["audio"]["device"] = dpg.get_value(sender)
        self.config.write(open('config.ini', 'w'))

    def changebuffersize(self, sender, data):
        self.buffersize = int(dpg.get_value(sender))
        self.config["network"]["buffersize"] = str(dpg.get_value(sender))
        self.config.write(open('config.ini', 'w'))

    def pubserverselectone(self, sender, data):
        if data == False:
            dpg.configure_item("connectbuttonpubserverselect", show=False)
            return
        else:
            dpg.configure_item("connectbuttonpubserverselect", show=True)

        if self.lsitem == None:
            self.lsitem = sender

        if self.lsitem != sender:
            dpg.set_value(self.lsitem, False)
            self.lsitem = sender

        if dpg.get_item_configuration(self.lsitem)["label"] == "N/A":
            dpg.configure_item("connectbuttonpubserverselect", show=False)


    def pubserverselectsearch(self, serversearch="", limit=10):
        self.lsitem = None
        if serversearch == "":
            dpg.configure_item("pubserverselectstatus", default_value="Please wait...", color=(255, 255, 0))
        else:
            dpg.configure_item("pubserverselectstatus", default_value="Searching...", color=(255, 255, 0))

        if self.ccserversecount != 0:
            # clear list
            for i in range(self.ccserversecount):
                dpg.delete_item(f"pubserverid{i}")

            self.ccserversecount = 0

        if serversearch == "":
            response = requests.get(f"https://thaisdr.damp11113.xyz/api/idrbdir/getallstation?limit={limit}")
        else:
            response = requests.get(f"https://thaisdr.damp11113.xyz/api/idrbdir/getallstation?limit={limit}&serversearch={serversearch}")

        # Check if the request was successful (status code 200)
        if response.status_code == 200:
            # Parse JSON data
            allstationdata = response.json()

            # Iterate over each station
            for server_id, server_details in allstationdata.items():
                with dpg.table_row(parent="pubserverlist", tag=f"pubserverid{self.ccserversecount}"):
                    if server_details['ServerURL'] == "" or server_details['ServerPort'] == "" or server_details['ServerProtocol'] == "":
                        dpg.add_selectable(label=f"N/A", span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
                    else:
                        dpg.add_selectable(label=f"{server_details['ServerProtocol']} | {server_details['ServerURL']}:{server_details['ServerPort']}", span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)

                    dpg.add_selectable(label=server_details["ServerName"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
                    dpg.add_selectable(label=server_details["ServerDesc"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
                    dpg.add_selectable(label=server_details["ConnectionUser"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)


                self.ccserversecount += 1

        dpg.configure_item("pubserverselectstatus", default_value=f"Founded {self.ccserversecount} server", color=(0, 255, 0))

    def pubserverselectopen(self, sender, data):
        dpg.configure_item("pubserverselectwindow", show=True)
        self.pubserverselectsearch()

    def streambuffer(self, socket):
        while self.working:
            try:
                if self.cprotocol == "TCP":
                    tempdata = b''
                    # data = socket.recv(1580152)
                    while True:
                        part = socket.recv(1024)
                        tempdata += part
                        if len(part) < 1024:
                            # either 0 or end of data
                            break
                    self.buffer.put(tempdata, timeout=0.1)
                elif self.cprotocol == "ZeroMQ":
                    self.buffer.put(socket.recv(), timeout=0.1)
                else:
                    self.buffer.put(b"")

                dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(0, 255, 0))
            except queue.Full:
                dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 0, 0))

        while not self.buffer.empty():
            self.buffer.get()

        dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 255, 0))
        dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
        dpg.configure_item("connectservergroup", show=True)

    def stream(self, socket):
        opus_decoder = None
        streamoutput = None
        tfrpx = list(range(250))
        altfrpy = [0] * 250
        adcctfrpy = [0] * 250
        imcctfrpy = [0] * 2500
        bytesconunt = 0
        bytesconunt_frame = 0
        codecbytesconunt = 0
        start_time = time.time()
        evaluation_audio_X = None
        decodecodec = None


        SBT = threading.Thread(target=self.streambuffer, args=(socket,))
        SBT.start()

        while True:
            try:
                if self.working:
                    if self.buffer.not_empty:
                        data = self.buffer.get()
                    else:
                        dpg.configure_item("serverstatus", default_value='Buffering...', color=(255, 255, 0))
                        continue

                    bytesconunt += len(data)


                    if bytesconunt_frame >= 10:
                        stoptime = time.time()
                        speed_kbps = calculate_speed(start_time, stoptime, bytesconunt)
                        dpg.configure_item("serverstatus", default_value=f'connected {int(speed_kbps)}Kbps ({len(data)})', color=(0, 255, 0))

                        codec_kbps = calculate_throughput(start_time, stoptime, codecbytesconunt)

                        dpg.configure_item("codecbitratestatus", default_value=f'{self.RDS["ContentInfo"]["Codec"].upper()} {self.RDS["ContentInfo"]["bitrate"] / 1000}/{codec_kbps:.2f} Kbps')

                        dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}')

                        start_time = time.time()
                        bytesconunt_frame = 0
                        bytesconunt = 0
                        codecbytesconunt = 0

                    if len(altfrpy) > 250:
                        altfrpy.pop(0)
                    altfrpy.append(len(data))
                    dpg.set_value('transferatealldataplot', [tfrpx, altfrpy])

                    if len(data) == 0:
                        dpg.configure_item("serverstatus", default_value='lost connected', color=(255, 0, 0))
                        socket.close()
                        if self.cprotocol == "TCP":
                            socket.close()
                        elif self.cprotocol == "ZeroMQ":
                            try:
                                message = socket.recv(zmq.NOBLOCK)
                                if message is None:
                                    break  # No more messages
                                # Process the received message if needed
                                print(f"Received message: {message.decode()}")
                            except zmq.error.ZMQError as e:
                                if e.errno == zmq.EAGAIN:
                                    break  # No more messages
                                else:
                                    raise
                            socket.close()
                        else:
                            socket.close()
                        self.disconnectserver()
                        break

                    try:
                        decompressed_data = lz4.frame.decompress(data)

                        datadecoded = pickle.loads(decompressed_data)
                    except:
                        pass

                    if len(imcctfrpy) > 250:
                        imcctfrpy.pop(0)
                    imcctfrpy.append(len(str(datadecoded["channel"][self.readchannel]["RDS"]["images"])))
                    dpg.set_value('transferateimagesoncchannelplot', [tfrpx, imcctfrpy])

                    try:
                        if datadecoded["channel"][self.readchannel]["RDS"] != self.RDS:
                            self.RDS = datadecoded["channel"][self.readchannel]["RDS"]
                            rdshow = threading.Thread(target=self.RDSshow)
                            rdshow.start()

                            dpg.configure_item("ServerListener", default_value="Listener: " + str(datadecoded["serverinfo"]["Listener"]) + " Users")
                            dpg.configure_item("ServerNamedisp", default_value="Server: " + str(datadecoded["serverinfo"]["RDS"]["ServerName"]))
                            dpg.configure_item("ServerDescdisp", default_value="Description: " + str(datadecoded["serverinfo"]["RDS"]["ServerDesc"]))

                    except:
                        pass

                    if self.firstrun:
                        decodecodec = datadecoded["channel"][self.readchannel]["RDS"]["ContentInfo"]["Codec"]

                        if decodecodec.upper() == "OPUS":
                            opus_decoder = OpusDecoder()
                            opus_decoder.set_channels(self.RDS["ContentInfo"]["channel"])
                            opus_decoder.set_sampling_frequency(self.RDS["ContentInfo"]["samplerates"])

                        streamoutput = self.paudio.open(format=pyaudio.paInt16, channels=self.RDS["ContentInfo"]["channel"], rate=self.RDS["ContentInfo"]["samplerates"], output=True, output_device_index=self.device_index_output)
                        evaluation_audio_X = np.fft.fftfreq(1024, 1.0 / self.RDS["ContentInfo"]["samplerates"])[:1024 // 2]

                        if len(datadecoded["channel"]) > 1:
                            channel_info = []
                            for i in range(1, len(datadecoded["channel"]) + 1):
                                channel_info.append(f'{i} {"[Encrypt]" if datadecoded["channel"][i]["Encrypt"] else "[No Encrypt]"} {datadecoded["channel"][i]["Station"]} ({datadecoded["channel"][i]["RDS"]["ContentInfo"]["Codec"]} {datadecoded["channel"][i]["RDS"]["ContentInfo"]["bitrate"] / 1000}Kbps {datadecoded["channel"][i]["RDS"]["AudioMode"]})')
                            dpg.configure_item("mediachannelselect", show=True, items=channel_info)

                        dpg.configure_item("morerdsbutton", show=True)
                        dpg.configure_item("serverinfobutton", show=True)
                        dpg.configure_item("logostatus", show=True)

                        try:
                            if self.RDS["images"]["logo"]["lazy"] and not self.ccthreadlogorecisworking:
                                if not self.RDS["images"]["logo"]["contents"] == b'' or \
                                        self.RDS["images"]["logo"]["part"]["total"] == \
                                        self.RDS["images"]["logo"]["part"]["current"] or \
                                        self.RDS["images"]["logo"]["part"]["current"] > 0:
                                    self.ccthreadlogorecisworking = True
                                    logoreciveprocessingthread = threading.Thread(target=self.RDSlogorecivelazy)
                                    logoreciveprocessingthread.start()
                            else:
                                if not self.RDS["images"]["logo"]["lazy"]:
                                    dpg.set_value("station_logo", CV22DPG(
                                        cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"]["contents"], np.uint8),
                                                     cv2.IMREAD_COLOR)))
                                    dpg.configure_item("station_logo_config", show=True)
                                    dpg.configure_item("logostatus", show=False)
                        except:
                            dpg.configure_item("station_logo_config", show=False)
                        dpg.configure_item("disconnectbutton", show=True)
                        dpg.configure_item("RDSPI", default_value=f"PI: {hex(self.RDS['PI'])[2:].upper()}")
                        if self.firststart and len(datadecoded["channel"]) > 1:
                            self.readchannel = datadecoded["mainchannel"]
                            dpg.configure_item("mediachannelselect", show=True, default_value=channel_info[self.readchannel - 1])
                        elif self.firststart:
                            self.readchannel = datadecoded["mainchannel"]
                        # check if channel is encrypted
                        if datadecoded["channel"][self.readchannel]["Encrypt"]:
                            dpg.configure_item("requestpasswordpopup", show=True)
                            dpg.configure_item("serverstatus", default_value='connected', color=(0, 255, 0))
                            self.ccisencrypt = True
                        else:
                            dpg.configure_item("serverstatus", default_value='connected --Kbps (----)', color=(0, 255, 0))

                        self.firstrun = False
                        self.firststart = False

                    if not self.firstrun:
                        data = datadecoded["channel"][self.readchannel]["Content"]

                        if len(data) == 0:
                            dpg.configure_item("serverstatus", default_value=f'connected but no audio', color=(255, 0, 0))

                        if self.ccisdecryptpassword and self.ccisencrypt:
                            try:
                                # decrypt data
                                encryptdata = data.split(b'|||||')[0]
                                salt = data.split(b'|||||')[1]
                                iv = data.split(b'|||||')[2]

                                data = decrypt_data(encryptdata, self.ccdecryptpassword, salt, iv)

                                if data == b'':
                                    self.ccisdecrypt = False
                                    self.ccdecryptpassword = None
                                else:
                                    self.ccisdecrypt = True
                            except:
                                dpg.configure_item("serverstatus", default_value="Decrypt Error", color=(255, 0, 0))

                        if self.ccisdecrypt or not self.ccisencrypt:
                            codecbytesconunt += len(data)

                            if decodecodec.upper() == "OPUS":
                                decoded_pcm = opus_decoder.decode(memoryview(bytearray(data)))
                            else: # pcm
                                decoded_pcm = data

                            # Check if the decoded PCM is empty or not
                            if len(decoded_pcm) > 0:
                                pcm_to_write = np.frombuffer(decoded_pcm, dtype=np.int16)

                                streamoutput.write(pcm_to_write.tobytes())

                                audioL = pcm_to_write[::2]
                                audioR = pcm_to_write[1::2]

                                Lnormalized_data = audioL * np.hanning(len(audioL))
                                Lfft_data = np.abs(np.fft.fft(Lnormalized_data))[:1024 // 2]

                                Rnormalized_data = audioR * np.hanning(len(audioR))
                                Rfft_data = np.abs(np.fft.fft(Rnormalized_data))[:1024 // 2]

                                dpg.set_value('audioinfoleftplot', [evaluation_audio_X, Lfft_data])
                                dpg.set_value('audioinforightplot', [evaluation_audio_X, Rfft_data])

                            else:
                                print("Decoded PCM is empty")

                        if len(adcctfrpy) > 250:
                            adcctfrpy.pop(0)
                        adcctfrpy.append(len(datadecoded["channel"][self.readchannel]["Content"]))
                        dpg.set_value('transferateaudiodataoncchannelplot', [tfrpx, adcctfrpy])

                    bytesconunt_frame += 1

                else:
                    SBT.join()
                    streamoutput.close()
                    if self.cprotocol == "TCP":
                        socket.close()
                    elif self.cprotocol == "ZeroMQ":
                        try:
                            message = socket.recv(zmq.NOBLOCK)
                            if message is None:
                                break  # No more messages
                            # Process the received message if needed
                            print(f"Received message: {message.decode()}")
                        except zmq.error.ZMQError as e:
                            if e.errno == zmq.EAGAIN:
                                break  # No more messages
                            else:
                                raise
                        socket.close()
                    else:
                        socket.close()
                    self.disconnectserver()
                    break
            except Exception as e:
                if str(e) == "An error occurred while decoding an Opus-encoded packet: corrupted stream":
                    dpg.configure_item("serverstatus", default_value="Unable to decode audio data", color=(255, 0, 0))
                else:
                    print("connection lost", e)
                    try:
                        streamoutput.close()
                    except:
                        pass
                    socket.close()
                    if self.cprotocol == "TCP":
                        socket.close()
                    elif self.cprotocol == "ZeroMQ":
                        try:
                            message = socket.recv(zmq.NOBLOCK)
                            if message is None:
                                break  # No more messages
                            # Process the received message if needed
                            print(f"Received message: {message.decode()}")
                        except zmq.error.ZMQError as e:
                            if e.errno == zmq.EAGAIN:
                                break  # No more messages
                            else:
                                raise
                        socket.close()
                    else:
                        socket.close()
                    self.disconnectserver()

                    break

    def init(self):
        if self.config["debug"]["hideconsole"] == "true":
            ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)

        ctypes.CDLL("opus.dll")

        dpg.create_context()
        dpg.create_viewport(title=f'IDRB Client v1.6.2 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48))  # set viewport window
        dpg.setup_dearpygui()
        # -------------- add code here --------------
        noimage_texture_data = []
        for i in range(0, 128 * 128):
            noimage_texture_data.append(20 / 255)
            noimage_texture_data.append(0)
            noimage_texture_data.append(20 / 255)
            noimage_texture_data.append(20 / 255)

        with dpg.texture_registry():
            dpg.add_raw_texture(128, 128, noimage_texture_data, tag="station_logo", format=dpg.mvFormat_Float_rgb)
            width, height, channels, data = dpg.load_image("IDRBlogo.png")

            dpg.add_static_texture(width=512, height=256, default_value=data, tag="app_logo")
            dpg.add_static_texture(width=512, height=256, default_value=data, tag="app_logo_background")

        with dpg.window(no_background=True, no_title_bar=True, no_move=True, no_resize=True, tag="backgroundviewportlogo"):
            dpg.add_image("app_logo_background")
            dpg.add_text("ThaiSDR Solutions", pos=(230, 230))

        appcomponent.window(self)
        appcomponent.menubar(self)

        num_devices = self.paudio.get_device_count()
        output_devices = []
        for i in range(num_devices):
            device_info = self.paudio.get_device_info_by_index(i)
            if device_info['maxOutputChannels'] > 0:
                output_devices.append(device_info['name'])

        dpg.configure_item("selectaudiooutputdevicecombo", items=output_devices, default_value=self.config["audio"]["device"])
        dpg.configure_item("buffersizeintinput", default_value=int(self.config["network"]["buffersize"]))

        # -------------------------------------------
        dpg.show_viewport()
        # Start a separate thread for a task
        self.thread_stop_event = threading.Event()

        while dpg.is_dearpygui_running():
            self.render()
            dpg.render_dearpygui_frame()

        # Signal the thread to stop and wait for it to finish
        self.thread_stop_event.set()
        dpg.destroy_context()

    def render(self):
        # insert here any code you would like to run in the render loop
        # you can manually stop by using stop_dearpygui() or self.exit()
        try:
            dpg.fit_axis_data("x_axis")
            dpg.fit_axis_data("y_axis1")
            dpg.fit_axis_data("y_axis2")
            dpg.fit_axis_data("y_axis3")

            dpg.fit_axis_data("x_axis_1")

            dpg.configure_item("backgroundviewportlogo", pos=(dpg.get_viewport_width() - 550, dpg.get_viewport_height() - 300))
        except:
            pass

    def exit(self):
        dpg.destroy_context()



app = App()
app.init()
