# -*- coding: utf-8 -*-
"""
Ejemplo 25: Grabar audio del sistema (loopback) o micro a MP3.

Detecta automaticamente el dispositivo loopback del dispositivo de salida
por defecto. Si no hay loopback, usa el microfono.

Al pulsar Ctrl+C se detiene y guarda el archivo MP3.

Uso:
  python 25_grabar_mp3.py                    (graba a grabacion.mp3)
  python 25_grabar_mp3.py mi_archivo.mp3     (graba a mi_archivo.mp3)

Requisitos: basskit con bassenc_mp3.dll
"""

import sys
import time
import ctypes
import signal
from basskit.dlls.bass import bass
from basskit.dlls.bassenc import bassenc
from basskit.dlls.addons import bassenc_mp3
from basskit.constantes.bass import (
	BASS_SAMPLE_FLOAT, BASS_DEVICE_LOOPBACK, BASS_DEVICE_DEFAULT,
)

# ─── Estado global ───────────────────────────────────────────────────
grabando = True
archivo_salida = "grabacion.mp3"


def signal_handler(sig, frame):
	global grabando
	grabando = False
	print("\n\nDeteniendo grabacion...")


def listar_dispositivos_grabacion():
	"""Lista dispositivos de grabacion incluyendo loopback."""
	from basskit.estructuras.bass import BASS_DEVICEINFO
	info = BASS_DEVICEINFO()
	i = 0
	dispositivos = []
	while bass.BASS_RecordGetDeviceInfo(i, ctypes.byref(info)):
		nombre = info.name.decode("utf-8", errors="replace") if info.name else "?"
		es_loopback = bool(info.flags & BASS_DEVICE_LOOPBACK)
		es_defecto = bool(info.flags & BASS_DEVICE_DEFAULT)
		habilitado = bool(info.flags & 1)
		dispositivos.append({
			"indice": i, "nombre": nombre,
			"loopback": es_loopback, "defecto": es_defecto,
			"habilitado": habilitado,
		})
		i += 1
	return dispositivos


def encontrar_dispositivo(tipo="loopback"):
	"""Encuentra el dispositivo loopback por defecto o micro por defecto."""
	devs = listar_dispositivos_grabacion()
	if tipo == "loopback":
		# Preferir el loopback marcado como defecto
		for d in devs:
			if d["loopback"] and d["defecto"] and d["habilitado"]:
				return d["indice"], d["nombre"]
		# Cualquier loopback habilitado
		for d in devs:
			if d["loopback"] and d["habilitado"]:
				return d["indice"], d["nombre"]
	else:
		# Micro por defecto
		for d in devs:
			if not d["loopback"] and d["defecto"] and d["habilitado"]:
				return d["indice"], d["nombre"]
		for d in devs:
			if not d["loopback"] and d["habilitado"]:
				return d["indice"], d["nombre"]
	return -1, ""


