//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// 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 3 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#ifdef USE_AVAHI
  #include <avahi-client/client.h>
  #include <avahi-client/publish.h>

  #include <avahi-common/alternative.h>
  #include <avahi-common/simple-watch.h>
  #include <avahi-common/malloc.h>
  #include <avahi-common/error.h>
  #include <avahi-common/timeval.h>
#endif

#include "conf.h"
#include "thread.h"
#include "utils.h"

extern char tmp[MAXMAXLEN];

#ifdef USE_AVAHI
static AvahiEntryGroup *group = NULL;
static AvahiSimplePoll *simple_poll = NULL;
static char *avahiName = NULL;

static void create_services(AvahiClient *c);

static int isAvahiUsed = 0;
static int tcpPort = -1;
static int webPort = -1;

int parsePortsForAvahi()
{
    int use = 0; 
    
    char* peers  = getDevice();
    //DEBUG2("[AV]: parsePortsForAvahi() ports %s", peers);
    char *bufPtr = NULL;
    
    // split peers by ','
    char *peer = strtok_r(peers, ",", &bufPtr);

    while (peer) {

        char* strPort;
        
        if ((strPort = strstr(peer, PEER_TCP)) != NULL) {
            
            strPort += strlen(PEER_TCP);
            
            char * ch = strPort;
            int isPort = 1;
            
            while (*ch != '\0') {
                if (isdigit(*ch)) {
                    ch++;
                } else {
                    isPort = 0;
                    break;
                }
            }

            if (isPort) {
                DEBUG2("[AV]: parsePortsForAvahi() TCP %s", strPort);
                tcpPort = atoi(strPort);
            }
        
        } else if ((strPort = strstr(peer, WEB_SOCKET)) != NULL) {
            
            strPort += strlen(WEB_SOCKET);
            
            char * ch = strPort;
            int isPort = 1;
            
            while (*ch != '\0') {
                if (isdigit(*ch)) {
                    ch++;
                } else {
                    isPort = 0;
                    break;
                }
            }

            if (isPort) {
                DEBUG2("[AV]: parsePorts() WEB %s", strPort);
                webPort = atoi(strPort);
            }
        
        } else if ((strPort = strstr(peer, AVAHI_USE)) != NULL) {
            use = 1; 
        }
        
        peer = strtok_r(NULL, ",", &bufPtr);
    }

    free(peers);
    return (use && (tcpPort > 0 || webPort > 0));
}

//
// Called whenever the entry group state changes
//
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) 
{
    group = g;
    
    logger(L_INF,  "[AV]: entry_group_callback");

    switch (state) {
        case AVAHI_ENTRY_GROUP_ESTABLISHED :
            
            // The entry group has been established successfully
            INFO2("[AV]: Service %s successfully established", avahiName)
            break;

        case AVAHI_ENTRY_GROUP_COLLISION: 
        {
            char *n;

            // A service name collision with a remote service happened. Let's pick a new name
            n = avahi_alternative_service_name(avahiName);
            avahi_free(avahiName);
            avahiName = n;

            INFO2("[AV]: Service name collision, renaming service to %s", avahiName)

            // And recreate the services
            create_services(avahi_entry_group_get_client(g));
            break;
        }

        case AVAHI_ENTRY_GROUP_FAILURE :

            ERROR2("[AV]: Entry group failure: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));

            // Some kind of failure happened while we were registering our services
            avahi_simple_poll_quit(simple_poll);
            break;

        case AVAHI_ENTRY_GROUP_UNCOMMITED:
            
            //INFO2("[AV]: entry_group_callback AVAHI_ENTRY_GROUP_UNCOMMITED");
            break;
        
        case AVAHI_ENTRY_GROUP_REGISTERING:
            
            //INFO2("[AV]: entry_group_callback AVAHI_ENTRY_GROUP_REGISTERING");
            break;
    }
}

static void create_services(AvahiClient *c) 
{
    char *n;
    int ret;
    
    logger(L_INF, "[AV]: create_services");
    
    if (tcpPort < 0 && webPort < 0) {
        logger(L_INF, "[AV]: create_services: no port to publish");
        avahi_simple_poll_quit(simple_poll);
        return;
    }
    
    
    // If this is the first time we're called, let's create a new entry group if necessary
    if (!group) {
        if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) {
            ERROR2("[AV]: create_services: avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
            avahi_simple_poll_quit(simple_poll);
            return;
        }
    }
    
    // If the group is empty (either because it was just created, or
    // because it was reset previously, add our entries

    if (avahi_entry_group_is_empty(group)) {
    
        INFO2("[AV]: Adding service %s", avahiName)

        if (tcpPort >= 0) {
            // Add service 
            if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, avahiName, 
                                                     "_anyremote._tcp", NULL, NULL, tcpPort, 
                                                     "Remote control daemon", NULL)) < 0) {

                if (ret == AVAHI_ERR_COLLISION) {
                    logger(L_WARN,  "[AV]: create_services collision");

                    // A service name collision with a local service happened. Let's pick a new name
                    n = avahi_alternative_service_name(avahiName);
                    avahi_free(avahiName);
                    avahiName = n;

                    INFO2("[AV]: Service name collision, renaming service to %s", avahiName);

                    avahi_entry_group_reset(group);

                    create_services(c);
                    return;
                }

                ERROR2("[AV]: Failed to add _remote._tcp service: %s", avahi_strerror(ret));
                avahi_simple_poll_quit(simple_poll);
                return;
            }
        }
        
        if (webPort >= 0) {
            
            // Add service 
            if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, avahiName, 
                                                     "_anyremote-http._tcp", NULL, NULL, webPort, 
                                                     "Remote control daemon - web interface", NULL)) < 0) {

                if (ret == AVAHI_ERR_COLLISION) {
                    logger(L_WARN,  "[AV]: create_services collision");

                    // A service name collision with a local service happened. Let's pick a new name
                    n = avahi_alternative_service_name(avahiName);
                    avahi_free(avahiName);
                    avahiName = n;

                    INFO2("[AV]: Service name collision, renaming service to %s", avahiName);

                    avahi_entry_group_reset(group);

                    create_services(c);
                    return;
                }

                ERROR2("[AV]: Failed to add _remote._http._tcp service: %s", avahi_strerror(ret));
                avahi_simple_poll_quit(simple_poll);
                return;
            }
        }
 
        // Tell the server to register the service
        if ((ret = avahi_entry_group_commit(group)) < 0) {
            ERROR2("[AV]: Failed to commit entry group: %s", avahi_strerror(ret));
            avahi_simple_poll_quit(simple_poll);
            return;
        }
    }

    logger(L_INF,  "[AV]: create_services OK");
    return;
}

