# This file is part of Gajim.
#
# SPDX-License-Identifier: GPL-3.0-only

from __future__ import annotations

import logging
from concurrent.futures import Future
from functools import partial
from pathlib import Path

from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk

from gajim.common import app
from gajim.common.enum import AudioPlayerState
from gajim.common.i18n import _
from gajim.common.multiprocess.audio_preview import extract_audio_properties
from gajim.common.util.text import format_duration

from gajim.gtk.audio_player import AudioPlayer
from gajim.gtk.preview.audio_waveform_navigator import AudioWaveformNavigator
from gajim.gtk.preview.misc import LoadingBox  # noqa: F401 # type: ignore
from gajim.gtk.util.classes import SignalManager
from gajim.gtk.util.misc import get_ui_string

log = logging.getLogger("gajim.gtk.preview.audio")


@Gtk.Template.from_string(string=get_ui_string("preview/audio.ui"))
class AudioPreviewWidget(Gtk.Box, SignalManager):
    __gtype_name__ = "AudioPreviewWidget"

    __gsignals__ = {
        "display-error": (
            GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
            None,
            (),
        )
    }

    _open_folder_button: Gtk.Button = Gtk.Template.Child()
    _save_as_button: Gtk.Button = Gtk.Template.Child()
    _stack: Gtk.Stack = Gtk.Template.Child()
    _speed_bar_adj: Gtk.Adjustment = Gtk.Template.Child()
    _drawing_box: Gtk.Box = Gtk.Template.Child()
    _progress_label: Gtk.Label = Gtk.Template.Child()
    _control_popover: Gtk.Popover = Gtk.Template.Child()
    _control_box: Gtk.Box = Gtk.Template.Child()
    _rewind_button: Gtk.Button = Gtk.Template.Child()
    _play_pause_button: Gtk.Button = Gtk.Template.Child()
    _play_icon: Gtk.Image = Gtk.Template.Child()
    _forward_button: Gtk.Button = Gtk.Template.Child()
    _speed_dec_button: Gtk.Button = Gtk.Template.Child()
    _speed_menubutton: Gtk.MenuButton = Gtk.Template.Child()
    _speed_label: Gtk.Label = Gtk.Template.Child()
    _speed_inc_button: Gtk.Button = Gtk.Template.Child()
    _speed_popover: Gtk.Popover = Gtk.Template.Child()
    _speed_bar: Gtk.Scale = Gtk.Template.Child()

    def __init__(
        self,
        audio_player: AudioPlayer,
        filename: str,
        file_size: int,
        orig_path: Path,
    ) -> None:
        Gtk.Box.__init__(self)
        SignalManager.__init__(self)

        self._audio_player = audio_player
        self._orig_path = orig_path
        self._filename = filename
        self._file_size = file_size

        self._id = hash(self._orig_path) % 100_000

        self._preview_state = self._audio_player.get_audio_state(self._id)

        # UI
        self.set_valign(Gtk.Align.CENTER)
        self._new_voice_message_track = False
        self._seek_step = 10e9  # 10 seconds

        self._open_folder_button.set_action_target_value(
            GLib.Variant("s", str(self._orig_path))
        )
        self._save_as_button.set_action_target_value(
            GLib.Variant("s", str(self._orig_path))
        )

        self._speed_bar_adj.set_value(self._preview_state.speed)
        self._set_playback_speed(self._preview_state.speed)

        self._speed_bar.add_mark(0.25, Gtk.PositionType.BOTTOM, "0.25")
        self._speed_bar.add_mark(0.5, Gtk.PositionType.BOTTOM, "")
        self._speed_bar.add_mark(0.75, Gtk.PositionType.BOTTOM, "")
        self._speed_bar.add_mark(1, Gtk.PositionType.BOTTOM, "1.0")
        self._speed_bar.add_mark(1.25, Gtk.PositionType.BOTTOM, "")
        self._speed_bar.add_mark(1.5, Gtk.PositionType.BOTTOM, "1.5")
        self._speed_bar.add_mark(1.75, Gtk.PositionType.BOTTOM, "")
        self._speed_bar.add_mark(2, Gtk.PositionType.BOTTOM, "2")
        self._connect(self._speed_bar, "change-value", self._on_speed_change)

        self._connect(self._speed_dec_button, "clicked", self._on_speed_dec_clicked)
        self._connect(self._speed_inc_button, "clicked", self._on_speed_inc_clicked)

        self._connect(self._rewind_button, "clicked", self._on_rewind_clicked)
        self._connect(self._forward_button, "clicked", self._on_forward_clicked)

        gesture_timestamp_click = Gtk.GestureClick(
            button=Gdk.BUTTON_PRIMARY, propagation_phase=Gtk.PropagationPhase.TARGET
        )
        self._connect(
            gesture_timestamp_click, "pressed", self._on_timestamp_label_clicked
        )
        self._progress_label.add_controller(gesture_timestamp_click)

        self._visualizer = AudioWaveformNavigator(
            preview_state=self._preview_state, width=270
        )
        self._drawing_box.append(self._visualizer)
        self._connect(self._visualizer, "seeked", self._on_seek)

        self._progress_id = None
        self._play_pause_button.add_css_class("circular")
        self._connect(self._play_pause_button, "clicked", self._on_play_clicked)

        if self._orig_path.is_file():
            # In case of the voice message recorder, start with no existing file
            if self._preview_state.duration == 0 or not self._preview_state.samples:
                self._get_audio_properties()
            else:
                self._display_audio_preview()

    @property
    def id(self) -> int:
        return self._id

    def run_destroy(self) -> None:
        self._disconnect_all()
        self._audio_player.stop(self._id)
        self._visualizer.run_destroy()
        del self._audio_player
        app.check_finalize(self)

    def sample_voice_message(self, audio_path: Path) -> None:
        self._orig_path = audio_path
        self._preview_state.position = 0
        self._new_voice_message_track = True
        self._get_audio_properties()

    def _get_audio_properties(self) -> None:
        assert self._orig_path is not None

        try:
            future = app.process_pool.submit(extract_audio_properties, self._orig_path)
            future.add_done_callback(
                partial(GLib.idle_add, self._get_audio_properties_finished)
            )
        except Exception as error:
            log.warning(
                "Creating audio preview failed for: %s %s", self._orig_path, error
            )

    def _get_audio_properties_finished(
        self, future: Future[tuple[list[tuple[float, float]], int]]
    ) -> bool:
        try:
            samples, duration = future.result()
        except Exception as error:
            log.exception(
                "Creating audio preview failed for: %s %s", self._orig_path, error
            )
            self.emit("display-error")
        else:
            self._preview_state.samples = samples
            self._preview_state.duration = duration
            log.info("State: %s", self._preview_state)
            self._display_audio_preview()

        return GLib.SOURCE_REMOVE

    def _set_preview_state_position(self, position: float) -> None:
        position = max(0.0, position)
        position = min(position, self._preview_state.duration)
        self._preview_state.position = position

    def _convert_pos_to_text(self, current_position: float, duration: float):
        dur_str = format_duration(duration, duration)
        ltr_char = "\u200e"

        if self._preview_state.is_timestamp_positive:
            cur_str = f"{format_duration(current_position, duration)}"
            cur_str = f"{cur_str}/{dur_str}"
        else:
            cur_str = f"{format_duration(duration - current_position, duration)}"
            cur_str = f"{ltr_char}-{cur_str}/{dur_str}"

        return cur_str

    def _on_audio_playback_progressed(
        self, _obj: AudioPlayer, preview_id: int, _state: AudioPlayerState
    ) -> None:
        if preview_id != self.id:
            return
        self._update_ui_from_state()

    def _on_audio_playback_changed(
        self, _obj: AudioPlayer, preview_id: int, state: int
    ) -> None:
        if preview_id != self.id:
            self._disconnect_object(self._audio_player)
            self._play_icon.set_from_icon_name("lucide-play-symbolic")
            return

        if state == AudioPlayerState.PLAYING.value:
            self._play_icon.set_from_icon_name("lucide-pause-symbolic")
            self._play_pause_button.set_tooltip_text(_("Pause"))
        else:
            self._play_icon.set_from_icon_name("lucide-play-symbolic")
            self._play_pause_button.set_tooltip_text(_("Play"))

        self._update_ui_from_state()

    def _update_ui_from_state(self):
        self._visualizer.update()
        self._progress_label.set_text(
            self._convert_pos_to_text(
                self._preview_state.position, self._preview_state.duration
            )
        )

    def _on_play_clicked(self, _button: Gtk.Button) -> None:
        if self._audio_player.preview_id != self._id or self._new_voice_message_track:
            self._new_voice_message_track = False
            self._connect(
                self._audio_player,
                "audio-playback-progressed",
                self._on_audio_playback_progressed,
            )
            self._connect(
                self._audio_player,
                "audio-playback-changed",
                self._on_audio_playback_changed,
            )
            self._audio_player.play_audio_file(self._orig_path, self._id)
        else:
            self._audio_player.toggle_playback()

    def _update_timestamp_label(self) -> None:
        cur = self._preview_state.position
        dur = self._preview_state.duration

        dur_str = format_duration(dur, dur)
        ltr_char = "\u200e"

        if self._preview_state.is_timestamp_positive:
            cur_str = f"{format_duration(cur, dur)}"
            self._progress_label.set_text(f"{cur_str}/{dur_str}")
        else:
            cur_str = f"{format_duration(dur - cur, dur)}"
            self._progress_label.set_text(f"{ltr_char}-{cur_str}/{dur_str}")

    def _on_timestamp_label_clicked(
        self,
        gesture_click: Gtk.GestureClick,
        _n_press: int,
        _x: float,
        _y: float,
    ) -> None:
        gesture_click.set_state(Gtk.EventSequenceState.CLAIMED)
        self._preview_state.is_timestamp_positive = (
            not self._preview_state.is_timestamp_positive
        )
        self._update_timestamp_label()

    def _display_audio_preview(self) -> None:
        self._visualizer.set_samples(self._preview_state.samples)
        self._update_ui_from_state()
        self._stack.set_visible_child_name("preview")

    def _on_seek(self, _widget: AudioWaveformNavigator, timestamp: float) -> None:
        self._set_preview_state_position(timestamp)
        self._update_ui_from_state()
        self._audio_player.set_playback_position(self._id, timestamp)

    def _set_playback_speed(self, speed: float) -> None:
        speed = max(0.25, speed)
        speed = min(speed, 2.0)

        self._preview_state.speed = speed
        self._speed_label.set_text(f"{speed:.2f}x")
        self._speed_bar_adj.set_value(speed)

        if self._audio_player.preview_id != self.id:
            return

        self._audio_player.set_playback_speed(speed)

    def _on_speed_change(
        self, _range: Gtk.Range, _scroll: Gtk.ScrollType, value: float
    ) -> None:
        self._set_playback_speed(value)

    def _on_speed_dec_clicked(self, _button: Gtk.Button) -> None:
        speed = self._preview_state.speed - self._speed_bar_adj.get_step_increment()
        self._set_playback_speed(speed)

    def _on_speed_inc_clicked(self, _button: Gtk.Button) -> None:
        speed = self._preview_state.speed + self._speed_bar_adj.get_step_increment()
        self._set_playback_speed(speed)

    def _change_playback_position_by_step(self, step: float) -> None:
        new_pos = self._preview_state.position + step
        new_pos = max(0.0, new_pos)
        new_pos = min(new_pos, self._preview_state.duration)

        if self._audio_player.preview_id != self.id:
            self._preview_state.position = new_pos
        else:
            self._audio_player.set_playback_position(self._id, new_pos)

        self._update_ui_from_state()

    def _on_rewind_clicked(self, _button: Gtk.Button) -> None:
        self._change_playback_position_by_step(-self._seek_step)

    def _on_forward_clicked(self, _button: Gtk.Button) -> None:
        self._change_playback_position_by_step(self._seek_step)
