/* kew - Music For The Shell
Copyright (C) 2022 Ravachol

http://codeberg.org/ravachol/kew

$$\
$$ |
$$ |  $$\  $$$$$$\  $$\  $$\  $$\
$$ | $$  |$$  __$$\ $$ | $$ | $$ |
$$$$$$  / $$$$$$$$ |$$ | $$ | $$ |
$$  _$$<  $$   ____|$$ | $$ | $$ |
$$ | \$$\ \$$$$$$$\ \$$$$$\$$$$  |
\__|  \__| \_______| \_____\____/

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */

#ifndef __USE_POSIX
#define __USE_POSIX
#endif

#ifdef __FreeBSD__
#define __BSD_VISIBLE 1
#endif

#ifndef KEW_VERSION
#define KEW_VERSION "4.0.0"
#endif

#include "common/appstate.h"
#include "common/common.h"

#include "sys/discord_rpc.h"
#include "sys/mpris.h"
#include "sys/notifications.h"
#include "sys/sys_integration.h"

#include "ui/chroma.h"
#include "ui/cli.h"
#include "ui/common_ui.h"
#include "ui/control_ui.h"
#include "ui/input.h"
#include "ui/player_ui.h"
#include "ui/playlist_ui.h"
#include "ui/queue_ui.h"
#include "ui/search_ui.h"
#include "ui/settings.h"
#include "ui/termbox2_input.h"
#include "ui/visuals.h"

#include "ops/library_ops.h"
#include "ops/playback_clock.h"
#include "ops/playback_ops.h"
#include "ops/playback_state.h"
#include "ops/playback_system.h"
#include "ops/playlist_ops.h"
#include "ops/track_manager.h"

#include "utils/file.h"
#include "utils/term.h"
#include "utils/utils.h"

#include <fcntl.h>
#include <gio/gio.h>
#include <glib-unix.h>
#include <glib.h>
#include <libintl.h>
#include <locale.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>

AppState *state_ptr = NULL;

/**
 * @brief Updates the player state based on window size changes.
 *
 * This function checks if the terminal window size has changed. If it has, it sets
 * the resize flag and adjusts the UI state. If the resize flag is set, it will resize
 * the UI; otherwise, it refreshes the player.
 */
void update_player(void)
{
        AppState *state = get_app_state();
        UIState *uis = &(state->uiState);
        struct winsize ws;
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
                if (ws.ws_col != state->uiState.windowSize.ws_col || ws.ws_row != state->uiState.windowSize.ws_row) {
                        uis->resizeFlag = 1;
                        state->uiState.windowSize = ws;
                }
        }

        if (uis->resizeFlag) {
                resize(uis);
        } else {
                refresh_player();
        }
}

/**
 * @brief Prepares the next song for playback.
 *
 * This function performs actions like resetting the clock, finishing loading,
 * and preparing the next song to be played. It handles playlist and repeat logic.
 */
void prepare_next_song(void)
{
        AppState *state = get_app_state();

        reset_clock();
        handle_skip_out_of_order();
        finish_loading();

        set_next_song(NULL);
        trigger_refresh();

        Node *current = get_current_song();

        if (!is_repeat_enabled() || current == NULL) {

                if (!sound_system_is_end_of_list_reached(sound_sys)) {
                        PlaybackState *ps = get_playback_state();
                        ps->loadedNextSong = false;
                }
        }

        if (current == NULL) {
                if (state->uiSettings.quitAfterStopping) {
                        quit();
                } else {
                        set_end_of_list_reached();
                }
        } else {
                determine_song_and_notify();
        }
}

/**
 * @brief Prepares and plays the specified song.
 *
 * This function sets up the player to play the specified song, unloads previous songs,
 * and loads the new song. It then resumes playback, creating a playback device if needed.
 *
 * @param song Pointer to the song (Node) to be played.
 *
 * @return int Status of the operation: 0 on success, negative value on error.
 */
int prepare_and_play_song(Node *song)
{
        if (!song)
                return -1;

        set_current_song(song);

        stop();

        int res = load_first(get_current_song());

        finish_loading();

        if (res >= 0) {
                resume_playback();
        }

        if (res < 0)
                set_end_of_list_reached();

        trigger_refresh();
        reset_clock();

        return res;
}

