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

# SoundBraid - Simple audio mixer <https://codeberg.org/JWMKit/soundbraid>
# Copyright © 2021 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/>.


class SndioMixer(Gtk.Window):

    def __init__(self):
        icon = '/usr/share/pixmaps/soundbraid.svg'
        self.streams = os.popen('aucatctl').read().strip().split()
        if self.streams:
            self.streams.insert(0, self.streams.pop(-1))
        Gtk.Window.__init__(self, title="SoundBraid")
        try:
            self.set_icon_from_file(icon)
        except gi.repository.GLib.Error:
            pass
        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.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.skip is used to signal when to skip the update loop to prevent conflicts.
        self.skip = [1, 1]
        self.audio_widgets = []
        self.mix_box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), True, False, 0)
        for stream in self.streams:
            self.add_slider(stream)
        self.show_all()

        self.stop_updates = self.call_update(.3)

    def add_slider(self, stream):
        stream = stream.split('=')
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        audio_widget = Gtk.Scale.new_with_range(1, 0, 127, .5)
        self.audio_widgets.append(audio_widget)
        audio_widget.set_digits(0)
        audio_widget.set_value(int(stream[1]))
        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, stream[0])
        box.pack_end(Gtk.Label(label=stream[0]), 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, i):
        stopped = Event()

        def update():
            while not stopped.wait(i):
                if not self.get_visible():
                    self.stop_updates()
                    return
                self.update_value()

        Thread(target=update).start()
        return stopped.set

    def set_value(self, slider, name):
        if not self.skip[0]:
            self.skip[0] = self.skip[0] - 1
            return
        level = str(int(slider.get_value()))
        if '{}={}'.format(name, level) not in self.streams:
            self.skip[1] = 2
            os.system('aucatctl {}={}'.format(name, level))

    def update_value(self):
        if not self.skip[1]:
            self.skip[1] = self.skip[1] - 1
            return
        streams = os.popen('aucatctl').read().strip().split()
        if streams:
            streams.insert(0, streams.pop(-1))
        if len(self.streams) != len(streams):
            tmp1 = [stream.split('=')[0] for stream in streams]
            tmp2 = [stream.split('=')[0] for stream in self.streams]
            new_stream = list(set(tmp1) - set(tmp2))
            new_stream = [stream for stream in streams if stream.startswith(new_stream[0])][0]
            self.add_slider(new_stream)
            self.mix_box.show_all()
        self.streams = streams
        for widget, stream in zip(self.audio_widgets, self.streams):
            stream = stream.split('=')
            if int(stream[1]) != int(widget.get_value()):
                self.skip[0] = 2
                widget.set_value(int(stream[1]))


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

# TODO
#  - scroll window
#  - dynamic size of scroll window

