/*
 * $Id: file_ruby18.c,v 1.1 2002/09/11 11:25:49 knu Exp $
 */

#include "ruby.h"
#include "config.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#define FNM_NOESCAPE	0x01
#define FNM_PATHNAME	0x02
#define FNM_DOTMATCH	0x04
#define FNM_CASEFOLD	0x08

#define FNM_NOMATCH	1
#define FNM_ERROR	2

#define downcase(c) (nocase && ISUPPER(c) ? tolower(c) : (c))

#ifndef CharNext		/* defined as CharNext[AW] on Windows. */
# if defined(DJGPP)
#   define CharNext(p) ((p) + mblen(p, MB_CUR_MAX))
# else
#   define CharNext(p) ((p) + 1)
# endif
#endif
#if defined DOSISH
#define isdirsep(c) ((c) == '/' || (c) == '\\')
static char *
find_dirsep(s)
    char *s;
{
    while (*s) {
	if (isdirsep(*s))
	    return s;
	s = CharNext(s);
    }
    return 0;
}
#else
#define isdirsep(c) ((c) == '/')
#define find_dirsep(s) strchr(s, '/')
#endif

static char *
range(pat, test, flags)
    char *pat;
    char test;
    int flags;
{
    int not, ok = 0;
    int nocase = flags & FNM_CASEFOLD;
    int escape = !(flags & FNM_NOESCAPE);

    not = *pat == '!' || *pat == '^';
    if (not)
	pat++;

    test = downcase(test);

    while (*pat) {
	int cstart, cend;
	cstart = cend = *pat++;
	if (cstart == ']')
	    return ok == not ? 0 : pat;
        else if (escape && cstart == '\\')
	    cstart = cend = *pat++;
	if (*pat == '-' && pat[1] != ']') {
	    if (escape && pat[1] == '\\')
		pat++;
	    cend = pat[1];
	    if (!cend)
		return 0;
	    pat += 2;
	}
	if (downcase(cstart) <= test && test <= downcase(cend))
	    ok = 1;
    }
    return 0;
}

#define ISDIRSEP(c) (pathname && isdirsep(c))
#define PERIOD(s) (period && *(s) == '.' && \
		  ((s) == string || ISDIRSEP((s)[-1])))
static int
fnmatch(pat, string, flags)
    const char *pat;
    const char *string;
    int flags;
{
    int c;
    int test;
    const char *s = string;
    int escape = !(flags & FNM_NOESCAPE);
    int pathname = flags & FNM_PATHNAME;
    int period = !(flags & FNM_DOTMATCH);
    int nocase = flags & FNM_CASEFOLD;

    while (c = *pat++) {
	switch (c) {
	case '?':
	    if (!*s || ISDIRSEP(*s) || PERIOD(s))
		return FNM_NOMATCH;
	    s++;
	    break;
	case '*':
	    while ((c = *pat++) == '*')
		;

	    if (PERIOD(s))
		return FNM_NOMATCH;

	    if (!c) {
		if (pathname && find_dirsep(s))
		    return FNM_NOMATCH;
		else
		    return 0;
	    }
	    else if (ISDIRSEP(c)) {
		s = find_dirsep(s);
		if (s) {
                    s++;
		    break;
                }
		return FNM_NOMATCH;
	    }

	    test = escape && c == '\\' ? *pat : c;
	    test = downcase(test);
	    pat--;
	    while (*s) {
		if ((c == '[' || downcase(*s) == test) &&
		    !fnmatch(pat, s, flags | FNM_DOTMATCH))
		    return 0;
		else if (ISDIRSEP(*s))
		    break;
		s++;
	    }
	    return FNM_NOMATCH;
      
	case '[':
	    if (!*s || ISDIRSEP(*s) || PERIOD(s))
		return FNM_NOMATCH;
	    pat = range(pat, *s, flags);
	    if (!pat)
		return FNM_NOMATCH;
	    s++;
	    break;

	case '\\':
	    if (escape
#if defined DOSISH
		&& *pat && strchr("*?[\\", *pat)
#endif
		) {
		c = *pat;
		if (!c)
		    c = '\\';
		else
		    pat++;
	    }
	    /* FALLTHROUGH */

	default:
#if defined DOSISH
	    if (ISDIRSEP(c) && isdirsep(*s))
		;
	    else
#endif
	    if(downcase(c) != downcase(*s))
		return FNM_NOMATCH;
	    s++;
	    break;
	}
    }
    return !*s ? 0 : FNM_NOMATCH;
}

