/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 shiftwidth=4: */
/* 
 * Copyright (C) 2008 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Lin Ma <lin.ma@sun.com>
 */

#include "config.h"
#include <glib.h>
#include "fen-data.h"
#include "fen-helper.h"
#include "fen-kernel.h"
#ifdef GIO_COMPILATION
#include "gfilemonitor.h"
#else
#include "gam_event.h"
#include "gam_server.h"
#include "gam_protocol.h"
#endif

#ifdef GIO_COMPILATION
#define FH_W if (fh_debug_enabled) g_warning
static gboolean fh_debug_enabled = FALSE;
#else
#include "gam_error.h"
#define FH_W(...) GAM_DEBUG(DEBUG_INFO, __VA_ARGS__)
#endif

G_LOCK_EXTERN (fen_lock);

static void default_emit_event_cb (fdata *f, int events);
static void default_emit_once_event_cb (fdata *f, int events, gpointer sub);
static int default_event_converter (int event);

static void
scan_children_init (node_t *f, gpointer sub)
{
	GDir *dir;
	GError *err = NULL;
    node_op_t op = {NULL, NULL, pre_del_cb, NULL};
    fdata* pdata;
    
    FH_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
    pdata = node_get_data (f);

    dir = g_dir_open (NODE_NAME(f), 0, &err);
    if (dir) {
        const char *basename;
        
        while ((basename = g_dir_read_name (dir)))
        {
            node_t *childf = NULL;
            fdata* data;
            GList *idx;

            childf = children_find (f, basename);
            if (childf == NULL) {
                gchar *filename;
            
                filename = g_build_filename (NODE_NAME(f), basename, NULL);
                childf = add_node (f, filename);
                g_assert (childf);
                g_free (filename);
            }
            if ((data = node_get_data (childf)) == NULL) {
                data = fdata_new (childf, FALSE);
            }
            
            if (is_monitoring (data)) {
                /* Ignored */
            } else if (/* !is_ported (data) && */
                port_add (&data->fobj, &data->len, data)) {
                /* Emit created to all other subs */
                fdata_emit_events (data, FN_EVENT_CREATED);
            }
            /* Emit created to the new sub */
#ifdef GIO_COMPILATION
            /* fdata_emit_events_once (data, FN_EVENT_CREATED, sub); */
#else
            gam_server_emit_one_event (NODE_NAME(childf),
              gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
#endif
        }
        g_dir_close (dir);
    } else {
        FH_W (err->message);
        g_error_free (err);
    }
}

/**
 * fen_add
 * 
 * Won't hold a ref, we have a timout callback to clean unused fdata.
 * If there is no value for a key, add it and return it; else return the old
 * one.
 */
void
fen_add (const gchar *filename, gpointer sub, gboolean is_mondir)
{
    node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
	node_t* f;
    fdata* data;
    
    g_assert (filename);
    g_assert (sub);

    G_LOCK (fen_lock);
	f = find_node_full (filename, &op);
    FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);
    g_assert (f);
    data = node_get_data (f);
    if (data == NULL) {
        data = fdata_new (f, is_mondir);
    }

    if (is_mondir) {
        data->mon_dir_num ++;
    }
    
    /* Change to active */
#ifdef GIO_COMPILATION
    if (port_add (&data->fobj, &data->len, data) ||
      g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
        if (is_mondir) {
            scan_children_init (f, sub);
        }
        fdata_sub_add (data, sub);
    } else {
        fdata_sub_add (data, sub);
        fdata_adjust_deleted (data);
    }
#else
    if (port_add (&data->fobj, &data->len, data) ||
      g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
        gam_server_emit_one_event (FN_NAME(data),
          gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
        if (is_mondir) {
            scan_children_init (f, sub);
        }
        gam_server_emit_one_event (FN_NAME(data),
          gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
        fdata_sub_add (data, sub);
    } else {
        fdata_sub_add (data, sub);
        gam_server_emit_one_event (FN_NAME(data),
          gam_subscription_is_dir (sub), GAMIN_EVENT_DELETED, sub, 1);
        fdata_adjust_deleted (data);
        gam_server_emit_one_event (FN_NAME(data),
          gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
    }
#endif
    G_UNLOCK (fen_lock);
}

void
fen_remove (const gchar *filename, gpointer sub, gboolean is_mondir)
{
    node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
    node_t* f;
    fdata* data;
    
    g_assert (filename);
    g_assert (sub);

    G_LOCK (fen_lock);
	f = find_node (filename);
    FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);

    g_assert (f);
    data = node_get_data (f);
    g_assert (data);
    
    if (is_mondir) {
        data->mon_dir_num --;
    }
    fdata_sub_remove (data, sub);
    if (FN_IS_PASSIVE(data)) {
#ifdef GIO_COMPILATION
        pending_remove_node (f, &op);
#else
        remove_node (f, &op);
#endif
    }
    G_UNLOCK (fen_lock);
}

static gboolean
fen_init_once_func (gpointer data)
{
    FH_W ("%s\n", __func__);
    if (!node_class_init ()) {
        FH_W ("node_class_init failed.");
        return FALSE;
    }
    if (!fdata_class_init (default_emit_event_cb,
          default_emit_once_event_cb,
          default_event_converter)) {
        FH_W ("fdata_class_init failed.");
        return FALSE;
    }
    return TRUE;
}

gboolean
fen_init ()
{
#ifdef GIO_COMPILATION
    static GOnce fen_init_once = G_ONCE_INIT;
    g_once (&fen_init_once, (GThreadFunc)fen_init_once_func, NULL);
    return (gboolean)fen_init_once.retval;
#else
    return fen_init_once_func (NULL);
#endif
}

static void
default_emit_once_event_cb (fdata *f, int events, gpointer sub)
{
#ifdef GIO_COMPILATION
    GFile* child;
    fen_sub* _sub = (fen_sub*)sub;
    child = g_file_new_for_path (FN_NAME(f));
    g_file_monitor_emit_event (G_FILE_MONITOR (_sub->user_data),
      child, NULL, events);
    g_object_unref (child);
#else
    gam_server_emit_one_event (FN_NAME(f),
      gam_subscription_is_dir (sub), events, sub, 1);
#endif
}

static void
default_emit_event_cb (fdata *f, int events)
{
    GList* i;
    fdata* pdata;
    
#ifdef GIO_COMPILATION
    GFile* child;
    child = g_file_new_for_path (FN_NAME(f));
    for (i = f->subs; i; i = i->next) {
        fen_sub* sub = (fen_sub*)i->data;
        gboolean file_is_dir = sub->is_mondir;
        if ((events != G_FILE_MONITOR_EVENT_CHANGED &&
              events != G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) ||
          !file_is_dir) {
            g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
              child, NULL, events);
        }
    }
    if ((pdata = get_parent_data (f)) != NULL) {
        for (i = pdata->subs; i; i = i->next) {
            fen_sub* sub = (fen_sub*)i->data;
            gboolean file_is_dir = sub->is_mondir;
            g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
              child, NULL, events);
        }
    }
    g_object_unref (child);
#else
    for (i = f->subs; i; i = i->next) {
        gboolean file_is_dir = gam_subscription_is_dir (i->data);
        if (events != GAMIN_EVENT_CHANGED || !file_is_dir) {
            gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
        }
    }
    if ((pdata = get_parent_data (f)) != NULL) {
        for (i = pdata->subs; i; i = i->next) {
            gboolean file_is_dir = gam_subscription_is_dir (i->data);
            gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
        }
    }
#endif
}

