/*
 * Apple // emulator for *ix
 *
 * This software package is subject to the GNU General Public License
 * version 3 or later (your choice) as published by the Free Software
 * Foundation.
 *
 * Copyright 1994 Alexander Jean-Claude Bottema
 * Copyright 1995 Stephen Lee
 * Copyright 1997, 1998 Aaron Culliney
 * Copyright 1998, 1999, 2000 Michael Deutschmann
 * Copyright 2013-2015 Aaron Culliney
 *
 */

#include "common.h"

#define SAVE_MAGICK "A2VM"
#define SAVE_MAGICK_LEN sizeof(SAVE_MAGICK)

bool do_logging = true; // also controlled by NDEBUG
FILE *error_log = NULL;
int sound_volume = 2;
color_mode_t color_mode = COLOR;
const char *data_dir = NULL;
char **argv = NULL;
int argc = 0;
CrashHandler_s *crashHandler = NULL;

__attribute__((constructor(CTOR_PRIORITY_FIRST)))
static void _init_common(void) {
#if defined(CONFIG_DATADIR)
    data_dir = strdup(CONFIG_DATADIR PATH_SEPARATOR PACKAGE_NAME);
#elif defined(ANDROID)
    // data_dir is set up in JNI
#elif !defined(__APPLE__)
#   error "Specify a CONFIG_DATADIR and PACKAGE_NAME"
#endif
}

static bool _save_state(int fd, const uint8_t * outbuf, ssize_t outmax) {
    ssize_t outlen = 0;
    do {
        if (TEMP_FAILURE_RETRY(outlen = write(fd, outbuf, outmax)) == -1) {
            ERRLOG("error writing emulator save state file");
            break;
        }
        outbuf += outlen;
        outmax -= outlen;
    } while (outmax > 0);

    return outmax == 0;
}

static bool _load_state(int fd, uint8_t * inbuf, ssize_t inmax) {
    ssize_t inlen = 0;
    do {
        if (TEMP_FAILURE_RETRY(inlen = read(fd, inbuf, inmax)) == -1) {
            ERRLOG("error reading emulator save state file");
            break;
        }
        if (inlen == 0) {
            ERRLOG("error reading emulator save state file (truncated)");
            break;
        }
        inbuf += inlen;
        inmax -= inlen;
    } while (inmax > 0);

    return inmax == 0;
}

bool emulator_saveState(const char * const path) {
    int fd = -1;
    bool saved = false;

    assert(cpu_isPaused() && "should be paused to save state");

    do {
        TEMP_FAILURE_RETRY(fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR));
        if (fd < 0) {
            break;
        }
        assert(fd != 0 && "crazy platform");

        // save header
        if (!_save_state(fd, SAVE_MAGICK, SAVE_MAGICK_LEN)) {
            break;
        }

        StateHelper_s helper = {
            .fd = fd,
            .save = &_save_state,
            .load = &_load_state,
        };

        if (!disk6_saveState(&helper)) {
            break;
        }

        if (!vm_saveState(&helper)) {
            break;
        }

        if (!cpu65_saveState(&helper)) {
            break;
        }

        if (!video_saveState(&helper)) {
            break;
        }

        TEMP_FAILURE_RETRY(fsync(fd));
        saved = true;
    } while (0);

    if (fd >= 0) {
        TEMP_FAILURE_RETRY(close(fd));
    }

    if (!saved) {
        ERRLOG("could not write to the emulator save state file");
        unlink(path);
    }

    return saved;
}

bool emulator_loadState(const char * const path) {
    int fd = -1;
    bool loaded = false;

    assert(cpu_isPaused() && "should be paused to load state");

    do {
        TEMP_FAILURE_RETRY(fd = open(path, O_RDONLY));
        if (fd < 0) {
            break;
        }
        assert(fd != 0 && "crazy platform");

        // load header
        uint8_t magick[SAVE_MAGICK_LEN] = { 0 };
        if (!_load_state(fd, magick, SAVE_MAGICK_LEN)) {
            break;
        }

        // check header
        if (memcmp(magick, SAVE_MAGICK, SAVE_MAGICK_LEN) != 0) {
            ERRLOG("bad header magick in emulator save state file");
            break;
        }

        StateHelper_s helper = {
            .fd = fd,
            .save = &_save_state,
            .load = &_load_state,
        };

        if (!disk6_loadState(&helper)) {
            break;
        }

        if (!vm_loadState(&helper)) {
            break;
        }

        if (!cpu65_loadState(&helper)) {
            break;
        }

        if (!video_loadState(&helper)) {
            break;
        }

        loaded = true;
    } while (0);

    if (fd >= 0) {
        TEMP_FAILURE_RETRY(close(fd));
    }

    if (!loaded) {
        ERRLOG("could not load emulator save state file");
    }

    return loaded;
}

static void _shutdown_threads(void) {
#if !TESTING
#   if defined(__linux__) && !defined(ANDROID)
    LOG("Emulator waiting for other threads to clean up...");
    do {
        DIR *dir = opendir("/proc/self/task");
        if (!dir) {
            ERRLOG("Cannot open /proc/self/task !");
            break;
        }

        int thread_count = 0;
        struct dirent *d = NULL;
        while ((d = readdir(dir)) != NULL) {
            if (strncmp(".", d->d_name, 2) == 0) {
                // ignore
            } else if (strncmp("..", d->d_name, 3) == 0) {
                // ignore
            } else {
                ++thread_count;
            }
        }

        closedir(dir);

        assert(thread_count >= 1 && "there must at least be one thread =P");
        if (thread_count == 1) {
            break;
        }

        static struct timespec ts = { .tv_sec=0, .tv_nsec=33333333 };
        nanosleep(&ts, NULL); // 30Hz framerate
    } while (1);
#   endif
#endif
}

void emulator_start(void) {
#ifdef INTERFACE_CLASSIC
    load_settings(); // user prefs
    c_keys_set_key(kF8); // show credits before emulation start
#endif
    video_init();
    timing_startCPU();
    video_main_loop();
}

void emulator_shutdown(void) {
    video_shutdown();
    timing_stopCPU();
    _shutdown_threads();
}

#if !TESTING && !defined(__APPLE__) && !defined(ANDROID)
int main(int _argc, char **_argv) {
    argc = _argc;
    argv = _argv;

    emulator_start();

    // main loop ...

    emulator_shutdown();

    LOG("Emulator exit ...");

    return 0;
}
#endif

