/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/* Cherokee
 *
 * Authors:
 *      Alvaro Lopez Ortega <alvaro@alobbs.com>
 *      Stefan de Konink <stefan@konink.de>
 *
 * Copyright (C) 2001-2008 Alvaro Lopez Ortega
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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
 */

#include "handler_avahi.h"
#include <cherokee/cherokee.h>

#include <avahi-client/client.h>
#include <avahi-client/lookup.h>

#include <avahi-common/malloc.h>
#include <avahi-common/error.h>

/* Plug-in initialization
 *
 * In this function you can use any of these:
 * http_delete | http_get | http_post | http_put
 *
 * For a full list: cherokee_http_method_t
 *
 * It is what your handler to be implements.
 *
 */
PLUGIN_INFO_HANDLER_EASIEST_INIT (avahi, http_get);


/* Methods implementation
 */

static ret_t 
props_free (cherokee_handler_avahi_props_t *props)
{
    if (props->threaded_poll)
       avahi_threaded_poll_stop(props->threaded_poll);

    if (props->sb)
        avahi_service_browser_free(props->sb);

    if (props->client)
       avahi_client_free(props->client);

    if (props->threaded_poll)
        avahi_threaded_poll_free(props->threaded_poll);

    cherokee_avl_mrproper (&props->entries, (cherokee_func_free_t) cherokee_buffer_mrproper);

	return cherokee_module_props_free_base (MODULE_PROPS(props));
}


ret_t 
cherokee_handler_avahi_configure (cherokee_config_node_t *conf, cherokee_server_t *srv, cherokee_module_props_t **_props)
{
	cherokee_list_t                      *i;
	cherokee_handler_avahi_props_t *props;
    int error;

	if (*_props == NULL) {
		CHEROKEE_NEW_STRUCT (n, handler_avahi_props);

		cherokee_module_props_init_base (MODULE_PROPS(n), 
						 MODULE_PROPS_FREE(props_free));		
        
        /* Look at handler_avahi.h
         * This is an avahi of configuration.
         */
        cherokee_buffer_init (&n->service_type);

		*_props = MODULE_PROPS(n);
	}

	props = PROP_AVAHI(*_props);

	cherokee_config_node_foreach (i, conf) {
		cherokee_config_node_t *subconf = CONFIG_NODE(i);

        if (equal_buf_str (&subconf->key, "service_type")) {
            cherokee_buffer_add_buffer (&props->service_type, &subconf->val);
		} else {
			PRINT_MSG ("ERROR: Handler file: Unknown key: '%s'\n", subconf->key.buf);
			return ret_error;
		}
	}

    props->client = NULL;
    props->sb = NULL;
    props->threaded_poll = NULL;

    cherokee_avl_init(&props->entries);

    if (!(props->threaded_poll = avahi_threaded_poll_new())) {
        return ret_error;
    }

    if (!(props->client = avahi_client_new(avahi_threaded_poll_get(props->threaded_poll), 0, client_callback, props, &error))) {
        return ret_error;
    }

    /* create some browsers on the client object here, if you wish */
    if (!(props->sb = avahi_service_browser_new(props->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, props->service_type.buf, NULL, 0, browse_callback, props))) {
        TRACE ("avahi", "Failed to create service browser: %s (%s)\n", avahi_strerror(avahi_client_errno(props->client)), props->service_type.buf);
        return ret_error;
    }

    /* Finally, start the event loop thread */
    if (avahi_threaded_poll_start(props->threaded_poll) < 0) {
        return ret_error;
    }


	return ret_ok;
}