/**
 * @brief Checks and loads the next song if necessary.
 *
 * This function checks if a song needs to be loaded, either due to a restart or if the
 * playlist is in shuffle mode. It prepares and loads the next song in the playlist if needed.
 */
void check_and_load_next_song(void)
{
        AppState *state = get_app_state();
        PlaybackState *ps = get_playback_state();

        if (should_start_playing()) {
                PlayList *playlist = get_playlist();
                if (playlist->head == NULL)
                        return;

                if (ps->waitingForPlaylist || ps->waitingForNext) {
                        ps->songLoading = true;

                        Node *next_song = determine_next_song(playlist);
                        if (!next_song)
                                return;

                        start_playing(false);
                        ps->waitingForPlaylist = false;
                        ps->waitingForNext = false;
                        state->uiState.songWasRemoved = false;

                        if (is_shuffle_enabled())
                                reshuffle_playlist();

                        int res = prepare_and_play_song(next_song);

                        if (res < 0)
                                set_end_of_list_reached();

                        ps->loadedNextSong = false;
                        set_next_song(NULL);
                }
        } else if (get_current_song() != NULL &&
                   (ps->nextSongNeedsRebuilding || get_next_song() == NULL) &&
                   !ps->songLoading) {
                update_next_song_if_needed();
        }
}

/**
 * @brief Loads the next song if needed, considering the current state of the playlist and player.
 *
 * This function loads the next song from the playlist if the current song has finished or
 * if the player is waiting for the next song. It handles various conditions, including
 * handling end-of-list behavior and errors.
 */
void load_waiting_music(void)
{
        PlayList *playlist = get_playlist();
        PlaybackState *ps = get_playback_state();

        if (playlist->head != NULL) {
                if ((ps->skipFromStopped || !ps->loadedNextSong ||
                     ps->nextSongNeedsRebuilding) &&
                    !sound_system_is_end_of_list_reached(sound_sys)) {
                        check_and_load_next_song();
                }

                if (ps->songHasErrors)
                        try_load_next();

                if (is_EOF_reached()) {
                        set_EOF_handled();
                        prepare_next_song();
                        switch_decoder();
                }
        } else {
                set_EOF_handled();
        }
}

/**
 * @brief Shuts down the application and cleans up resources.
 *
 * This function stops playback, frees resources, and shuts down the application. It handles
 * cleanup tasks like saving settings, stopping playback, and freeing memory.
 */
void kew_shutdown()
{
        AppState *state = get_app_state();
        FileSystemEntry *library = get_library();
        AppSettings *settings = get_app_settings();
        PlayList *favorites_playlist = get_favorites_playlist();

        shutdown_sound_system();

        emit_playback_stopped_mpris();

        if (chroma_is_started())
                state->uiSettings.chromaPreset = chroma_get_current_preset();
        else
                state->uiSettings.chromaPreset = -1;

        chroma_stop();

        bool noMusicFound = false;

        if (library == NULL || library->children == NULL) {
                noMusicFound = true;
        }

#ifdef CHAFA_VERSION_1_16
        retire_passthrough_workarounds_tmux();
#endif
        bool wait_until_complete = true;
        update_library_if_changed_detected(wait_until_complete);
        shutdown_input();

        if (state->uiSettings.discordRPCEnabled)
                discord_rpc_shutdown();
        free_search_results();
        cleanup_mpris();
        set_path(settings->path);
        set_prefs(settings, &(state->uiSettings));
        save_favorites_playlist(settings->path, favorites_playlist);
        save_library();
        free_library();
        free_playlists();
        set_default_text_color();

        pthread_mutex_destroy(&(state->switch_mutex));

        free_visuals();

#ifdef USE_DBUS
        cleanup_notifications();
#endif

#ifdef DEBUG
        if (state->uiState.logFile)
                fclose(state->uiState.logFile);
#endif

        if (freopen("/dev/stderr", "w", stderr) == NULL) {
                perror("freopen error");
        }

        if (state_ptr->uiSettings.mouseEnabled)
                disable_terminal_mouse_buttons();

        printf("\n");
        show_cursor();
        exit_alternate_screen_buffer();
        restore_terminal_mode();

        if (state_ptr->uiSettings.trackTitleAsWindowTitle)
                restore_terminal_window_title();

        if (noMusicFound) {
                printf(_("No Music found.\n"));
                printf(_("Please make sure the path is set correctly. \n"));
                printf(_("To set it type: kew path \"/path/to/Music\". \n"));
        } else if (state->uiState.noPlaylist) {
                printf(_("Music not found.\n"));
        }

        if (has_error_message()) {
                printf(_("%s\n"), get_error_message());
        }

        printf("\n");
        fflush(stdout);
        exit(0);
}

