#ifdef _WIN32
#define PACKAGE "cava"
#define VERSION "1.0.0"
#define _CRT_SECURE_NO_WARNINGS 1
#endif // _WIN32

#include "config.h"
#include "input/common.h"
#include "output/common.h"

#include <signal.h>

#ifdef __GNUC__
// curses.h or other sources may already define
#undef GCC_UNUSED
#define GCC_UNUSED __attribute__((unused))
#else
#define GCC_UNUSED /* nothing */
#endif

#ifdef _WIN32
char *optarg = NULL;
int optind = 1;
int optopt = 0;

static int getopt(int argc, char *const argv[], const char *optstring) {
    if ((optind >= argc) || (argv[optind][0] != '-') || (argv[optind][0] == 0)) {
        return -1;
    }

    if (argv[optind][0] == '-' && argv[optind][1] == '\0') {
        return -1;
    }

    if (strcmp(argv[optind], "--") == 0) {
        optind++;
        return -1;
    }

    if (argv[optind][0] == '-' && argv[optind][1] == '-' && argv[optind][2] != '\0') {
        const char *longopt = argv[optind] + 2;
        if (strcmp(longopt, "help") == 0) {
            optopt = 'h';
            optind++;
            return 'h';
        }
        if (strcmp(longopt, "version") == 0) {
            optopt = 'v';
            optind++;
            return 'v';
        }
        if (strcmp(longopt, "config") == 0) {
            optopt = 'p';
            optind++;
            if (optind >= argc) {
                return ':';
            }
            optarg = argv[optind];
            optind++;
            return 'p';
        }
        optopt = 0;
        optind++;
        return '?';
    }

    int opt = argv[optind][1];
    const char *p = strchr(optstring, opt);

    if (p == NULL) {
        optopt = opt;
        optind++;
        return '?';
    }
    if (p[1] == ':') {
        optopt = opt;
        optind++;
        if (optind >= argc) {
            return ':';
        }
        optarg = argv[optind];
        optind++;
        return opt;
    }

    optopt = opt;
    optind++;
    return opt;
}
#endif

// used by sig handler
// whether we should reload the config or not
volatile sig_atomic_t should_reload = 0;
// whether we should only reload colors or not
volatile sig_atomic_t reload_colors = 0;
// whether we should quit
volatile sig_atomic_t should_quit = 0;
volatile sig_atomic_t signal_received = 0;

static bool get_file_state(const char *path, time_t *mtime, long long *size) {
#ifdef _WIN32
    struct _stat st;
    if (_stat(path, &st) != 0)
        return false;
#else
    struct stat st;
    if (stat(path, &st) != 0)
        return false;
#endif
    *mtime = st.st_mtime;
    *size = (long long)st.st_size;
    return true;
}

// these variables are used only in main, but making them global
// will allow us to not free them on exit without ASan complaining
struct config_params p = {0};