//
// Called whenever the client or server state changes
//
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) 
{
    logger(L_INF,  "[AV]: client_callback");
    switch (state) {
        case AVAHI_CLIENT_S_RUNNING:

            logger(L_INF,  "[AV]: client_callback AVAHI_CLIENT_S_RUNNING");
            // The server has startup successfully and registered its host
            // name on the network, so it's time to create our services
            create_services(c);
            break;

        case AVAHI_CLIENT_FAILURE:

            ERROR2("[AV]: client_callback AVAHI_CLIENT_FAILURE: %s", avahi_strerror(avahi_client_errno(c)));
            avahi_simple_poll_quit(simple_poll);
            break;

        case AVAHI_CLIENT_S_COLLISION:

            logger(L_INF,  "[AV]: client_callback AVAHI_CLIENT_S_COLLISION");
            // Let's drop our registered services. When the server is back in AVAHI_SERVER_RUNNING 
            // state we will register them again with the new host name
            if (group) {
                avahi_entry_group_reset(group);
            }
            break;

        case AVAHI_CLIENT_S_REGISTERING:

            logger(L_INF,  "[AV]: client_callback AVAHI_CLIENT_S_REGISTERING");
            // The server records are now being established. This might be caused by a host name change. 
            // We need to wait for our own records to register until the host name is properly established
            if (group) {
                avahi_entry_group_reset(group);
            }
            break;

        case AVAHI_CLIENT_CONNECTING:
            logger(L_INF,  "[AV]: client_callback AVAHI_CLIENT_CONNECTING");
    }
}

pointer_t startAvahi(pointer_t thread)
{
    AvahiClient *client = NULL;
    int error;
    int ret = 1;
    
    logger(L_INF,"[AV]: Start avahi thread");
    
    // Allocate main loop object
    if ((simple_poll = avahi_simple_poll_new())) {
    
        avahiName = avahi_strdup(getServiceName());

        // Allocate a new client
        client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error);

        logger(L_INF,"[AV]: avahi_client_new");

        // Check wether creating the client object succeeded
        if (client) {
        
            logger(L_INF, "[AV]: startAvahi: start loop");
            
            isAvahiUsed = 1;

            // Run the main loop
            avahi_simple_poll_loop(simple_poll);

            logger(L_INF, "[AV]: startAvahi: loop exiting");

            ret = 0;
        
        } else {
            ERROR2("[AV]: Failed to create client: %s", avahi_strerror(error));
        }
    } else {
        logger(L_ERR, "[AV]: Failed to create simple poll object");
    }

    // Cleanup things
    if (ret == 0) {
        logger(L_INF, "[AV]: startAvahi: exit");
    } else {
        logger(L_ERR, "[AV]: startAvahi: fail");
    }

    if (client) {
        avahi_client_free(client);
    }
    if (simple_poll) {
        avahi_simple_poll_free(simple_poll);
    }

    avahi_free(avahiName);

    return NULL;
}

void stopAvahi()
{
    logger(L_INF, "[AV]: stopAvahi");
    
    if (isAvahiUsed) {
        avahi_simple_poll_quit(simple_poll);
        threadJoin(T_AVAHI);
    }
}

#else

pointer_t startAvahi(pointer_t thread) {}
void      stopAvahi ()                 {}
int       parsePortsForAvahi()         {return 0;}

#endif