/**
 * @brief Main callback for the event loop, runs periodically.
 *
 * This function handles actions such as updating elapsed time, processing events, and
 * updating the player UI. It runs at different speeds based on the current view.
 *
 * @param data Additional data passed to the callback (unused in this case).
 *
 * @return gboolean Returns TRUE to keep the callback running.
 */
gboolean mainloop_callback(gpointer data)
{
        (void)data;

        calc_elapsed_time(get_current_song_duration());
        increment_update_counter();
        handle_cooldown();

        if (should_exit()) {
                g_main_loop_quit((GMainLoop *)data);
                return FALSE;
        }

        int update_counter = get_update_counter();

        // Different views run at different speeds to lower the impact on system
        // requirements
        if ((update_counter % 2 == 0 && state_ptr->currentView == SEARCH_VIEW) ||
            (state_ptr->currentView == TRACK_VIEW || update_counter % 3 == 0)) {
                process_d_bus_events();

                update_player();

                load_waiting_music();
        }

        return TRUE;
}

/**
 * @brief Quits the application upon receiving a signal.
 *
 * This function terminates the main loop and cleans up resources upon receiving a termination signal.
 *
 * @param user_data User data (GMainLoop) passed to the signal handler.
 *
 * @return gboolean Returns G_SOURCE_REMOVE to remove the signal source.
 */
static gboolean quit_on_signal(gpointer user_data)
{
        GMainLoop *loop = (GMainLoop *)user_data;
        g_main_loop_quit(loop);
        return G_SOURCE_REMOVE; // Remove the signal source
}

/**
 * @brief Creates and runs the main event loop.
 *
 * This function sets up the main event loop and signals for quitting. It uses
 * `g_main_loop_run()` to run the loop and handles updates at regular intervals.
 */
void create_loop(void)
{
        update_last_input_time();

        GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);

        g_unix_signal_add(SIGINT, quit_on_signal, main_loop);
        g_unix_signal_add(SIGHUP, quit_on_signal, main_loop);
        g_unix_signal_add(SIGTERM, quit_on_signal, main_loop);
        g_timeout_add(34, mainloop_callback, main_loop);
        g_main_loop_run(main_loop);
        g_main_loop_unref(main_loop);
        kew_shutdown();
}

/**
 * @brief Runs the application with the specified settings.
 *
 * This function initializes various settings and begins playing music, depending on the
 * provided `start_playing` parameter. It also handles the playlist and UI settings.
 *
 * @param start_playing Boolean flag to indicate whether to start playing immediately.
 */
void run(bool start_playing)
{
        AppState *state = get_app_state();
        PlayList *playlist = get_playlist();
        PlayList *unshuffled_playlist = get_unshuffled_playlist();
        PlaybackState *ps = get_playback_state();

        if (unshuffled_playlist == NULL) {
                set_unshuffled_playlist(deep_copy_playlist(playlist));
        }

        if (state->uiSettings.saveRepeatShuffleSettings) {

                if (state->uiSettings.shuffle_enabled)
                        toggle_shuffle();
        }

        if (playlist->head == NULL) {
                state->currentView = LIBRARY_VIEW;
        }

        init_mpris();

        if (state->uiSettings.chromaPreset >= 0) {
                chroma_set_current_preset(state->uiSettings.chromaPreset);
                state->uiSettings.visualizations_instead_of_cover = true;
        }
        ps->loadedNextSong = false;
        if (start_playing)
                ps->waitingForPlaylist = true;

        if (playlist->count != 0)
                check_and_load_next_song();

        create_loop();
}

/**
 * @brief Initializes the locale settings for the application.
 *
 * This function sets up the locale settings and binds text domains for translations.
 */