// general: handle signals
#ifdef _WIN32
int sig_handler(DWORD sig_no) {
#else
void sig_handler(int sig_no) {
#endif
#ifndef _WIN32

    if (sig_no == SIGUSR1) {
        should_reload = 1;
        return;
    }

    if (sig_no == SIGUSR2) {
        reload_colors = 1;
        return;
    }
#endif

#ifdef _WIN32
    if (sig_no == CTRL_C_EVENT || sig_no == CTRL_CLOSE_EVENT) {
        sig_no = SIGINT;
    } else {
        return TRUE;
    }
#endif
    if (sig_no == SIGINT || sig_no == SIGTERM) {
        should_reload = 1;
        should_quit = 1;
    }

    signal_received = sig_no;

#ifdef _WIN32
    return TRUE;
#endif
}

// general: entry point
int main(int argc, char **argv) {

    // handle command-line arguments
    char configPath[PATH_MAX];
    configPath[0] = '\0';
#ifdef _WIN32
    if (!SetConsoleCtrlHandler(sig_handler, TRUE)) {
        fprintf(stderr, "ERROR: Could not set control handler");
        exit(EXIT_FAILURE);
    }
#else
    // general: handle Ctrl+C
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    action.sa_handler = &sig_handler;
    sigaction(SIGINT, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
    sigaction(SIGUSR1, &action, NULL);
    sigaction(SIGUSR2, &action, NULL);
#endif
    char *usage = "\n\
Usage: " PACKAGE " [options]\n\
Visualize audio input in terminal.\n\
\n\
Options:\n\
\t-p, --config <path>    Path to config file\n\
\t-v, --version          Print version and exit\n\
\t-h, --help             Show this help and exit\n\
\n\
Keys:\n\
        Up        Increase sensitivity\n\
        Down      Decrease sensitivity\n\
        Left      Decrease number of bars\n\
        Right     Increase number of bars\n\
        r         Reload config\n\
        c         Reload colors only\n\
        f         Cycle foreground color\n\
        b         Cycle background color\n\
        o         Change orientation bottom -> right -> top -> left (ncurses), top <-> bottom (other modes)\n\
        q         Quit\n\
\n";
    int c;
#ifndef _WIN32
    static struct option long_options[] = {
        {"config", required_argument, NULL, 'p'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {0, 0, 0, 0},
    };
    opterr = 0;
    while ((c = getopt_long(argc, argv, ":p:vh", long_options, NULL)) != -1) {
#else
    while ((c = getopt(argc, argv, ":p:vh")) != -1) {
#endif
        switch (c) {
        case 'p': // argument: config path
            snprintf(configPath, sizeof(configPath), "%s", optarg);
            break;
        case 'h': // argument: print usage
            printf("%s", usage);
            return 0;
        case 'v': // argument: print version
            printf(PACKAGE " " VERSION "\n");
            return 0;
        case ':': // missing argument
            fprintf(stderr, PACKAGE ": error: option requires an argument -- '%c'\n", optopt);
            fprintf(stderr, "Try '%s --help' for more information.\n", PACKAGE);
            return 1;
        case '?': // unknown option
            if (optopt != 0) {
                fprintf(stderr, PACKAGE ": error: invalid option -- '%c'\n", optopt);
            } else {
                fprintf(stderr, PACKAGE ": error: invalid option\n");
            }
            fprintf(stderr, "Try '%s --help' for more information.\n", PACKAGE);
            return 1;
        default: // argument: no arguments; exit
            abort();
        }
    }

    if (optind < argc) {
        fprintf(stderr, PACKAGE ": error: unknown argument '%s'\n", argv[optind]);
        fprintf(stderr, "Try '%s --help' for more information.\n", PACKAGE);
        return 1;
    }

#ifndef _WIN32
    // general: console title
    printf("%c]0;%s%c", '\033', PACKAGE, '\007');
#endif // !_WIN32

    // general: main loop
    while (1) {

        // config: load
        struct error_s error;
        error.length = 0;
        if (!load_config(configPath, &p, &error)) {
            fprintf(stderr, "Error loading config. %s", error.message);
            exit(EXIT_FAILURE);
        }

        time_t config_mtime = 0;
        long long config_size = 0;
        bool has_config_state = false;
        if (p.live_config) {
            has_config_state = get_file_state(configPath, &config_mtime, &config_size);
        }

        p.inAtty = 0;
        p.inAterminal = 0;

#ifndef _WIN32
        if ((p.output == OUTPUT_NCURSES || p.output == OUTPUT_NONCURSES) && ttyname(0) != NULL) {
            // Check if we're running in a tty
            if (strncmp(ttyname(0), "/dev/tty", 8) == 0 ||
                strcmp(ttyname(0), "/dev/console") == 0 ||
                strncmp(ttyname(0), "/dev/ttyS", 9) == 0 ||
                strncmp(ttyname(0), "/dev/ttyUSB", 11) == 0)
                p.inAtty = 1;

            // Check if we are running in a dumb terminal
            if (strncmp(ttyname(0), "/dev/ttyS", 9) == 0 ||
                strncmp(ttyname(0), "/dev/ttyUSB", 11) == 0)
                p.inAterminal = 1;

            // in macos virtual terminals are called ttys(xyz) and there are no ttys
            if (strncmp(ttyname(0), "/dev/ttys", 9) == 0)
                p.inAtty = 0;

            if (p.inAtty) {
                if (p.inAterminal) {
                    printf("\033P 1;32;1;0;0;2 { sp @ "
                           "\?\?\?\?\?\?\?\?/GGGGGGGG;"
                           "\?\?\?\?\?\?\?\?/GGGGGGGG;"
                           "\?\?\?\?\?\?\?\?/KKKKKKKK;"
                           "\?\?\?\?\?\?\?\?/MMMMMMMM;"
                           "\?\?\?\?\?\?\?\?/NNNNNNNN;"
                           "oooooooo/NNNNNNNN;"
                           "wwwwwwww/NNNNNNNN;"
                           "}}}}}}}}/NNNNNNNN;"
                           "~~~~~~~~/NNNNNNNN");

                    printf("\033( sp @ ");
                } else {
#ifdef CAVAFONT
                    // checking if cava psf font is installed in FONTDIR
                    FILE *font_file;
                    font_file = fopen(FONTDIR "/" FONTFILE, "r");
                    if (font_file) {
                        fclose(font_file);
#ifdef __FreeBSD__
                        system("vidcontrol -f " FONTDIR "/" FONTFILE " >/dev/null 2>&1");
#else
                        system("setfont " FONTDIR "/" FONTFILE " >/dev/null 2>&1");
#endif
                    } else {
                        // if not it might still be available, we dont know, must try
#ifdef __FreeBSD__
                        system("vidcontrol -f " FONTFILE " >/dev/null 2>&1");
#else
                        system("setfont " FONTFILE " >/dev/null 2>&1");
#endif
                    }
#endif // CAVAFONT
#ifndef __FreeBSD__
                    if (p.disable_blanking)
                        system("setterm -blank 0");
#endif
                }
                if (p.orientation != ORIENT_BOTTOM) {
                    cleanup(p.output);
                    fprintf(stderr, "only default bottom orientation is supported in tty\n");
                    exit(EXIT_FAILURE);
                }
            }

            // We use unicode block characters to draw the bars and
            // the locale var LANG must be set to use unicode chars.
            // For some reason this var can't be retrieved with
            // setlocale(LANG, NULL), so we get it with getenv.
            // Also we can't set it with setlocale(LANG "") so we
            // must set LC_ALL instead.
            // Attempting to set to en_US if not set, if that lang
            // is not installed and LANG is not set there will be
            // no output, for more info see #109 #344
            if (!getenv("LANG"))
                setlocale(LC_ALL, "en_US.utf8");
            else
                setlocale(LC_ALL, "");
        }
#endif
        // input: init

        struct audio_data audio;
        struct audio_raw audio_raw;
        struct cava_plan *plan = NULL;

        memset(&audio, 0, sizeof(audio));
        memset(&audio_raw, 0, sizeof(audio_raw));

        audio.format = -1;
        audio.rate = 0;
        audio.samples_counter = 0;
        audio.channels = 2;
        audio.IEEE_FLOAT = 0;
        audio.autoconnect = 0;

        audio.input_buffer_size = BUFFER_SIZE * audio.channels;
        audio.cava_buffer_size = 16384; // this is the size at rates of 44100 or 48k, will be
                                        // adjusted later if sample rate is unusual

        audio.threadparams = 0; // most input threads don't adjust the parameters
        audio.terminate = 0;

        pthread_t p_thread;
        int timeout_counter = 0;
        int total_bar_height = 0;

        struct timespec timeout_timer = {.tv_sec = 0, .tv_nsec = 1000000};
        int thr_id GCC_UNUSED;

        // Run audio source reader
        pthread_mutex_init(&audio.lock, NULL);
        thr_id = pthread_create(&p_thread, NULL, get_input(&audio, &p), (void *)&audio);

        timeout_counter = 0;
        while (true) {
#ifdef _WIN32
            Sleep(1);
#else
            nanosleep(&timeout_timer, NULL);
#endif
            pthread_mutex_lock(&audio.lock);
            if ((audio.threadparams == 0) && (audio.format != -1) && (audio.rate != 0))
                break;

            pthread_mutex_unlock(&audio.lock);
            timeout_counter++;
            if (timeout_counter > 5000) {
                cleanup(p.output);
                fprintf(stderr, "could not get rate and/or format, problems with audio thread? "
                                "quitting...\n");
                exit(EXIT_FAILURE);
            }
        }
        pthread_mutex_unlock(&audio.lock);

#ifdef SDL
        // output: start sdl mode
        if (p.output == OUTPUT_SDL) {
            init_sdl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y, p.sdl_full_screen);
            audio_raw.height = p.sdl_height;
            audio_raw.width = p.sdl_width;
        }
#endif
#ifdef SDL_GLSL
        if (p.output == OUTPUT_SDL_GLSL) {
            init_sdl_glsl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y, p.sdl_full_screen,
                                 p.vertex_shader, p.fragment_shader);
            audio_raw.height = p.sdl_height;
            audio_raw.width = p.sdl_width;
        }
#endif

        bool reloadConf = false;
        while (!reloadConf) { // jumping back to this loop means that you resized the screen
            // Initialize audio raw data structures
            audio_raw_init(&audio, &audio_raw, &p, &plan);

            bool resizeTerminal = false;

            long long frame_time_ns = (long long)(1000000000.0 / (double)p.framerate);
            if (frame_time_ns < 1)
                frame_time_ns = 1;
            struct timespec sleep_timer = {.tv_sec = frame_time_ns / 1000000000,
                                           .tv_nsec = frame_time_ns % 1000000000};
            int frame_time_msec = (int)(frame_time_ns / 1000000LL);
            if (frame_time_msec < 1)
                frame_time_msec = 1;

#ifdef _WIN32
            LARGE_INTEGER frequency; // ticks per second
            LARGE_INTEGER t1, t2;    // ticks
            double elapsedTime;
            QueryPerformanceFrequency(&frequency);
#else
            struct timespec t1, t2, t3;
            double elapsedTimens;
            double wakeupTimens = 0.0;
#endif // _WIN32

            int sleep_counter = 0;
            bool silence = false;
            char ch = '\0';

            struct timespec sleep_mode_timer = {.tv_sec = 1, .tv_nsec = 0};

            int total_frames = 0;

            // audio samples to use per visual frame.
            int samples_per_frame = audio.rate / p.framerate;

            // on sdl we use SDL_delay instead of our own sleep and it is in whole milliseconds, so
            // we need to adjust a little
            if (p.output == OUTPUT_SDL_GLSL || p.output == OUTPUT_SDL) {
                float actual_framerate = 1000.0 / (float)frame_time_msec;
                samples_per_frame = audio.rate / actual_framerate;
            }

            // check if drawing loop is faster than audio loop.
            int high_framerate = 0;
            if (samples_per_frame < audio.input_buffer_size / (int)audio.channels) {
                high_framerate = 1;
            }

            pthread_mutex_lock(&audio.lock);
            audio.samples_counter = 0;
            pthread_mutex_unlock(&audio.lock);

            while (!resizeTerminal) {
#ifdef _WIN32
                QueryPerformanceCounter(&t1);
#else
                clock_gettime(CLOCK_MONOTONIC, &t1);
#endif

// general: keyboard controls
#ifdef NCURSES
                if (p.output == OUTPUT_NCURSES)
                    ch = getch();
#endif

#ifndef _WIN32
                if (p.output == OUTPUT_NONCURSES)
                    read(0, &ch, sizeof(ch));
#endif

                switch (ch) {
                case 65: // key up
                    p.sens = p.sens * 1.05;
                    break;
                case 66: // key down
                    p.sens = p.sens * 0.95;
                    break;
                case 68: // key right
                    p.bar_width++;
                    resizeTerminal = true;
                    break;
                case 67: // key left
                    if (p.bar_width > 1)
                        p.bar_width--;
                    resizeTerminal = true;
                    break;
                case 'r': // reload config
                    should_reload = 1;
                    break;
                case 'c': // reload colors
                    reload_colors = 1;
                    break;
                case 'f': // change foreground color
                    if (p.col < 7)
                        p.col++;
                    else
                        p.col = 0;
                    resizeTerminal = true;
                    break;
                case 'b': // change background color
                    if (p.bgcol < 7)
                        p.bgcol++;
                    else
                        p.bgcol = 0;
                    resizeTerminal = true;
                    break;
                case 'o': // change orientation
                    p.orientation++;
                    if (p.output == OUTPUT_NONCURSES) {
                        if (p.orientation > ORIENT_SPLIT_V) {
                            p.orientation = ORIENT_BOTTOM;
                        }
                    } else if (p.output == OUTPUT_NCURSES) {
                        if (p.orientation > ORIENT_RIGHT) {
                            p.orientation = ORIENT_BOTTOM;
                        }
                    } else {
                        p.orientation =
                            (p.orientation == ORIENT_BOTTOM) ? ORIENT_TOP : ORIENT_BOTTOM;
                    }

                    if (p.orientation == ORIENT_LEFT || p.orientation == ORIENT_RIGHT ||
                        p.orientation == ORIENT_SPLIT_V) {
                        audio_raw.dimension_bar = &audio_raw.height;
                        audio_raw.dimension_value = &audio_raw.width;
                    } else {
                        audio_raw.dimension_bar = &audio_raw.width;
                        audio_raw.dimension_value = &audio_raw.height;
                    }

                    resizeTerminal = true;
                    break;

                case 'q':
                    should_reload = 1;
                    should_quit = 1;
                }

                ch = 0;

                if (should_reload) {
                    reloadConf = true;
                    resizeTerminal = true;
                    should_reload = 0;
                }

                if (reload_colors) {
                    struct error_s error;
                    char *themeFile;
                    error.length = 0;
                    bool result = get_themeFile(configPath, &p, NULL, &error, &themeFile);
                    if (!result) {
                        cleanup(p.output);
                        exit(EXIT_FAILURE);
                    }
                    if (!load_colors(themeFile, (void *)&p, &error)) {
                        cleanup(p.output);
                        free(themeFile);
                        fprintf(stderr, "Error loading config. %s", error.message);
                        exit(EXIT_FAILURE);
                    }
                    resizeTerminal = true;
                    reload_colors = 0;
                    free(themeFile);
                    break;
                }

                if (resizeTerminal)
                    break;

                // checking if audio thread has exited unexpectedly
                pthread_mutex_lock(&audio.lock);
                if (audio.terminate == 1) {
                    cleanup(p.output);
                    fprintf(stderr, "Audio thread exited unexpectedly. %s\n", audio.error_message);
                    exit(EXIT_FAILURE);
                }
                pthread_mutex_unlock(&audio.lock);

                // process: check if input is present
                silence = true;

                for (int n = 0; n < audio.samples_counter; n++) {
                    if (audio.cava_in[n]) {
                        silence = false;
                        break;
                    }
                }
#ifndef _WIN32

                if (p.output != OUTPUT_SDL) {
                    if (p.sleep_timer) {
                        int effective_framerate = p.framerate;
                        if (effective_framerate <= 0)
                            effective_framerate = 60;
                        int sleep_limit = effective_framerate * p.sleep_timer;
                        if (silence && sleep_counter <= sleep_limit)
                            sleep_counter++;
                        else if (!silence)
                            sleep_counter = 0;

                        if (sleep_counter > sleep_limit) {
                            nanosleep(&sleep_mode_timer, NULL);
                            if (p.live_config) {
                                time_t new_mtime = 0;
                                long long new_size = 0;
                                if (get_file_state(configPath, &new_mtime, &new_size) &&
                                    (!has_config_state || new_mtime != config_mtime ||
                                     new_size != config_size)) {
                                    should_reload = 1;
                                    config_mtime = new_mtime;
                                    config_size = new_size;
                                    has_config_state = true;
                                }
                            }
                            continue;
                        }
                    }
                }

#endif // !_WIN32

                // process: execute cava
                pthread_mutex_lock(&audio.lock);

                int samples_to_use = 0;

                if (!high_framerate) {
                    // we dont need buffers and use all available samples.
                    samples_to_use = audio.samples_counter;
                } else {
                    // we are running with higher framerates and need buffers!
                    // only use the calculated samples per frame
                    samples_to_use = samples_per_frame * audio.channels;

                    // buffer underrun! only use what we have
                    if (audio.samples_counter < samples_to_use) {
                        samples_to_use = audio.samples_counter;
                    }

                    // buffer overflow! we have more samples than we need, just use them.
                    if (audio.samples_counter > audio.input_buffer_size + samples_to_use) {
                        samples_to_use = audio.samples_counter - audio.input_buffer_size;
                    }
                }

                if (p.waveform) {
                    for (int n = 0; n < samples_to_use; n++) {

                        for (int i = audio_raw.number_of_bars - 1; i > 0; i--) {
                            audio_raw.cava_out[i] = audio_raw.cava_out[i - 1];
                        }
                        if (audio.channels == 2) {
                            audio_raw.cava_out[0] =
                                p.sens * (audio.cava_in[n] / 2 + audio.cava_in[n + 1] / 2);
                            n++;
                        } else {
                            audio_raw.cava_out[0] = p.sens * audio.cava_in[n];
                        }
                    }
                } else {
                    cava_execute(audio.cava_in, samples_to_use, audio_raw.cava_out, plan);
                }

                audio.samples_counter -= samples_to_use;

                if (audio.samples_counter != 0) {
                    // shift the input buffer
                    for (int n = 0; n < audio.samples_counter; n++) {
                        audio.cava_in[n] = audio.cava_in[n + samples_to_use];
                    }
                }

                pthread_mutex_unlock(&audio.lock);
#ifndef SDL_GLSL
                // Do transformation under raw data
                audio_raw_fetch(&audio_raw, &p, plan);
#else
                int re_paint = 0;
                // Do transformation under raw data
                audio_raw_fetch(&audio_raw, &p, &re_paint, plan);
#endif

                // output: draw processed input
                if (p.sync_updates) {
                    printf("\033[2026h\033\\");
                    fflush(stdout);
                    printf("\033[2026l\033\\");
                }
                int rc = 0;
                switch (p.output) {
#ifdef SDL
                case OUTPUT_SDL:
                    rc = draw_sdl(audio_raw.number_of_bars, p.bar_width, p.bar_spacing,
                                  audio_raw.remainder, *audio_raw.dimension_value, audio_raw.bars,
                                  audio_raw.previous_frame, frame_time_msec, p.orientation,
                                  p.gradient);

                    break;
#endif
#ifdef SDL_GLSL
                case OUTPUT_SDL_GLSL:
                    rc = draw_sdl_glsl(audio_raw.number_of_bars, audio_raw.bars_raw,
                                       audio_raw.previous_bars_raw, frame_time_msec, re_paint,
                                       p.continuous_rendering);
                    break;
#endif
                case OUTPUT_NONCURSES:
                    if (p.orientation == ORIENT_SPLIT_H || p.orientation == ORIENT_SPLIT_V) {
                        // in split horizontal mode we need to draw two times
                        // once for bottom and once for top
                        // ORIENT_BOTTOM will be the top bars and ORIENT_TOP the bottom bars
                        // since bottom hear means from mid upwards and top is from mid
                        // downwards

                        if (p.split_stereo) {
                            // in horizontal stereo mode we need to split the bars array in half
                            // first half is right channel, second half is left channel
                            for (int i = 0; i < audio_raw.number_of_bars / 2; i++) {
                                audio_raw.right_bars[i] =
                                    audio_raw.bars[i + audio_raw.number_of_bars / 2];
                                audio_raw.right_previous_frame[i] =
                                    audio_raw.previous_frame[i + audio_raw.number_of_bars / 2];
                            }
                            if (p.orientation == ORIENT_SPLIT_H) {
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame,
                                                             ORIENT_BOTTOM, &p, &audio_raw);
                                rc = draw_terminal_noncurses(audio_raw.right_bars,
                                                             audio_raw.right_previous_frame,
                                                             ORIENT_TOP, &p, &audio_raw);
                            }
                            if (p.orientation == ORIENT_SPLIT_V) {
                                rc = draw_terminal_noncurses(audio_raw.right_bars,
                                                             audio_raw.right_previous_frame,
                                                             ORIENT_LEFT, &p, &audio_raw);
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame, ORIENT_RIGHT,
                                                             &p, &audio_raw);
                            }

                        } else {
                            // pure mirrored split, same bars for both sides
                            if (p.orientation == ORIENT_SPLIT_H) {
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame,
                                                             ORIENT_BOTTOM, &p, &audio_raw);
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame, ORIENT_TOP,
                                                             &p, &audio_raw);
                            } else if (p.orientation == ORIENT_SPLIT_V) {
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame, ORIENT_LEFT,
                                                             &p, &audio_raw);
                                rc = draw_terminal_noncurses(audio_raw.bars,
                                                             audio_raw.previous_frame, ORIENT_RIGHT,
                                                             &p, &audio_raw);
                            }
                        }
                    } else {
                        rc = draw_terminal_noncurses(audio_raw.bars, audio_raw.previous_frame,
                                                     p.orientation, &p, &audio_raw);
                    }
                    break;
                case OUTPUT_NCURSES:
#ifdef NCURSES
                    rc = draw_terminal_ncurses(p.inAtty, *audio_raw.dimension_value / 8,
                                               *audio_raw.dimension_bar, audio_raw.number_of_bars,
                                               p.bar_width, p.bar_spacing, audio_raw.remainder,
                                               audio_raw.bars, audio_raw.previous_frame, p.gradient,
                                               p.xaxis, p.orientation);
                    break;
#endif
                case OUTPUT_RAW:
#ifndef _WIN32
                    rc = print_raw_out(audio_raw.number_of_bars, audio_raw.fp, p.raw_format,
                                       p.bit_format, p.ascii_range, p.bar_delim, p.frame_delim,
                                       audio_raw.bars);
#else
                    rc =
                        print_raw_out(audio_raw.number_of_bars, p.hFile, p.raw_format, p.bit_format,
                                      p.ascii_range, p.bar_delim, p.frame_delim, audio_raw.bars);
#endif
                    break;
                case OUTPUT_NORITAKE:
#ifndef _WIN32
                    rc = print_ntk_out(audio_raw.number_of_bars, audio_raw.fp, p.bit_format,
                                       p.bar_width, p.bar_spacing, p.bar_height, audio_raw.bars);
#else
                    rc = print_ntk_out(audio_raw.number_of_bars, p.hFile, p.bit_format, p.bar_width,
                                       p.bar_spacing, p.bar_height, audio_raw.bars);
#endif
                    break;
                default:
                    exit(EXIT_FAILURE); // Can't happen.
                }

                if (p.sync_updates) {
                    printf("\033[2026h\033\\");
                    fflush(stdout);
                    printf("\033[2026l\033\\");
                }
                // terminal has been resized breaking to recalibrating values
                if (rc == -1)
                    resizeTerminal = true;

                if (rc == -2) {
                    resizeTerminal = true;
                    reloadConf = true;
                    should_quit = true;
                }

                memcpy(audio_raw.previous_frame, audio_raw.bars,
                       audio_raw.number_of_bars * sizeof(int));
                if (p.output == OUTPUT_SDL_GLSL) {
                    memcpy(audio_raw.previous_bars_raw, audio_raw.bars_raw,
                           audio_raw.number_of_bars * sizeof(float));
                }

                if (p.live_config) {
                    time_t new_mtime = 0;
                    long long new_size = 0;
                    if (get_file_state(configPath, &new_mtime, &new_size) &&
                        (!has_config_state || new_mtime != config_mtime ||
                         new_size != config_size)) {
                        should_reload = 1;
                        config_mtime = new_mtime;
                        config_size = new_size;
                        has_config_state = true;
                    }
                }

                if (p.draw_and_quit > 0) {
                    total_frames++;
                    if (total_frames >= p.draw_and_quit) {
                        for (int n = 0; n < audio_raw.number_of_bars; n++) {
                            if (p.output != OUTPUT_RAW && p.output != OUTPUT_NORITAKE &&
                                audio_raw.bars[n] == 1) {
                                audio_raw.bars[n] = 0;
                            }
                            total_bar_height += audio_raw.bars[n];
                        }
                        resizeTerminal = true;
                        reloadConf = true;
                        should_quit = true;
                        break;
                    }
                }
#ifdef _WIN32
                QueryPerformanceCounter(&t2);
                elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
                int fps_sync_time = frame_time_msec;
                if (elapsedTime < 1.0)
                    fps_sync_time = frame_time_msec;
                else if ((int)elapsedTime > frame_time_msec)
                    fps_sync_time = 0;
                else
                    fps_sync_time = (frame_time_msec - (int)elapsedTime) / 2;
#endif
                if (p.output != OUTPUT_SDL && p.output != OUTPUT_SDL_GLSL) {
#ifdef _WIN32
                    Sleep(fps_sync_time);
#else
                    clock_gettime(CLOCK_MONOTONIC, &t2);
                    elapsedTimens =
                        (t2.tv_sec - t1.tv_sec) * 1000000000.0 + (t2.tv_nsec - t1.tv_nsec);

                    int sleep_time_ns = frame_time_ns - (int)elapsedTimens - (int)wakeupTimens;

                    if (sleep_time_ns > 1) {

                        sleep_timer.tv_sec = sleep_time_ns / 1000000000;
                        sleep_timer.tv_nsec = sleep_time_ns % 1000000000;
                        nanosleep(&sleep_timer, NULL);
                        clock_gettime(CLOCK_MONOTONIC, &t3);
                        double actualSleeptime =
                            (t3.tv_sec - t2.tv_sec) * 1000000000.0 + (t3.tv_nsec - t2.tv_nsec);
                        wakeupTimens = actualSleeptime - sleep_time_ns;
                    } else {
                        wakeupTimens = 0;
                    }

#endif
                }
            } // resize terminal
            cava_destroy(plan);
            audio_raw_clean(&audio_raw);