ret_t
cherokee_handler_avahi_new  (cherokee_handler_t **hdl, cherokee_connection_t *cnt, cherokee_module_props_t *props)
{
	ret_t ret;

	CHEROKEE_NEW_STRUCT (n, handler_avahi);
	
	/* Init the base class object
	 */
	cherokee_handler_init_base(HANDLER(n), cnt, HANDLER_PROPS(props), PLUGIN_INFO_HANDLER_PTR(avahi));
	   
	MODULE(n)->init         = (handler_func_init_t) cherokee_handler_avahi_init;
	MODULE(n)->free         = (module_func_free_t) cherokee_handler_avahi_free;
	HANDLER(n)->step        = (handler_func_step_t) cherokee_handler_avahi_step;
	HANDLER(n)->add_headers = (handler_func_add_headers_t) cherokee_handler_avahi_add_headers;

	HANDLER(n)->support = hsupport_length | hsupport_range;

	/* Init
	 */
	ret = cherokee_buffer_init (&n->buffer);
	if (unlikely(ret != ret_ok)) 
		return ret;

	ret = cherokee_buffer_ensure_size (&n->buffer, 4*1024);
	if (unlikely(ret != ret_ok)) 
		return ret;

	*hdl = HANDLER(n);

	return ret_ok;
}


ret_t 
cherokee_handler_avahi_free (cherokee_handler_avahi_t *hdl)
{
	cherokee_buffer_mrproper (&hdl->buffer);

	return ret_ok;
}

static ret_t
while_func_entries (cherokee_buffer_t *key, void *value, void *param) {
    cherokee_buffer_t *buf = (cherokee_buffer_t *)param;

    cherokee_buffer_add_buffer (buf, key);
    cherokee_buffer_add_str (buf, " - ");
    cherokee_buffer_add_buffer (buf, (cherokee_buffer_t *) value);
    cherokee_buffer_add_str (buf, "\n");

    return ret_ok;
}

static void
avahi_build_page (cherokee_handler_avahi_t *hdl)
{
    ret_t              ret;
    cherokee_server_t *srv;
    cherokee_buffer_t *buf;

    /* Init
     */
    buf = &hdl->buffer;
    srv = HANDLER_SRV(hdl);

    /* Useful output
     */

    /* But we did configure something!
     */
//    if (HDL_AVAHI_PROPS(hdl)->service_config ) {
//        cherokee_buffer_add_str (buf, "!!!");
//    }

    /* First, block the event loop */
//    avahi_thread_poll_lock(&hdl->threaded_poll);

    /* Than, do your stuff */
//    if (!avahi_service_browser_new(client, ....)) {
        /* do something bad */
//    }

    /* Finally, unblock the event loop */
//    avahi_thread_poll_unlock(threaded_poll);

    cherokee_avl_while (&HDL_AVAHI_PROPS(hdl)->entries, (cherokee_avl_while_func_t) while_func_entries, (void *) buf, NULL, NULL);

}

ret_t 
cherokee_handler_avahi_init (cherokee_handler_avahi_t *hdl)
{
	ret_t   ret;
	void   *param;
	cint_t  web_interface = 1;

	/* Build the page
	 */
	if (web_interface) {
		avahi_build_page (hdl);
	}

	hdl->action = send_page;
	
	return ret_ok;
}


ret_t 
cherokee_handler_avahi_step (cherokee_handler_avahi_t *hdl, cherokee_buffer_t *buffer)
{
	cherokee_buffer_add_buffer (buffer, &hdl->buffer);
	return ret_eof_have_data;
}


ret_t 
cherokee_handler_avahi_add_headers (cherokee_handler_avahi_t *hdl, cherokee_buffer_t *buffer)
{
    if (hdl->buffer.len == 0)
        HANDLER_CONN(hdl)->error_code = http_not_found;

	cherokee_buffer_add_va (buffer, "Content-Length: %d"CRLF, hdl->buffer.len);

	switch (hdl->action) {
    	case send_page:
    	default:
	    	cherokee_buffer_add_str (buffer, "Content-Type: text/html"CRLF);
		    break;
	}

	return ret_ok;
}


/* Avahi example stuff */