static int
apply2files(void (*func)(), VALUE vargs, void *arg)
{
    int i;
    VALUE path;
    struct RArray *args = RARRAY(vargs);

    for (i=0; i<args->len; i++) {
	Check_SafeStr(args->ptr[i]);
    }

    for (i=0; i<args->len; i++) {
	path = args->ptr[i];
	(*func)(RSTRING(path)->ptr, arg);
    }

    return args->len;
}

#if defined(HAVE_LCHMOD)
static void
lchmod_internal(const char *path, int mode)
{
    if (lchmod(path, mode) < 0)
	rb_sys_fail(path);
}

static VALUE
rb_file_s_lchmod(int argc, VALUE *argv)
{
    VALUE vmode;
    VALUE rest;
    int mode, n;

    rb_secure(2);
    rb_scan_args(argc, argv, "1*", &vmode, &rest);
    mode = NUM2INT(vmode);

    n = apply2files(lchmod_internal, rest, (void *)mode);
    return INT2FIX(n);
}
#else
static VALUE
rb_file_s_lchmod(int argc, VALUE *argv)
{
    rb_notimplement();
}
#endif

struct chown_args {
    int owner, group;
};

#if defined(HAVE_LCHOWN) && !defined(__CHECKER__)
static void
lchown_internal(const char *path, struct chown_args *args)
{
    if (lchown(path, args->owner, args->group) < 0)
	rb_sys_fail(path);
}

static VALUE
rb_file_s_lchown(int argc, VALUE *argv)
{
    VALUE o, g, rest;
    struct chown_args arg;
    int n;

    rb_secure(2);
    rb_scan_args(argc, argv, "2*", &o, &g, &rest);
    if (NIL_P(o)) {
	arg.owner = -1;
    }
    else {
	arg.owner = NUM2INT(o);
    }
    if (NIL_P(g)) {
	arg.group = -1;
    }
    else {
	arg.group = NUM2INT(g);
    }

    n = apply2files(lchown_internal, rest, &arg);
    return INT2FIX(n);
}
#else
static VALUE
rb_file_s_lchown(int argc, VALUE *argv)
{
    rb_notimplement();
}
#endif

static VALUE
rb_stat_rdev_major(self)
    VALUE self;
{
#if defined(HAVE_ST_RDEV) && defined(major)
    long rdev = FIX2LONG(rb_funcall(self, rb_intern("rdev"), 0));
    return INT2NUM(major(rdev));
#else
    return INT2FIX(0);
#endif
}

static VALUE
rb_stat_rdev_minor(self)
    VALUE self;
{
#if defined(HAVE_ST_RDEV) && defined(minor)
    long rdev = FIX2LONG(rb_funcall(self, rb_intern("rdev"), 0));
    return INT2NUM(minor(rdev));
#else
    return INT2FIX(0);
#endif
}

static VALUE
file_s_fnmatch(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    VALUE pattern, path;
    VALUE rflags;
    int flags;

    if (rb_scan_args(argc, argv, "21", &pattern, &path, &rflags) == 3)
	flags = NUM2INT(rflags);
    else
	flags = 0;

#ifdef StringValue
    StringValue(pattern);
    StringValue(path);
#else
    Check_Type(pattern, T_STRING);
    Check_Type(path, T_STRING);
#endif

    if (fnmatch(RSTRING(pattern)->ptr, RSTRING(path)->ptr, flags) == 0)
	return Qtrue;

    return Qfalse;
}

void
Init_fnmatch()
{
}

void
Init_file_ruby18()
{
    VALUE rb_cFileStat = rb_path2class("File::Stat");
    VALUE rb_mFConst = rb_path2class("File::Constants");

    rb_define_singleton_method(rb_cFile, "fnmatch", file_s_fnmatch, -1);
    rb_define_singleton_method(rb_cFile, "fnmatch?", file_s_fnmatch, -1);
    rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
    rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1);

    rb_define_method(rb_cFileStat, "rdev_major", rb_stat_rdev_major, 0);
    rb_define_method(rb_cFileStat, "rdev_minor", rb_stat_rdev_minor, 0);

    rb_define_const(rb_mFConst, "FNM_NOESCAPE", INT2FIX(FNM_NOESCAPE));
    rb_define_const(rb_mFConst, "FNM_PATHNAME", INT2FIX(FNM_PATHNAME));
    rb_define_const(rb_mFConst, "FNM_DOTMATCH", INT2FIX(FNM_DOTMATCH));
    rb_define_const(rb_mFConst, "FNM_CASEFOLD", INT2FIX(FNM_CASEFOLD));
}