void init_locale(void)
{
        setlocale(LC_ALL, "");
        setlocale(LC_CTYPE, "");

        bindtextdomain("kew", LOCALEDIR);
        textdomain("kew");
}

/**
 * @brief Initializes the application and sets up necessary states.
 *
 * This function sets the initial state of the application, initializes resources, and prepares
 * the environment for playback. It handles various settings, file paths, and library initialization.
 *
 * @param set_library_enqueued_status Flag indicating whether to set the library's enqueued status.
 */
void kew_init(bool set_library_enqueued_status)
{
        AppState *state = get_app_state();

        set_nonblocking_mode();

        disable_terminal_line_input();
        init_resize();
        ioctl(STDOUT_FILENO, TIOCGWINSZ, &state->uiState.windowSize);
        enable_scrolling();

        init_input();

        if (state->uiSettings.discordRPCEnabled)
                discord_rpc_init();

        // This is to not stop Chroma when we can't keep up with it, instead just return an error
        signal(SIGPIPE, SIG_IGN);

        PlayList *playlist = get_playlist();

        start_playing(true);

        unsigned int seed = (unsigned int)time(NULL);

        srand(seed);

        pthread_mutex_init(&(playlist->mutex), NULL);
        free_search_results();
        reset_chosen_dir();
        create_library(set_library_enqueued_status);
        state->uiSettings.LAST_ROW = _(" [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help]");
        clear_screen();
        fflush(stdout);

        set_term_size();

#ifdef DEBUG
        // g_setenv("G_MESSAGES_DEBUG", "all", TRUE);
        state->uiState.logFile = freopen("error.log", "w", stderr);
        if (state->uiState.logFile == NULL) {
                fprintf(stdout, "Failed to redirect stderr to error.log\n");
        }
#else
        FILE *null_stream = freopen("/dev/null", "w", stderr);
        (void)null_stream;
#endif

        if (!create_sound_system())
                quit();
}

/**
 * @brief Initializes the default state for the application.
 *
 * This function initializes the default UI settings, playback state, and other system resources.
 * It sets up the playlist, resets various flags, and prepares the system for playback.
 */
void init_default_state(void)
{
        bool set_library_enqueued_status = true;
        kew_init(set_library_enqueued_status);

        AppState *state = get_app_state();
        FileSystemEntry *library = get_library();
        PlayList *playlist = get_playlist();
        PlaybackState *ps = get_playback_state();

        add_enqueued_songs_to_playlist(library, playlist);

        set_unshuffled_playlist(deep_copy_playlist(playlist));

        reset_list_after_dequeuing_playing_song();

        start_playing(true);
        sound_system_set_end_of_list_reached(sound_sys, true);
        ps->loadedNextSong = false;

        state->currentView = LIBRARY_VIEW;

        run(false);
}

/**
 * @brief Handles the "play" command from the playlist.
 *
 * This function validates paths and processes the playlist for playback. It checks if the
 * provided paths exist and are valid, then initiates the playback with the playlist.
 *
 * @param argc Pointer to the argument count.
 * @param argv Array of argument strings.
 *
 * @return bool Returns true if the command is valid, false otherwise.
 */
static bool handle_play_command_playlist(int *argc, char **argv)
{
        char de_expanded[PATH_MAX];
        // Working with multiple files
        //validate all paths

        for (int i = 2; i < *argc; i++) {
                if ((expand_path(argv[i], de_expanded) != 0) || (exists_file(de_expanded) == -1)) {
                        return false;
                }
        }
        play_command_with_playlist(argc, argv);

        return true;
}

/**
 * @brief Initializes the state for the application.
 *
 * This function sets the application state, initializes variables, and prepares the system for
 * running. It sets up playback, UI settings, and various other application states.
 */