#ifndef _WIN32
            if ((p.output == OUTPUT_RAW || p.output == OUTPUT_NORITAKE) &&
                strcmp(p.raw_target, "/dev/stdout") != 0) {
                if (audio_raw.close_output_fd && audio_raw.fp != -1) {
                    close(audio_raw.fp);
                    audio_raw.fp = -1;
                    audio_raw.close_output_fd = false;
                }
                if (audio_raw.keepalive_fd != -1) {
                    close(audio_raw.keepalive_fd);
                    audio_raw.keepalive_fd = -1;
                }
            }
#endif
        } // reloading config

        //**telling audio thread to terminate**//
        pthread_mutex_lock(&audio.lock);
        audio.terminate = 1;
        pthread_mutex_unlock(&audio.lock);
        pthread_join(p_thread, NULL);

        pthread_mutex_destroy(&audio.lock);

        free(audio.source);
        free(audio.cava_in);
        cleanup(p.output);
        free_config(&p);

        if (should_quit && signal_received == 0) {
            if (p.zero_test && total_bar_height > 0) {
                fprintf(stderr, "Test mode: expected total bar height to be zero, but was: %d\n",
                        total_bar_height);
                return EXIT_FAILURE;
            } else if (p.non_zero_test && total_bar_height == 0) {
                fprintf(stderr,
                        "Test mode: expected total bar height to be non-zero, but was zero\n");
                return EXIT_FAILURE;
            } else {
                return EXIT_SUCCESS;
            }
        }
        if (signal_received != 0) {
            signal(signal_received, SIG_DFL);
            raise(signal_received);
        }
        // fclose(fp);
        // CloseHandle(hFile);
    }
}