static void resolve_callback(
    AvahiServiceResolver *r,
    AVAHI_GCC_UNUSED AvahiIfIndex interface,
    AVAHI_GCC_UNUSED AvahiProtocol protocol,
    AvahiResolverEvent event,
    const char *name,
    const char *type,
    const char *domain,
    const char *host_name,
    const AvahiAddress *address,
    uint16_t port,
    AvahiStringList *txt,
    AvahiLookupResultFlags flags,
    void* userdata) {
    assert(r);

    /* Called whenever a service has been resolved successfully or timed out */

    switch (event) {
        case AVAHI_RESOLVER_FAILURE:
            fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
            break;

        case AVAHI_RESOLVER_FOUND: {
            char a[AVAHI_ADDRESS_STR_MAX], *t;
            
            avahi_address_snprint(a, sizeof(a), address);
            
            cherokee_buffer_t buf;
            cherokee_buffer_t buf2;
            cherokee_buffer_t *buf2a;
            cherokee_buffer_init(&buf);
            cherokee_buffer_init(&buf2);
            cherokee_buffer_add(&buf, name, strlen(name));
            cherokee_buffer_add(&buf2, a, strlen(a));
            cherokee_buffer_dup(&buf2, &buf2a);
            cherokee_avl_add(&PROP_AVAHI(userdata)->entries, &buf, buf2a);
            cherokee_buffer_mrproper(&buf);
            cherokee_buffer_mrproper(&buf2);

            fprintf(stderr, "Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
            
            t = avahi_string_list_to_string(txt);
            fprintf(stderr,
                    "\t%s:%u (%s)\n"
                    "\tTXT=%s\n"
                    "\tcookie is %u\n"
                    "\tis_local: %i\n"
                    "\tour_own: %i\n"
                    "\twide_area: %i\n"
                    "\tmulticast: %i\n"
                    "\tcached: %i\n",
                    host_name, port, a,
                    t,
                    avahi_string_list_get_service_cookie(txt),
                    !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
                    !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
                    !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
                    !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
                    !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
                
            avahi_free(t);
        }
    }

    avahi_service_resolver_free(r);
}

static void browse_callback(
    AvahiServiceBrowser *b,
    AvahiIfIndex interface,
    AvahiProtocol protocol,
    AvahiBrowserEvent event,
    const char *name,
    const char *type,
    const char *domain,
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
    void* userdata) {
    
    AvahiClient *c = PROP_AVAHI(userdata)->client;
    assert(b);

    /* Called whenever a new services becomes available on the LAN or is removed from the LAN */

    switch (event) {
        case AVAHI_BROWSER_FAILURE:            
            fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
            avahi_threaded_poll_stop(PROP_AVAHI(userdata)->threaded_poll);
            return;

        case AVAHI_BROWSER_NEW:
            fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);

            /* We ignore the returned resolver object. In the callback
               function we free it. If the server is terminated before
               the callback function is called the server will free
               the resolver for us. */

            if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, userdata)))
                fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
            
            break;

        case AVAHI_BROWSER_REMOVE: {
            void **val;
            cherokee_buffer_t buf;
            cherokee_buffer_init(&buf);
            cherokee_buffer_add(&buf, name, strlen(name));
            if (cherokee_avl_del(&PROP_AVAHI(userdata)->entries, &buf, val) == ret_ok) {
                cherokee_buffer_mrproper((cherokee_buffer_t *) *val);
            } 
            cherokee_buffer_mrproper(&buf);

            fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
            break;
        }

        case AVAHI_BROWSER_ALL_FOR_NOW:
        case AVAHI_BROWSER_CACHE_EXHAUSTED:
            fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
            break;
    }
}

static void client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {
    assert(c);

    /* Called whenever the client or server state changes */

    if (state == AVAHI_CLIENT_FAILURE) {
        fprintf(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
        avahi_threaded_poll_stop(PROP_AVAHI(userdata)->threaded_poll);
    }
}