void init_state(void)
{
        AppState *state = get_app_state();

        state->uiSettings.VERSION = KEW_VERSION;
        state->uiSettings.uiEnabled = true;
        state->uiSettings.color.r = 125;
        state->uiSettings.color.g = 125;
        state->uiSettings.color.b = 125;
        state->uiSettings.coverEnabled = true;
        state->uiSettings.hideLogo = false;
        state->uiSettings.hideHelp = false;
        state->uiSettings.hideFooter = false;
        state->uiSettings.hideTimeStatus = true;
        state->uiSettings.quitAfterStopping = false;
        state->uiSettings.hideGlimmeringText = false;
        state->uiSettings.coverAnsi = false;
        state->uiSettings.visualizerEnabled = true;
        state->uiSettings.discordRPCEnabled = true;
        state->uiSettings.visualizer_height = 5;
        state->uiSettings.visualizer_color_type = 0;
        state->uiSettings.visualizerBrailleMode = false;
        state->uiSettings.visualizer_bar_mode = 2;
        state->uiSettings.titleDelay = 9;
        state->uiSettings.cacheLibrary = -1;
        state->uiSettings.mouseEnabled = true;
        state->uiSettings.mouseLeftClickAction = 0;
        state->uiSettings.mouseMiddleClickAction = 1;
        state->uiSettings.mouseRightClickAction = 2;
        state->uiSettings.mouseScrollUpAction = 3;
        state->uiSettings.mouseScrollDownAction = 4;
        state->uiSettings.mouseAltScrollUpAction = 7;
        state->uiSettings.mouseAltScrollDownAction = 8;
        state->uiSettings.replayGainCheckFirst = 0;
        state->uiSettings.saveRepeatShuffleSettings = 1;
        state->uiSettings.repeatState = 0;
        state->uiSettings.shuffle_enabled = 0;
        state->uiSettings.trackTitleAsWindowTitle = 1;
        state->uiState.numDirectoryTreeEntries = 0;
        state->uiState.num_progress_bars = 35;
        state->uiState.chosen_node_id = 0;
        state->uiState.resetPlaylistDisplay = true;
        state->uiState.allowChooseSongs = false;
        state->uiState.allowChooseSearchSongs = false;
        state->uiState.openedSubDir = false;
        state->uiState.numSongsAboveSubDir = 0;
        state->uiState.numSongsAboveSearchSubDir = 0;
        state->uiState.openedSearchSubDir = 0;
        state->uiState.collapseSearchView = 0;
        state->uiState.previous_chosen_search_row = 0;
        state->uiState.resizeFlag = 0;
        state->uiState.collapseView = false;
        state->uiState.refresh = true;
        state->uiState.isFastForwarding = false;
        state->uiState.isRewinding = false;
        state->uiState.songWasRemoved = false;
        state->uiState.startFromTop = false;
        state->uiState.lastNotifiedId = -1;
        state->uiState.noPlaylist = false;
        state->uiState.logFile = NULL;
        state->uiState.showLyricsPage = false;
        state->uiState.currentLibEntry = NULL;
        state->uiSettings.default_color = 150;
        state->uiSettings.defaultColorRGB.r = state->uiSettings.default_color;
        state->uiSettings.defaultColorRGB.g = state->uiSettings.default_color;
        state->uiSettings.defaultColorRGB.b = state->uiSettings.default_color;
        state->uiSettings.kewColorRGB.r = 222;
        state->uiSettings.kewColorRGB.g = 43;
        state->uiSettings.kewColorRGB.b = 77;
        state->uiSettings.chromaPreset = -1;
        state->uiSettings.visualizations_instead_of_cover = false;
        state->uiSettings.lastVolume = 100;

        PlaybackState *ps = get_playback_state();

        ps->lastPlayedId = -1;
        ps->nextSongNeedsRebuilding = false;
        ps->songHasErrors = false;
        ps->forceSkip = false;
        ps->skipFromStopped = false;
        ps->skipping = false;
        ps->skipOutOfOrder = false;
        ps->clearingErrors = false;
        ps->hasSilentlySwitched = false;
        ps->songLoading = false;
        ps->loadedNextSong = false;
        ps->waitingForNext = false;
        ps->waitingForPlaylist = false;
        ps->notifySwitch = false;
        ps->notifyPlaying = false;
        ps->notifySeek = false;

        pthread_mutex_init(&(state->switch_mutex), NULL);

        set_unshuffled_playlist(NULL);
        set_favorites_playlist(NULL);

        reset_digits_pressed();

        state_ptr = state;
}

