#!/usr/bin/python3
# ZR6TG - Tom - 2022/06/23

# basic signals detection code ported from https://github.com/m0dts/QO-100-WB-Live-Tune

import asyncio
import pygame
import websockets
import os
import pygame.gfxdraw


# CONFIGURATION
FFT_URL = "wss://eshail.batc.org.uk/wb/fft" #official batc fft



COL_BG = (20, 29, 43)
COL_SPECTRUM = (217, 127, 43)
COL_TEXT = (224,224,224)

WIDTH = 800
HEIGHT = 480

class Graphics:
 
    def __init__(self, width, height):
        self.start_freq = 10490.5
        self.width =  width
        self.height = height
        self.x_tab = (self.width-50) /922
        self.font = pygame.font.SysFont('freesans', 20)
        self.bigfont = pygame.font.SysFont('freesans', 180)
        self.mediumfont = pygame.font.SysFont('freesans', 50)


    def align_symbolrate(self, width):
        if width < 0.002: return 0
        if width < 0.065: return 0.035
        if width < 0.086: return 0.066
        if width < 0.195: return 0.125
        if width < 0.277: return 0.250
        if width < 0.388: return 0.333
        if width < 0.700: return 0.500
        if width < 1.2: return 1.0
        if width < 1.6: return 1.5
        if width < 2.2: return 2
        
        return int(width)

    async def find_signals(self, fft_data):
        signals = []
        i = 0
        j = 0
        noise_level = 11000
        signal_threshold = 18000
        in_signal = False
        start_signal = 0
        end_signal = 0
        mid_signal = 0
        signal_strength = 0
        signal_bw = 0
        signal_freq = 0
        acc = 0
        acc_i = 0

        i = 2
        while i < len(fft_data):

            if in_signal == False:
                if (fft_data[i] + fft_data[i-1] + fft_data[i-2]) / 3 > signal_threshold:
                    in_signal = True
                    start_signal = i
            else:
                if (fft_data[i] + fft_data[i-1] + fft_data[i-2]) / 3 < signal_threshold:
                    in_signal = False
                    end_signal = i

                    acc = 0
                    acc_i = 0

                    j = int(start_signal + (0.3 * ( end_signal - start_signal )))

                    while ( j < start_signal + (0.8 * (end_signal - start_signal))):
                        acc = acc + fft_data[j]
                        acc_i = acc_i + 1
                        j+=1

                    if acc_i == 0:
                        in_signal = False
                        continue

                    signal_strength = acc / acc_i

                    # find real start
                    j = start_signal 
                    while (fft_data[j] - noise_level) < 0.75 * (signal_strength - noise_level):
                        start_signal = j
                        j+=1

                    # find real end
                    j = end_signal
                    end_signal_orig = j
                    while (fft_data[j] - noise_level) < 0.75 * (signal_strength - noise_level):                        
                        end_signal = j

                        if j <= 0:
                            end_signal = end_signal_orig
                            fake_end = True
                            break
                        j -= 1


                    mid_signal = start_signal + ((end_signal - start_signal)/2)
                    signal_freq = self.start_freq + (((mid_signal + 1) / (len(fft_data)) * 9))
                    signal_bw = self.align_symbolrate((end_signal - start_signal) * (9 / len(fft_data)))

                    if signal_bw >= 0.033:
                        signals.append({'start': start_signal, 'end' : end_signal, 'mid' : mid_signal, 'freq' : signal_freq, 'signal_strength' : signal_strength/255, 'signal_bw' : signal_bw})



            i += 1

        return signals


    async def update(self, window, fft):
        polygon_data = []
        fft_data = []
        x = 0

        while ( x < len(fft)-1 ):
            db = [fft[x],fft[x+1]]
            #y = int.from_bytes(db, 'little')/255
            fft_data.append(int.from_bytes(db, 'little'))
            polygon_data.append((25 + (x/2 * self.x_tab),480-int.from_bytes(db, 'little')/255))
            x += 2

        signals = await self.find_signals(fft_data)

        polygon_data.append((25,480))
        pygame.gfxdraw.filled_polygon(window, polygon_data,COL_SPECTRUM)
        pygame.gfxdraw.aapolygon(window, polygon_data,(197,244,103))

         

        for sig in signals:
            text_width, text_height = self.font.size(str(round(sig['freq'],2)))
            text = self.font.render(str(round(sig['freq'],2)) + "", True, COL_TEXT)
            window.blit(text, (25 + (int(sig['mid']) * self.x_tab) - text_width/2, 480 - int(sig['signal_strength']) - 60))
            text_width, text_height = self.font.size(str(int(sig['signal_bw'] * 1000)) + "Ks")

            text = self.font.render(str(int(sig['signal_bw'] * 1000)) + "Ks", True, COL_TEXT)
            window.blit(text, (25 + (int(sig['mid']) * self.x_tab) - text_width/2, 480 - int(sig['signal_strength']) - 40))

 
class Socket:
    def __init__(self):
        self.websocket = None

    async def startup(self):
        self.websocket = await websockets.connect(FFT_URL)

    async def updateFFT(self):
        return await self.websocket.recv()

async def main():
    
    window = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.mouse.set_visible(False)

    graphics = Graphics(WIDTH, HEIGHT)
    socket = Socket()
    await socket.startup()

    while True:

        fft = await socket.updateFFT()
        window.fill(COL_BG)
        await graphics.update(window, fft)
        pygame.display.flip()

 
 
if __name__ == "__main__":
    pygame.init()
    asyncio.run(main())
    pygame.quit()
