#!/usr/bin/python3
import os, gi
from threading import Event, Thread
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, GLib

# SoundBraid - Simple audio mixer <https://codeberg.org/JWMKit/soundbraid>
# Copyright © 2021-2023 Calvin Kent McNabb <apps.jwmkit@gmail.com>

# This program is free software:; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


def mute_cmd(button, name):
    os.system('sndioctl -q {}=0'.format(name.split('=')[0]))


def make_label(label):
    if '/' in label:
        label = label.split('/')[1]
    if '.' in label:
        label = label.split('.')[0]
    return label.replace('output', 'Master').capitalize()


class SndioMixer(Gtk.Window):

    def __init__(self):
        self.sndio_streams = os.popen('sndioctl').read().strip().split()
        self.sndio_streams = [s for s in self.sndio_streams if '.level=' in s]
        self.stream_labels = []
        Gtk.Window.__init__(self, title="SoundBraid")
        icons = ['/usr/share/pixmaps/', '{}/'.format(os.getcwd()), '{}/../icons/'.format(os.getcwd()),
                 '{}/.local/share/icons/'.format(os.path.expanduser('~'))]
        icon = self.find_icon(icons)

        self.set_border_width(15)
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        top_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        self.mix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        self.add(main_box)
        main_box.pack_start(top_box, False, False, 0)
        main_box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), True, False, 0)
        width = len(self.sndio_streams) * 70
        if width > 800: width = 800
        scroll_box = Gtk.ScrolledWindow()
        scroll_box.set_min_content_width(width)
        scroll_box.set_min_content_height(260)
        self.mix_box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), True, False, 0)
        main_box.pack_start(scroll_box, True, True, 0)
        scroll_box.add(self.mix_box)
        image = Gtk.Image()
        try:
            pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon, 48, 48,
                                                         preserve_aspect_ratio=True)
            image.set_from_pixbuf(pb)
        except gi.repository.GLib.Error:
            pass
        title_label = Gtk.Label()
        title_label.set_markup('<big>SoundBraid</big>\nSimple sound mixer')
        top_box.pack_start(image, False, False, 0)
        top_box.pack_start(title_label, False, False, 0)
        self.audio_widgets = []
        for i, stream in enumerate(self.sndio_streams):
            self.add_slider(stream, i)
        self.show_all()
        self.call_update()

    def add_slider(self, stream, i):
        stream = stream.split('=')
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        audio_widget = Gtk.Scale.new_with_range(1, 0, 100, .1)
        self.audio_widgets.append(audio_widget)
        audio_widget.set_digits(0)
        audio_widget.set_value(int(float(stream[1]) * 100))
        audio_widget.set_value_pos(Gtk.PositionType.TOP)
        audio_widget.set_inverted(True)
        audio_widget.set_property("height-request", 180)
        audio_widget.connect("value-changed", self.set_value, self.sndio_streams[i])
        mute_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_CLOSE))
        mute_button.set_always_show_image(True)
        mute_button.connect("clicked", mute_cmd, self.sndio_streams[i])
        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        button_box.pack_start(mute_button, True, False, 0)
        label = Gtk.Label(label=make_label(stream[0]))
        self.stream_labels.append(label)
        box.pack_end(label, False, False, 0)
        box.pack_end(button_box, False, False, 0)
        box.pack_end(audio_widget, True, True, 0)
        self.mix_box.pack_start(box, True, True, 0)
        self.mix_box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), True, False, 0)

    def call_update(self):
        action = Event()

        def update():
            while not action.wait(.3):
                GLib.idle_add(self.update_value)

        Thread(target=update, daemon=True).start()
        return

    def set_value(self, slider, name):
        name = name.split('=')[0]
        level = str(slider.get_value() / 100)
        if '{}={}'.format(name, level) not in self.sndio_streams:
            os.system('sndioctl -q {}={}'.format(name, level))

    def update_value(self):
        streams = os.popen('sndioctl').read().strip().split()
        streams = [s for s in streams if '.level=' in s]
        if len(self.sndio_streams) != len(streams):
            self.sndio_streams = streams
            for widget in self.mix_box: self.mix_box.remove(widget)
            self.audio_widgets = []
            self.stream_labels = []
            for i, s in enumerate(streams):
                self.add_slider(s, i)
            self.mix_box.show_all()
        else:
            self.sndio_streams = streams
        for widget, stream in zip(self.audio_widgets, self.sndio_streams):
            stream = stream.split('=')
            if int(float(stream[1]) * 100) != int(widget.get_value()):
                widget.set_value(int(float(stream[1]) * 100))

    def find_icon(self, icons):
        names = ['soundbraidl', 'soundbraid']
        for name in names:
            for path in icons:
                try:
                    self.set_icon_from_file('{}{}.svg'.format(path, name))
                    return '{}{}.svg'.format(path, name)
                except gi.repository.GLib.Error:
                    pass


if __name__ == "__main__":
    window = SndioMixer()
    window.connect("delete-event", Gtk.main_quit)
    window.set_position(Gtk.WindowPosition.CENTER)
    Gtk.main()