def main():
	global grabando, archivo_salida

	if len(sys.argv) > 1:
		archivo_salida = sys.argv[1]
	if not archivo_salida.endswith(".mp3"):
		archivo_salida += ".mp3"

	print("=== Grabador de Audio a MP3 ===\n")

	# Registrar handler de Ctrl+C
	signal.signal(signal.SIGINT, signal_handler)

	# Inicializar BASS (dispositivo 0 = sin sonido, solo para grabacion)
	bass.BASS_Init(0, 44100, 0, 0, None)

	# Listar dispositivos
	print("Dispositivos de grabacion disponibles:\n")
	devs = listar_dispositivos_grabacion()
	for d in devs:
		tipo = " [LOOPBACK]" if d["loopback"] else " [MICRO]"
		defecto = " *DEFECTO*" if d["defecto"] else ""
		hab = "" if d["habilitado"] else " (deshabilitado)"
		print(f"  {d['indice']}) {d['nombre']}{tipo}{defecto}{hab}")

	if not devs:
		print("Error: No se encontraron dispositivos de grabacion.")
		bass.BASS_Free()
		return

	# Dejar elegir al usuario
	print()
	while True:
		try:
			eleccion = input("Elige el numero de dispositivo: ").strip()
			fuente_idx = int(eleccion)
			# Verificar que existe
			dev_elegido = None
			for d in devs:
				if d["indice"] == fuente_idx:
					dev_elegido = d
					break
			if dev_elegido is None:
				print(f"  Dispositivo {fuente_idx} no existe. Intenta de nuevo.")
				continue
			if not dev_elegido["habilitado"]:
				print(f"  Dispositivo {fuente_idx} esta deshabilitado. Intenta de nuevo.")
				continue
			fuente_nombre = dev_elegido["nombre"]
			tipo_str = "LOOPBACK" if dev_elegido["loopback"] else "MICROFONO"
			print(f"\n  Seleccionado: [{tipo_str}] {fuente_nombre}")
			break
		except ValueError:
			print("  Introduce un numero valido.")
		except EOFError:
			print("\nCancelado.")
			bass.BASS_Free()
			return

	# Inicializar dispositivo de grabacion
	if not bass.BASS_RecordInit(fuente_idx):
		print(f"Error al inicializar grabacion: {bass.BASS_ErrorGetCode()}")
		bass.BASS_Free()
		return

	# Crear stream de grabacion (44100 Hz, stereo, float)
	frecuencia = 44100
	canales = 2
	rec = bass.BASS_RecordStart(frecuencia, canales, BASS_SAMPLE_FLOAT, None, None)
	if rec == 0:
		# Intentar mono si stereo falla
		canales = 1
		rec = bass.BASS_RecordStart(frecuencia, canales, BASS_SAMPLE_FLOAT, None, None)
	if rec == 0:
		print(f"Error al iniciar grabacion: {bass.BASS_ErrorGetCode()}")
		bass.BASS_Free()
		return

	# Iniciar encoder MP3 sobre el canal de grabacion
	opciones = b"--preset standard"  # LAME preset: ~192 kbps VBR
	archivo_b = archivo_salida.encode("utf-8")

	encoder = bassenc_mp3.BASS_Encode_MP3_StartFile(rec, opciones, 0, archivo_b)
	if encoder == 0:
		print(f"Error al iniciar encoder MP3: {bass.BASS_ErrorGetCode()}")
		bass.BASS_StreamFree(rec)
		bass.BASS_Free()
		return

	print(f"\nGrabando a: {archivo_salida}")
	print(f"Fuente: {fuente_nombre} ({frecuencia} Hz, {'stereo' if canales == 2 else 'mono'})")
	print(f"Formato: MP3 (LAME --preset standard)")
	print("\nPresiona Ctrl+C para detener y guardar.\n")

	# Bucle de grabacion - IMPORTANTE: hay que leer datos del canal
	# para que BASSenc los intercepte y los envie al encoder MP3.
	inicio = time.time()
	buf = (ctypes.c_byte * 32768)()
	while grabando:
		# Leer datos del canal de grabacion -> BASSenc los captura y codifica
		disponible = bass.BASS_ChannelGetData(rec, ctypes.byref(buf), len(buf))
		if disponible == 0xFFFFFFFF:
			err = bass.BASS_ErrorGetCode()
			if err == 41:  # BASS_ERROR_ENDED
				break
			# Sin datos aun, esperar un poco
			time.sleep(0.05)
			continue
		transcurrido = time.time() - inicio
		mins = int(transcurrido) // 60
		secs = int(transcurrido) % 60
		print(f"\r  Grabando... {mins:02d}:{secs:02d}", end="", flush=True)
		time.sleep(0.05)

	# Detener
	print(f"\n\nDeteniendo...")
	bassenc.BASS_Encode_Stop(encoder)
	bass.BASS_ChannelStop(rec)
	bass.BASS_StreamFree(rec)
	bass.BASS_RecordFree()
	bass.BASS_Free()

	transcurrido = time.time() - inicio
	print(f"Duracion: {int(transcurrido)//60:02d}:{int(transcurrido)%60:02d}")
	print(f"Guardado: {archivo_salida}")
	print("Fin.")


if __name__ == "__main__":
	main()
