#include "main_window.hpp"
#include <gtkmm.h>
#include "my_slider.hpp"
#include <filesystem>
#include <format>
#include <gtkmm/menubutton.h>
#include <util.hpp>
void MainWindow::set_song_loaded(bool loaded) {
    bool unloaded = !loaded;
    seek_bar.set_sensitive(loaded);
    pause_btn.set_sensitive(loaded);
    restart_btn.set_sensitive(loaded);
    stop_btn.set_sensitive(loaded);
    action_enabled_changed("toggle_pause", loaded);
    action_enabled_changed("stop_playback", loaded);
    action_enabled_changed("restart_playback", loaded);
}
void MainWindow::set_subtitle(std::optional<Glib::ustring> subtitle) {
    subTitleLabel.set_visible(subtitle.has_value());
    if (subtitle.has_value()) {
        subTitleLabel.set_text(subtitle.value());
        set_title(subtitle.value() + Glib::ustring(" - ") + titleLabel.get_text());
    } else {
        set_title(titleLabel.get_text());
    }
}
void MainWindow::update_file(optional<std::string> new_file) {

    playback_file = new_file;
    bool song_loaded = new_file.has_value();
    set_song_loaded(song_loaded);
    if (song_loaded) {
        length = playback->GetLength();
        length_component_count = TimeToComponentCount(length);
        length_label.set_text(TimeToString(length, length_component_count).c_str());
        seek_bar.set_range(0, length);
    } else {
        length = 0;
        length_component_count = 2;
        length_label.set_text("00:00");
        seek_bar.set_value(0.0);
    }
    for (auto row : stream_selection_box.get_children()) {
        row->remove_data(Glib::Quark("id"));
    }
    stream_selection_box.remove_all();
    for (auto &stream : playback->get_streams()) {
        int id = stream.id;
        Glib::ustring name(stream.name.c_str());
        Gtk::Label idLabel;
        idLabel.set_text(Glib::ustring(std::format("{}", id).c_str()));
        Gtk::Label nameLabel;
        nameLabel.set_text(name);
        Gtk::Box parentBox;
        parentBox.append(idLabel);
        parentBox.append(nameLabel);
        parentBox.set_orientation(Gtk::Orientation::HORIZONTAL);
        parentBox.set_hexpand();
        nameLabel.set_hexpand();
        Gtk::ListBoxRow listBoxRow;
        listBoxRow.set_child(parentBox);
        listBoxRow.set_hexpand();
        listBoxRow.set_data(Glib::Quark("id"), new int(id));
        stream_selection_box.append(listBoxRow);
        if (id == this->playback->get_current_stream()) {
            stream_selection_box.select_row(listBoxRow);
        }
    }
    stream_selection_box.signal_row_selected().connect([=,this](auto *row) {
        if (row == nullptr || row->get_data(Glib::Quark("id")) == nullptr) {
            return;
        }
        this->playback->play_stream(*(int*)row->get_data(Glib::Quark("id")));
    });
    std::optional<std::string> title = playback->get_current_title();
    song_name = title.value_or("");
    subTitleLabel.set_visible(title.has_value());
    
    if (title.has_value()) {
        set_subtitle(song_name);
    } else {
        set_subtitle({});
    }
    bool length_valid = length > 0;
    seek_bar.set_visible(length_valid);
    seek_indeterminate.set_visible(!length_valid);
    time_label.set_visible(length_valid);
    length_label.set_visible(length_valid);
}
struct signals_split_by_expected {
    uint16_t expected;
    uint16_t unexpected;
    inline uint16_t all_signals() {
        return expected|unexpected;
    }
    inline uint16_t filter(uint16_t signal) {
        return all_signals() & signal;
    }
    inline bool emitted(uint16_t signal) {
        return filter(signal) != 0;
    }
    inline uint16_t filter_expected(uint16_t signal) {
        return expected & signal;
    }
    inline uint16_t filter_unexpected(uint16_t signal) {
        return unexpected & signal;
    }
    inline bool signal_expected(uint16_t signal) {
        return filter_expected(signal) == filter(signal);
    }
    inline bool signal_unexpected(uint16_t signal) {
        return filter_unexpected(signal) == filter(signal);
    }
    signals_split_by_expected(uint16_t expected, uint16_t unexpected)
    : expected(expected)
    , unexpected(unexpected)
    {}
};
bool MainWindow::update() {
    uint16_t signals_expected_and_emitted = playback->handle_signals(expected_signals);
    uint16_t unexpected_signals = PlaybackSignalNone;
    expected_signals &= ~signals_expected_and_emitted;
    auto handle_signal = [this,signals_expected_and_emitted,&unexpected_signals](uint16_t signals) {
        uint16_t output = signals_expected_and_emitted & signals;
        uint16_t unexpected_output = PlaybackSignalNone;
        uint16_t remaining_signals = signals & ~output;
        if (remaining_signals != PlaybackSignalNone) {
            unexpected_output |= playback->handle_signals(remaining_signals);
        }
        unexpected_signals |= unexpected_output;
        return signals_split_by_expected(output, unexpected_output);
    };
    auto signals_handleable = handle_signal(PlaybackSignalFileChanged|PlaybackSignalErrorOccurred|PlaybackSignalPaused|PlaybackSignalResumed|PlaybackSignalPitchChanged|PlaybackSignalSeeked|PlaybackSignalSpeedChanged|PlaybackSignalStarted|PlaybackSignalStopped|PlaybackSignalTempoChanged);
    if (signals_handleable.emitted(PlaybackSignalFileChanged)) {
        auto new_file = playback->get_current_file();
        if (new_file != playback_file) {
            update_file(new_file);
        }
    }
    bool disambiguate_running = false;
    bool stopped = signals_handleable.emitted(PlaybackSignalStopped);
    bool started = signals_handleable.emitted(PlaybackSignalStarted);
    if (stopped && started) {
        disambiguate_running = true;
        started = playback->get_current_file().has_value();
        stopped = !started;
    }
    if (stopped) {
        update_file({});
    }
    double position = playback->GetPosition();
    if (!signals_handleable.emitted(PlaybackSignalSeeked) || signals_handleable.signal_unexpected(PlaybackSignalSeeked)) {
        seek_bar.set_value(position);
    }
    time_label.set_text(TimeToString(position, length_component_count));
    std::vector<Glib::ustring> classes;
    if (playback->IsPaused()) {
        classes.push_back("paused");
        pause_btn.set_icon_name("media-playback-start");
    } else {
        pause_btn.set_icon_name("media-playback-pause");
    }
    if (length <= 0) {
        classes.push_back("no-length");
    }
    if (playback_file.has_value()) {
        classes.push_back("loaded");
    }
    if (signals_handleable.signal_unexpected(PlaybackSignalPitchChanged)) {
        pitch_slider.set_value(playback->GetPitch());
    }
    if (signals_handleable.signal_unexpected(PlaybackSignalSpeedChanged)) {
        speed_slider.set_value(playback->GetSpeed());
    }
    if (signals_handleable.signal_unexpected(PlaybackSignalTempoChanged)) {
        tempo_slider.set_value(playback->GetTempo());
    }
    if (!running) {
        timer_stopped = true;
        g_mutex_lock(timer_mutex);
        g_cond_signal(timer_stopped_condition);
        g_mutex_unlock(timer_mutex);
        return false;
    } else {
        return true;
    }
}
void MainWindow::expect_signal(uint16_t signal) {
    expected_signals |= signal;
}
void MainWindow::update_dark_mode() {
    Gtk::Settings::get_default()->set_property("gtk-application-prefer-dark-theme", Looper::Options::get_option<bool>("ui.gtk.dark_mode", false));
}
MainWindow::MainWindow(Playback *playback, Glib::RefPtr<Gtk::Application> app)
 : playback(playback),
   open_dialog("Open some music...")
 {
    set_application(app);
    update_dark_mode();
    open_dialog.set_transient_for(*this);
    open_dialog.set_modal(true);
    open_dialog.signal_response().connect([this](int response) {
        if (response == Gtk::ResponseType::OK) {
            this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged);
            this->playback->Start(this->open_dialog.get_file().get()->get_path());
        }
        open_dialog.set_visible(false);
    });
    open_dialog.add_button("_Cancel", Gtk::ResponseType::CANCEL);
    open_dialog.add_button("_Open", Gtk::ResponseType::OK);
    auto filter_text = Gtk::FileFilter::create();
    filter_text->set_name("Music files");
    filter_text->add_mime_type("audio/*");
    open_dialog.add_filter(filter_text);
    set_title("Looper");
    set_icon_name("looper");
    set_name("main-window");
    add_action("about_window", [this]() {
        aboutWindow.present();
    });
    add_action("options_window", [this]() {
        optionsWindow.present();
    });
    add_action("open_file", [this]() {
        this->open_dialog.present();
    });
    add_action("toggle_pause", [this]() {
        this->expect_signal((this->playback->IsPaused() ? PlaybackSignalResumed : PlaybackSignalPaused));
        this->playback->Pause();
    });
    add_action("stop_playback", [this]() {
        this->playback->Stop();
    });
    add_action("restart_playback", [this]() {
        this->expect_signal(PlaybackSignalSeeked);
        this->playback->Seek(0.0);
    });
    
    titleBox.set_orientation(Gtk::Orientation::VERTICAL);
    titleBox.set_valign(Gtk::Align::CENTER);
    titleLabel.set_text(get_title());
    titleLabel.set_css_classes({"title"});
    subTitleLabel.set_visible(false);
    subTitleLabel.set_css_classes({"subtitle"});
    titleLabel.insert_at_end(titleBox);
    subTitleLabel.insert_at_end(titleBox);
    headerBar.set_title_widget(titleBox);
    set_default_size(400, 400);
    openBtn.set_label("Open...");
    openBtn.set_name("open-btn");
    openBtn.set_icon_name("folder-open");
    openBtn.set_action_name("win.open_file");
    headerBar.pack_start(openBtn);
    running = true;
    timer_stopped = false;
    timer_stopped_condition = (GCond*)malloc(sizeof(GCond));
    timer_mutex = (GMutex*)malloc(sizeof(GMutex));
    g_cond_init(timer_stopped_condition);
    g_mutex_init(timer_mutex);
    
    top_box.set_orientation(Gtk::Orientation::HORIZONTAL);
    top_box.set_name("top-box");
    bottom_box.set_orientation(Gtk::Orientation::HORIZONTAL);
    bottom_box.set_name("bottom-box");
    controls_box.set_orientation(Gtk::Orientation::VERTICAL);
    controls_box.set_name("controls-box");
    main_box.set_orientation(Gtk::Orientation::VERTICAL);
    main_box.set_name("main-box");
    //main_box.set_valign(Gtk::Align::END);
    main_box.set_vexpand(true);
    controls_box.insert_at_end(main_box);
    top_box.insert_at_end(controls_box);
    bottom_box.insert_at_end(controls_box);
    pause_btn.set_action_name("win.toggle_pause");
    pause_btn.set_name("pause-btn");
    pause_btn.insert_at_end(top_box);
    restart_btn.set_name("restart-btn");
    restart_btn.set_icon_name("view-refresh");
    restart_btn.set_action_name("win.restart_playback");
    restart_btn.insert_at_end(top_box);
    time_label.set_name("time-label");
    time_label.insert_at_end(top_box);
    seek_indeterminate.set_name("seek-placeholder");
    seek_indeterminate.set_text("");
    seek_indeterminate.set_hexpand(true);
    seek_indeterminate.insert_at_end(top_box);
    seek_bar.signal_change_value().connect([this](Gtk::ScrollType scrollType, double value) -> bool {
        this->expect_signal(PlaybackSignalSeeked);
        this->playback->Seek(value);
        return true;
    }, true);
    seek_bar.set_hexpand();
    seek_bar.set_name("seek-bar");
    seek_bar.insert_at_end(top_box);
    length_label.set_name("length-label");
    length_label.insert_at_end(top_box);
    stop_btn.set_action_name("win.stop_playback");
    stop_btn.set_name("stop-btn");
    stop_btn.set_icon_name("media-playback-stop");
    stop_btn.insert_at_end(top_box);
    volume_slider.set_min_value(0.0);
    volume_slider.set_max_value(100.0);
    volume_slider.set_digits_after_decimal(0);
    volume_slider.set_suffix("%");
    volume_slider.set_prefix("Volume: ");
    volume_slider.set_name("volume-slider");
    volume_slider.value_changed.connect([this](double value) {
        this->playback->SetVolume(value);
    });
    volume_slider.set_hexpand(false);
    volume_slider.insert_at_end(top_box);
    speed_slider.set_hexpand();
    speed_slider.set_digits_after_decimal(2);
    speed_slider.set_prefix("Speed: ");
    speed_slider.set_suffix("x");
    speed_slider.set_min_value(0.25);
    speed_slider.set_max_value(4);
    speed_slider.set_logarithmic();
    speed_slider.set_value(playback->GetSpeed());
    speed_slider.set_name("speed-slider");
    speed_slider.value_changed.connect([this](double value) {
        this->expect_signal(PlaybackSignalSpeedChanged);
        this->playback->SetSpeed(value);
    });
    speed_slider.insert_at_end(bottom_box);
    tempo_slider.set_hexpand();
    tempo_slider.set_digits_after_decimal(2);
    tempo_slider.set_prefix("Tempo: ");
    tempo_slider.set_suffix("x");
    tempo_slider.set_min_value(0.25);
    tempo_slider.set_max_value(4);
    tempo_slider.set_logarithmic();
    tempo_slider.set_value(playback->GetTempo());
    tempo_slider.set_name("tempo-slider");
    tempo_slider.value_changed.connect([this](double value) {
        this->expect_signal(PlaybackSignalTempoChanged);
        this->playback->SetTempo(value);
    });
    tempo_slider.insert_at_end(bottom_box);
    pitch_slider.set_hexpand();
    pitch_slider.set_digits_after_decimal(2);
    pitch_slider.set_prefix("Pitch: ");
    pitch_slider.set_suffix("x");
    pitch_slider.set_min_value(0.25);
    pitch_slider.set_max_value(4);
    pitch_slider.set_logarithmic();
    pitch_slider.set_value(playback->GetPitch());
    pitch_slider.set_name("pitch-slider");
    pitch_slider.value_changed.connect([this](double value) {
        this->expect_signal(PlaybackSignalPitchChanged);
        this->playback->SetPitch(value);
    });
    pitch_slider.insert_at_end(bottom_box);
    /*dropSpaceLabel.set_text("Drop a music file here to play it...");
    dropSpaceLabel.set_can_focus(false);
    dropSpaceLabel.set_can_target(false);
    dropSpaceLabel.set_name("drop-space-label");*/
    dropSpace.set_can_target(true);
    dropTarget = Gtk::DropTarget::create(G_TYPE_FILE, Gdk::DragAction::LINK);
    dropSpace.add_controller(dropTarget);
    //stream_selection_box.set_child(dropSpaceLabel);
    dropSpace.set_expand(true);
    dropSpace.set_vexpand_set(true);
    dropSpace.set_name("drop-space-scrolled-window");
    dropSpace.set_child(stream_selection_box);
    stream_selection_box.set_vexpand(true);
    stream_selection_box.set_hexpand(true);
    stream_selection_box.set_vexpand_set();
    stream_selection_box.set_name("stream-selection-box");
    dropTarget->signal_drop().connect([this](const Glib::ValueBase &value, double a, double b) {
        auto obj = g_value_get_object(value.gobj());
        g_object_ref(obj);
        Gio::File file((GFile*)obj);
        this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged);
        this->playback->Start(file.get_path());
        return false;
    }, false);
    dropSpace.insert_at_start(main_box);
    set_child(main_box);
    update_file(playback->get_current_file());
    pitch_slider.set_value(playback->GetPitch(), true);
    speed_slider.set_value(playback->GetSpeed(), true);
    tempo_slider.set_value(playback->GetTempo(), true);
    volume_slider.set_value(playback->GetVolume(), true);
    optionsWindow.set_visible(false);
    optionsWindow.set_transient_for(*this);
    optionsWindow.set_hide_on_close(true);
    optionsWindow.set_modal();
    aboutWindow.set_visible(false);
    aboutWindow.set_transient_for(*this);
    aboutWindow.set_hide_on_close(true);
    aboutWindow.set_modal();
    main_menu = Gio::Menu::create();
    auto file_menu = Gio::Menu::create();
    file_menu->append("Open...", "win.open_file");
    file_menu->append("About...", "win.about_window");
    file_menu->append("Options...", "win.options_window");
    file_menu->append("Quit", "app.quit");
    auto playback_menu = Gio::Menu::create();
    playback_menu->append("Pause/Resume", "win.toggle_pause");
    playback_menu->append("Restart from Beginning", "win.restart_playback");
    playback_menu->append("Stop", "win.stop_playback");
    main_menu->append_submenu("File", file_menu);
    main_menu->append_submenu("Playback", playback_menu);
    app->set_menubar(main_menu);
    options_menu = Gio::Menu::create();
    options_menu->append("About", "win.about_window");
    options_menu->append("Options", "win.options_window");
    set_show_menubar(false);
    menuPopover.set_menu_model(options_menu);
    menuPopover.set_autohide();
    menuBtn.set_popover(menuPopover);
    menuBtn.set_icon_name("application-menu");
    headerBar.pack_end(menuBtn);
    optionsWindow.optionsSaved.connect(sigc::mem_fun(*this, &MainWindow::update_dark_mode));
    set_titlebar(headerBar);
    Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::update), 100);
}
MainWindow::~MainWindow() {
    running = false;
    while (!timer_stopped) {
        g_cond_wait(timer_stopped_condition, timer_mutex);
    }
    free(timer_stopped_condition);
    free(timer_mutex);
}