import gradio as gr
import numpy as np
import io
from scipy.io import wavfile
import speech_recognition as sr
import soundfile as sf

# Morse code dictionary (ITU standard)
MORSE_CODE_DICT = {
    'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
    'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
    'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
    'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
    'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--',
    '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..',
    '9': '----.', '0': '-----', ' ': '/'
}
MORSE_TO_CHAR = {v: k for k, v in MORSE_CODE_DICT.items()}

# Morse code timing (in seconds)
DIT_DURATION = 0.1  # 100ms for dit
DAH_DURATION = 3 * DIT_DURATION  # 300ms for dah
SPACE_DURATION = 7 * DIT_DURATION  # 700ms for word space
CHAR_SPACE = DIT_DURATION  # Space between characters
SAMPLE_RATE = 44100  # Standard audio sample rate

# Decode Morse from audio (simplified)
def decode_morse_from_audio(audio_data):
    if audio_data is None:
        return "", ""
    sample_rate, data = audio_data
    if len(data.shape) > 1:
        data = data.mean(axis=1)
    data = data / np.max(np.abs(data))
    
    threshold = 0.1
    signal = data > threshold
    morse_code, decoded_text = "", ""
    i = 0
    
    while i < len(signal) - int(SAMPLE_RATE * DIT_DURATION):
        if signal[i]:
            start = i
            while i < len(signal) and signal[i]:
                i += 1
            duration = (i - start) / sample_rate
            morse_code += "-" if duration >= DAH_DURATION else "."
        else:
            start = i
            while i < len(signal) and not signal[i]:
                i += 1
            pause = (i - start) / sample_rate
            if pause >= SPACE_DURATION and morse_code:
                decoded_text += " "
                morse_code = ""
            elif pause >= DIT_DURATION and morse_code:
                decoded_text += MORSE_TO_CHAR.get(morse_code, "?")
                morse_code = ""
        i += 1
    
    if morse_code:
        decoded_text += MORSE_TO_CHAR.get(morse_code, "?")
    return morse_code, decoded_text.strip()

# Convert text to Morse code
def text_to_morse(text):
    text = text.upper()
    morse = " ".join(MORSE_CODE_DICT.get(char, "?") for char in text if char in MORSE_CODE_DICT)
    return morse

# Generate Morse code audio from text
def generate_morse_audio(morse):
    audio = []
    frequency = 750  # Hz for Morse tone
    
    for symbol in morse.split():
        if symbol == "/":
            audio.extend([0] * int(SAMPLE_RATE * SPACE_DURATION))
        else:
            for char in symbol:
                duration = DAH_DURATION if char == "-" else DIT_DURATION
                t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
                tone = 0.5 * np.sin(2 * np.pi * frequency * t)
                audio.extend(tone)
                audio.extend([0] * int(SAMPLE_RATE * DIT_DURATION))  # Space between dits/dahs
            audio.extend([0] * int(SAMPLE_RATE * CHAR_SPACE))  # Space between characters
    
    audio = np.array(audio, dtype=np.float32)
    buffer = io.BytesIO()
    sf.write(buffer, audio, SAMPLE_RATE, format="wav")
    buffer.seek(0)
    return buffer

# Speech to text
def speech_to_text(audio):
    recognizer = sr.Recognizer()
    with sr.AudioFile(audio) as source:
        audio_data = recognizer.record(source)
    try:
        return recognizer.recognize_google(audio_data)
    except sr.UnknownValueError:
        return "Could not understand audio"
    except sr.RequestError:
        return "Speech recognition service unavailable"

# Highlight alphabet in UI
def generate_alphabet_html(decoded_text):
    html = "<div style='font-family: monospace; font-size: 16px;'>"
    for char in MORSE_CODE_DICT.keys():
        color = "red" if char in decoded_text.upper() else "black"
        html += f"<span style='color: {color}; margin: 5px;'>{char}: {MORSE_CODE_DICT[char]}</span>"
        if char in "AEIMQUZ":
            html += "<br>"
    html += "</div>"
    return html

# Combined processing function
def process_input(text=None, speech=None, audio=None):
    morse, decoded_text, audio_output = "", "", None
    
    if text:  # Text input
        morse = text_to_morse(text)
        decoded_text = text
        audio_output = generate_morse_audio(morse)
    
    elif speech:  # Speech input
        text = speech_to_text(speech)
        morse = text_to_morse(text)
        decoded_text = text
        audio_output = generate_morse_audio(morse)
    
    elif audio:  # Live audio input
        morse, decoded_text = decode_morse_from_audio(audio)
    
    alphabet_html = generate_alphabet_html(decoded_text)
    return morse, decoded_text, alphabet_html, audio_output

# Gradio UI with Blocks
with gr.Blocks(title="Morse Code Decoder & Generator") as demo:
    gr.Markdown("# Morse Code Decoder & Generator")
    gr.Markdown("Decode live Morse audio, or generate Morse from text/speech!")
    
    with gr.Tab("Decode Live Audio"):
        audio_input = gr.Audio(source="microphone", type="numpy", streaming=True, label="Live Audio Input")
        with gr.Row():
            with gr.Column():
                morse_output = gr.Textbox(label="Detected Morse Code", interactive=False)
                text_output = gr.Textbox(label="Decoded Text", interactive=False)
            alphabet_display = gr.HTML(label="Morse Alphabet (Highlighted)")
        
        audio_input.stream(
            fn=process_input,
            inputs=[audio_input],
            outputs=[morse_output, text_output, alphabet_display, gr.Audio(visible=False)],
        )
    
    with gr.Tab("Generate from Text"):
        text_input = gr.Textbox(label="Enter Text", placeholder="Type here...")
        generate_btn = gr.Button("Generate Morse")
        with gr.Row():
            with gr.Column():
                morse_gen_output = gr.Textbox(label="Morse Code", interactive=False)
                text_gen_output = gr.Textbox(label="Original Text", interactive=False)
            gen_alphabet_display = gr.HTML(label="Morse Alphabet (Highlighted)")
        audio_playback = gr.Audio(label="Morse Audio Playback", interactive=False)
        
        generate_btn.click(
            fn=process_input,
            inputs=[text_input],
            outputs=[morse_gen_output, text_gen_output, gen_alphabet_display, audio_playback]
        )
    
    with gr.Tab("Generate from Speech"):
        speech_input = gr.Audio(source="microphone", type="filepath", label="Speak Your Text")
        speech_btn = gr.Button("Convert Speech to Morse")
        with gr.Row():
            with gr.Column():
                morse_speech_output = gr.Textbox(label="Morse Code", interactive=False)
                text_speech_output = gr.Textbox(label="Transcribed Text", interactive=False)
            speech_alphabet_display = gr.HTML(label="Morse Alphabet (Highlighted)")
        speech_audio_playback = gr.Audio(label="Morse Audio Playback", interactive=False)
        
        speech_btn.click(
            fn=process_input,
            inputs=[speech_input],
            outputs=[morse_speech_output, text_speech_output, speech_alphabet_display, speech_audio_playback]
        )

# Launch the app
demo.launch()