/*
 * Copyright (C) 2003-2010 Neverball authors
 *
 * NEVERBALL 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.
 *
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "fs.h"
#include "dir.h"
#include "array.h"
#include "common.h"

/*
 * This file implements the high-level virtual file system layer
 * routines.
 */

/*---------------------------------------------------------------------------*/

static int cmp_dir_items(const void *A, const void *B)
{
    const struct dir_item *a = A, *b = B;
    return strcmp(a->path, b->path);
}

static int is_archive(struct dir_item *item)
{
    return (str_ends_with(item->path, ".zip") ||
            str_ends_with(item->path, ".pk3"));
}

static void add_archives(const char *path)
{
    Array archives;
    int i;

    if ((archives = dir_scan(path, is_archive, NULL, NULL)))
    {
        array_sort(archives, cmp_dir_items);

        for (i = 0; i < array_len(archives); i++)
            fs_add_path(DIR_ITEM_GET(archives, i)->path);

        dir_free(archives);
    }
}

int fs_add_path_with_archives(const char *path)
{
    add_archives(path);
    return fs_add_path(path);
}

/*---------------------------------------------------------------------------*/

int fs_rename(const char *src, const char *dst)
{
    const char *write_dir;
    char *real_src, *real_dst;
    int rc = 0;

    if ((write_dir = fs_get_write_dir()))
    {
        real_src = concat_string(write_dir, "/", src, NULL);
        real_dst = concat_string(write_dir, "/", dst, NULL);

        rc = file_rename(real_src, real_dst);

        free(real_src);
        free(real_dst);
    }

    return rc;
}

/*---------------------------------------------------------------------------*/

int fs_getc(fs_file fh)
{
    unsigned char c;

    if (fs_read(&c, 1, 1, fh) != 1)
        return -1;

    return (int) c;
}

int fs_putc(int c, fs_file fh)
{
    unsigned char b = (unsigned char) c;

    if (fs_write(&b, 1, 1, fh) != 1)
        return -1;

    return b;
}

int fs_puts(const char *src, fs_file fh)
{
    while (*src)
        if (fs_putc(*src++, fh) < 0)
            return -1;

    return 0;
}

char *fs_gets(char *dst, int count, fs_file fh)
{
    char *s = dst;
    int c;

    assert(dst);
    assert(count > 0);

    if (fs_eof(fh))
        return NULL;

    while (count > 1)
        if ((c = fs_getc(fh)) >= 0)
        {
            count--;

            *s = c;

            /* Keep a newline and break. */

            if (*s == '\n')
            {
                s++;
                break;
            }

            /* Ignore carriage returns. */

            if (*s == '\r')
            {
                count++;
                s--;
            }

            s++;
        }
        else if (s == dst)
            return NULL;
        else
            break;

    *s = '\0';

    return dst;
}

/*---------------------------------------------------------------------------*/

/*
 * Write out a multiline string to a file with appropriately converted
 * linefeed characters.
 */
static int write_lines(const char *start, int length, fs_file fh)
{
#ifdef _WIN32
    static const char crlf[] = "\r\n";
#else
    static const char crlf[] = "\n";
#endif

    int total_written = 0;

    int datalen;
    int written;
    char *lf;

    while (total_written < length)
    {
        lf = strchr(start, '\n');

        datalen = lf ? (int) (lf - start) : length - total_written;
        written = fs_write(start, 1, datalen, fh);

        if (written < 0)
            break;

        total_written += written;

        if (written < datalen)
            break;

        if (lf)
        {
            if (fs_puts(crlf, fh) < 0)
                break;

            total_written += 1;
            start = lf + 1;
        }
    }

    return total_written;
}

/*---------------------------------------------------------------------------*/

int fs_printf(fs_file fh, const char *fmt, ...)
{
    char *buff;
    int len;

    va_list ap;

    va_start(ap, fmt);
    len = 1 + vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);

    if ((buff = malloc(len)))
    {
        int written;

        va_start(ap, fmt);
        vsnprintf(buff, len, fmt, ap);
        va_end(ap);

        /*
         * HACK.  This assumes fs_printf is always called with the
         * intention of writing text, and not arbitrary data.
         */

        written = write_lines(buff, strlen(buff), fh);

        free(buff);

        return written;
    }

    return 0;
}

/*---------------------------------------------------------------------------*/

void *fs_load(const char *path, int *datalen)
{
    fs_file fh;
    void *data;

    data = NULL;

    if ((*datalen = fs_size(path)) > 0)
        if ((fh = fs_open_read(path)))
        {
            if ((data = malloc(*datalen)))
            {
                if (fs_read(data, *datalen, 1, fh) != 1)
                {
                    free(data);
                    data = NULL;
                    *datalen = 0;
                }
            }

            fs_close(fh);
        }

    return data;
}

/*---------------------------------------------------------------------------*/

/*
 * Convert a system path into a VFS path.
 */
const char *fs_resolve(const char *system)
{
    static char path[MAXSTR];

    const char *p;

    /*
     * PhysicsFS will claim a file doesn't exist if its path uses a
     * directory separator other than a forward slash, even if that
     * separator is valid for the system. We'll oblige.
     */

    SAFECPY(path, system);

    path_normalize(path);

    if (fs_exists(path))
        return path;

    /* Chop off directories until we have a match. */

    p = path;

    while ((p = path_next_sep(p)))
    {
        /* Skip separator. */

        p += 1;

        if (fs_exists(p))
            return p;
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/
