/*
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * See the COPYING file for license information.
 *
 * Guillaume Chazarain <guichaz@yahoo.fr>
 */

/*****************************************
 * A simplified ftw(3), specific to GLiv *
 *****************************************/

#include <sys/stat.h>           /* struct stat, stat() */
#include <dirent.h>             /* struct dirent, *dir() */
#include <stdio.h>              /* perror() */

#include "gliv.h"
#include "foreach_file.h"
#include "pathset.h"

/* Push directories and handle files. */
static GSList *process_dir(GSList * stack, const gchar * dirname,
                           foreach_file_func func, gint * res)
{
    DIR *dir;
    struct dirent *dir_ent;
    struct stat st;
    gchar *name, *fullname;
    gboolean is_dir = FALSE, is_dir_known;

    dir = opendir(dirname);
    if (dir == NULL) {
        perror(dirname);
        return stack;
    }

    for (dir_ent = readdir(dir); dir_ent != NULL; dir_ent = readdir(dir)) {
        name = dir_ent->d_name;

        if (name[0] == '.' &&
            (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
            /* Skip "." and "..". */
            continue;

        fullname = g_build_filename(dirname, name, NULL);
        is_dir_known = FALSE;

#ifdef _DIRENT_HAVE_D_TYPE
        switch (dir_ent->d_type) {
        case DT_UNKNOWN:
        case DT_LNK:
            break;

        case DT_DIR:
            is_dir = TRUE;
            is_dir_known = TRUE;
            break;

        default:
            is_dir = FALSE;
            is_dir_known = TRUE;
            break;
        }
#endif

        if (is_dir_known == FALSE && !stat(fullname, &st)) {
            is_dir = S_ISDIR(st.st_mode);
            is_dir_known = TRUE;
        }

        if (is_dir_known) {
            if (is_dir)
                /* A directory. */
                stack = g_slist_prepend(stack, fullname);
            else {
                /* A file. */
                *res += (*func) (fullname);
                g_free(fullname);
            }
        }
    }

    closedir(dir);
    return stack;
}

/*
 * We know that func is add_file_to_list and
 * we return the number of inserted files.
 */
gint foreach_file(const gchar * path, foreach_file_func func)
{
    GSList *stack = NULL;
    gchar *current;
    struct pathset *set;
    struct stat st;
    gint res = 0;

    if (stat(path, &st) < 0) {
        /* The path is not usable. */
        perror(path);
        return 0;
    }

    if (S_ISDIR(st.st_mode) == FALSE)
        /* The path is a file, not a directory. */
        return (*func) (path);

    /* The path is a valid directory. */

    stack = g_slist_prepend(stack, g_strdup(path));     /* push */

    set = pathset_new();

    while (stack) {
        current = stack->data;
        stack = g_slist_remove_link(stack, stack);      /* pop */

        if (!stat(current, &st) && pathset_add(set, current))
            /* Not already scanned. */
            stack = process_dir(stack, current, func, &res);

        g_free(current);
    }

    pathset_free(set);

    return res;
}
