// Copyright (c)  2024  Xiaomi Corporation
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'package:flutter/material.dart';

import 'package:audioplayers/audioplayers.dart';
import 'package:sherpa_onnx/sherpa_onnx.dart' as sherpa_onnx;

import './model.dart';
import './utils.dart';

class TtsScreen extends StatefulWidget {
  const TtsScreen({super.key});

  @override
  State<TtsScreen> createState() => _TtsScreenState();
}

class _TtsScreenState extends State<TtsScreen> {
  late final TextEditingController _controller_text_input;
  late final TextEditingController _controller_sid;
  late final TextEditingController _controller_hint;
  late final AudioPlayer _player;
  String _title = 'Text to speech';
  String _lastFilename = '';
  bool _isInitialized = false;
  int _maxSpeakerID = 0;
  double _speed = 1.0;

  sherpa_onnx.OfflineTts? _tts;

  @override
  void initState() {
    _controller_text_input = TextEditingController();
    _controller_hint = TextEditingController();
    _controller_sid = TextEditingController(text: '0');

    super.initState();
  }

  Future<void> _init() async {
    if (!_isInitialized) {
      sherpa_onnx.initBindings();

      _tts?.free();
      _tts = await createOfflineTts();

      _player = AudioPlayer();

      _isInitialized = true;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(_title),
        ),
        body: Padding(
          padding: EdgeInsets.all(10),
          child: Column(
            // mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextField(
                  decoration: InputDecoration(
                    labelText: "Speaker ID (0-$_maxSpeakerID)",
                    hintText: 'Please input your speaker ID',
                  ),
                  keyboardType: TextInputType.number,
                  maxLines: 1,
                  controller: _controller_sid,
                  onTapOutside: (PointerDownEvent event) {
                    FocusManager.instance.primaryFocus?.unfocus();
                  },
                  inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly]),
              Slider(
                // decoration: InputDecoration(
                //   labelText: "speech speed",
                // ),
                label: "Speech speed ${_speed.toStringAsPrecision(2)}",
                min: 0.5,
                max: 3.0,
                divisions: 25,
                value: _speed,
                onChanged: (value) {
                  setState(() {
                    _speed = value;
                  });
                },
              ),
              const SizedBox(height: 5),
              TextField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Please enter your text here',
                ),
                maxLines: 5,
                controller: _controller_text_input,
                onTapOutside: (PointerDownEvent event) {
                  FocusManager.instance.primaryFocus?.unfocus();
                },
              ),
              const SizedBox(height: 5),
              Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
                OutlinedButton(
                  child: Text("Generate"),
                  onPressed: () async {
                    await _init();
                    await _player?.stop();

                    setState(() {
                      _maxSpeakerID = _tts?.numSpeakers ?? 0;
                      if (_maxSpeakerID > 0) {
                        _maxSpeakerID -= 1;
                      }
                    });

                    if (_tts == null) {
                      _controller_hint.value = TextEditingValue(
                        text: 'Failed to initialize tts',
                      );
                      return;
                    }

                    _controller_hint.value = TextEditingValue(
                      text: '',
                    );

                    final text = _controller_text_input.text.trim();
                    if (text == '') {
                      _controller_hint.value = TextEditingValue(
                        text: 'Please first input your text to generate',
                      );
                      return;
                    }

                    final sid = int.tryParse(_controller_sid.text.trim()) ?? 0;

                    final stopwatch = Stopwatch();
                    stopwatch.start();
                    final audio = _tts!.generate(text: text, sid: sid, speed: _speed);
                    final suffix = '-sid-$sid-speed-${_speed.toStringAsPrecision(2)}';
                    final filename = await generateWaveFilename(suffix);

                    final ok = sherpa_onnx.writeWave(
                      filename: filename,
                      samples: audio.samples,
                      sampleRate: audio.sampleRate,
                    );

                    if (ok) {
                      stopwatch.stop();
                      double elapsed = stopwatch.elapsed.inMilliseconds.toDouble();

                      double waveDuration = audio.samples.length.toDouble() / audio.sampleRate.toDouble();

                      _controller_hint.value = TextEditingValue(
                        text: 'Saved to\n$filename\n'
                            'Elapsed: ${(elapsed / 1000).toStringAsPrecision(4)} s\n'
                            'Wave duration: ${waveDuration.toStringAsPrecision(4)} s\n'
                            'RTF: ${(elapsed / 1000).toStringAsPrecision(4)}/${waveDuration.toStringAsPrecision(4)} '
                            '= ${(elapsed / 1000 / waveDuration).toStringAsPrecision(3)} ',
                      );
                      _lastFilename = filename;

                      await _player?.play(DeviceFileSource(_lastFilename));
                    } else {
                      _controller_hint.value = TextEditingValue(
                        text: 'Failed to save generated audio',
                      );
                    }
                  },
                ),
                const SizedBox(width: 5),
                OutlinedButton(
                  child: Text("Clear"),
                  onPressed: () {
                    _controller_text_input.value = TextEditingValue(
                      text: '',
                    );

                    _controller_hint.value = TextEditingValue(
                      text: '',
                    );
                  },
                ),
                const SizedBox(width: 5),
                OutlinedButton(
                  child: Text("Play"),
                  onPressed: () async {
                    if (_lastFilename == '') {
                      _controller_hint.value = TextEditingValue(
                        text: 'No generated wave file found',
                      );
                      return;
                    }
                    await _player?.stop();
                    await _player?.play(DeviceFileSource(_lastFilename));
                    _controller_hint.value = TextEditingValue(
                      text: 'Playing\n$_lastFilename',
                    );
                  },
                ),
                const SizedBox(width: 5),
                OutlinedButton(
                  child: Text("Stop"),
                  onPressed: () async {
                    await _player?.stop();
                    _controller_hint.value = TextEditingValue(
                      text: '',
                    );
                  },
                ),
              ]),
              const SizedBox(height: 5),
              TextField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Logs will be shown here.\n'
                      'The first run is slower due to model initialization.',
                ),
                maxLines: 6,
                controller: _controller_hint,
                readOnly: true,
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _tts?.free();
    super.dispose();
  }
}