/**
 * @brief Restores the terminal state when a signal is received.
 *
 * This function handles restoring the terminal state, such as showing the cursor, leaving
 * the alternate screen buffer, and disabling mouse input when a signal is received (e.g., SIGINT).
 *
 * @param sig The signal number.
 */
void force_terminal_restore(int sig)
{
        ssize_t res;

        // Show cursor
        res = write(STDOUT_FILENO, "\033[?25h", 7);
        (void)res;

        // Leave alternate screen
        res = write(STDOUT_FILENO, "\033[?1049l", 8);
        (void)res;

        // Disable mouse
        res = write(STDOUT_FILENO, "\033[?1000l", 9);
        (void)res;

        // Restore default handler for this signal
        signal(sig, SIG_DFL);

        // Re-raise the signal so the kernel prints the crash message
        raise(sig);
}

/**
 * @brief Main entry point of the application.
 *
 * This function processes command-line arguments, initializes the application, and runs the main event loop.
 * It handles various cases for displaying help, version, and running the application based on user input.
 *
 * @param argc The number of command-line arguments.
 * @param argv The array of command-line argument strings.
 *
 * @return int The exit status of the program.
 */
int main(int argc, char *argv[])
{
        AppState *state = get_app_state();

        init_state();
        init_locale();
        restart_if_already_running(argv);

        AppSettings *settings = get_app_settings();
        PlayList *playlist = get_playlist();
        PlayList *favorites_playlist = get_favorites_playlist();

        if ((argc == 2 &&
             ((strcmp(argv[1], "--help") == 0) ||
              (strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "-?") == 0)))) {
                show_help();
                exit(0);
        } else if (argc == 2 && (strcmp(argv[1], "--version") == 0 ||
                                 strcmp(argv[1], "-v") == 0)) {
                state->uiSettings.colorMode = COLOR_MODE_ALBUM;
                state->uiSettings.color = state->uiSettings.defaultColorRGB;
                print_about_for_version(NULL);
                exit(0);
        }
        *settings = init_settings();
        transfer_settings_to_ui();
        init_key_mappings(settings);
        set_track_title_as_window_title();

        bool run_for_play_command_with_playlist = false;

        if (argc == 3 && (strcmp(argv[1], "path") == 0)) {
                char de_expanded[PATH_MAX];
                collapse_path(argv[2], de_expanded);
                c_strcpy(settings->path, de_expanded, sizeof(settings->path));
                set_path(settings->path);
                exit(0);
        } else if (argc >= 3 && (strcmp(argv[1], "play") == 0)) {
                run_for_play_command_with_playlist = handle_play_command_playlist(&argc, argv);
        }

        enable_mouse(&(state->uiSettings));
        enter_alternate_screen_buffer();

        signal(SIGINT, force_terminal_restore);
        signal(SIGSEGV, force_terminal_restore);
        signal(SIGABRT, force_terminal_restore);

        if (settings->path[0] == '\0') {
                set_music_path();
        }

        bool exact_search = false;
        handle_options(&argc, argv, &exact_search);
        load_favorites_playlist(settings->path, &favorites_playlist);

        ensure_default_theme_pack();

        init_theme(argc, argv);

        if (argc == 1) {
                init_default_state();
        } else if (argc == 2 && strcmp(argv[1], "all") == 0) {
                kew_init(false);
                play_all();
                run(true);
        } else if (argc == 2 && strcmp(argv[1], "albums") == 0) {
                kew_init(false);
                play_all_albums();
                run(true);
        } else if (argc == 2 && strcmp(argv[1], ".") == 0 && favorites_playlist->count != 0) {
                kew_init(false);
                play_favorites_playlist();
                run(true);
        } else if (run_for_play_command_with_playlist) {
                kew_init(false);
                FileSystemEntry *library = get_library();
                mark_list_as_enqueued(library, playlist);
                run(true);
        } else if (argc >= 2) {
                kew_init(false);
                make_playlist(&playlist, argc, argv, exact_search, settings->path);

                if (playlist->count == 0) {
                        if (argc > 1 && argv[1] && strcmp(argv[1], "theme") != 0)
                                state->uiState.noPlaylist = true;
                        kew_shutdown();
                }

                FileSystemEntry *library = get_library();

                mark_list_as_enqueued(library, playlist);
                run(true);
        }

        return 0;
}