static int
default_event_converter (int event)
{
#ifdef GIO_COMPILATION
    switch (event) {
    case FN_EVENT_CREATED:
        return G_FILE_MONITOR_EVENT_CREATED;
    case FILE_DELETE:
    case FILE_RENAME_FROM:
        return G_FILE_MONITOR_EVENT_DELETED;
    case UNMOUNTED:
        return G_FILE_MONITOR_EVENT_UNMOUNTED;
    case FILE_ATTRIB:
        return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
    case MOUNTEDOVER:
    case FILE_MODIFIED:
    case FILE_RENAME_TO:
        return G_FILE_MONITOR_EVENT_CHANGED;
    default:
        /* case FILE_ACCESS: */
        g_assert_not_reached ();
        return -1;
    }
#else
    switch (event) {
    case FN_EVENT_CREATED:
        return GAMIN_EVENT_CREATED;
    case FILE_DELETE:
    case FILE_RENAME_FROM:
        return GAMIN_EVENT_DELETED;
    case FILE_ATTRIB:
    case MOUNTEDOVER:
    case UNMOUNTED:
    case FILE_MODIFIED:
    case FILE_RENAME_TO:
        return GAMIN_EVENT_CHANGED;
    default:
        /* case FILE_ACCESS: */
        g_assert_not_reached ();
        return -1;
    }
#endif
}