#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include <iostream>
#include <uuid/uuid.h>
//#include  <sys/types.h>
//#include  <sys/socket.h>
//#include  <netinet/in.h>
//#include  <arpa/inet.h>
//#include  <netdb.h>
//#include <net/if.h>
//#include <linux/sockios.h>
//#include <sys/ioctl.h>
//#include <arpa/inet.h>

#include "CSESLog.h"
#include "SESPublishService.h"

using namespace std;
using namespace LibSESData;

AvahiEntryGroup* CSESPublishService::m_group = NULL;
AvahiSimplePoll* CSESPublishService::m_simple_poll = NULL;
string CSESPublishService::m_Name = "";
string CSESPublishService::m_Comment = "";
int CSESPublishService::m_iPort = 0;
int CSESPublishService::m_iDownloadCAPort = 0;

//string CSESPublishService::m_IP = "";
//std::vector< HostInfo_t > CSESPublishService::m_IPList;

AvahiClient* CSESPublishService::m_client = NULL;
//pthread_t CSESPublishService::SESPublisherThreadID = 0;
bool CSESPublishService::bLoop = false;
bool CSESPublishService::bUpdate = false;
static char* name = NULL;
bool bunDisconnected = false;

CSESPublishService::CSESPublishService(void)
{
	//m_IP = GetHostIP();
	//GetHostIPList(m_IPList);
}

CSESPublishService::~CSESPublishService(void)
{
	avahi_simple_poll_quit(m_simple_poll);
	usleep(1000000);

	//pthread_join(SESPublisherThreadID, NULL);
	//SESPublisherThreadID = 0;

	if (m_client)
	{
		avahi_client_free(m_client);
		m_client = NULL;
	}
	if (m_simple_poll)
	{
		avahi_simple_poll_free(m_simple_poll);
		m_simple_poll = NULL;
	}
	if(name)
	{
		avahi_free(name);
		name = NULL;
	}

}

void CSESPublishService::client_callback(AvahiClient *c,
		AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
{
	char   numStr[6];
	bzero(numStr,6);
	snprintf(numStr,5,"%d",state);
	CSESLog::WriteLine("TSD CSESPublishService::client_callback event=");
	CSESLog::WriteLine(numStr);

	switch (state)
	{
	case AVAHI_CLIENT_S_RUNNING:

		create_services(c);
		break;

	case AVAHI_CLIENT_FAILURE:

		bLoop = false;
		avahi_simple_poll_quit(m_simple_poll);

		break;

	case 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. */

	case 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 esatblished. */

		if (m_group)
			avahi_entry_group_reset(m_group);

		break;

	case AVAHI_CLIENT_CONNECTING:

		break;

	default:
		break;
	}
}

void CSESPublishService::create_services(AvahiClient *c)
{
	//char *n;
	int ret;
	assert(c);

	if (!m_group)
		if (!(m_group = avahi_entry_group_new(c, entry_group_callback, NULL)))
		{
#ifdef _DEBUG
			cout << "avahi_entry_group_new() failed:" << avahi_strerror(
					avahi_client_errno(c));
#endif
			CSESLog::WriteLine("TSD CSESPublishService::avahi_entry_group_new error");
			CSESLog::WriteLine(avahi_strerror(avahi_client_errno(c)));
			bLoop = false;
			avahi_simple_poll_quit(m_simple_poll);
		}

	/* 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(m_group))
	{
#ifdef _DEBUG
		cout << "add new avahi service:" << name << endl;
#endif
		if (m_Comment.size() > 0)
		{
			ret = avahi_entry_group_add_service(m_group, AVAHI_IF_UNSPEC, //interface
					AVAHI_PROTO_UNSPEC, //protocol
					(AvahiPublishFlags) 0, //flags
					name, //The name for the new service
					SERVICE_TYPE, //The service type for the new service
					NULL, //The domain to register this domain in
					NULL, //The host this services is residing on
					m_iPort,//SERVICE_PORT, //The IP port number of this service
					m_Comment.c_str(), //comment information 1
					NULL);
		}
		else
		{
			ret = avahi_entry_group_add_service(m_group, AVAHI_IF_UNSPEC, //interface
					AVAHI_PROTO_UNSPEC, //protocol
					(AvahiPublishFlags) 0, //flags
					name, //The name for the new service
					SERVICE_TYPE, //The service type for the new service
					NULL, //The domain to register this domain in
					NULL, //The host this services is residing on
					m_iPort,//SERVICE_PORT, //The IP port number of this service
					//NULL,//m_Comment.c_str(), //comment information 1
					NULL);
		}
#ifdef _DEBUG
		cout << "Adding service" << m_Name << endl;
#endif

		if (ret < 0)
		{

			if (ret == AVAHI_ERR_COLLISION)
			{
				CSESLog::WriteLine("TSD CSESPublishService::create_services AVAHI_ERR_COLLISION");
				//n = avahi_alternative_service_name(name);
				avahi_free(name);
				GenerateServiceName();
				name = avahi_strdup(m_Name.c_str());
				//name = n;
#ifdef _DEBUG
				cout << "Service name collision, renaming service to:"
						<< m_Name << endl;
#endif

				avahi_entry_group_reset(m_group);
				//avahi_entry_group_free(m_group);
				create_services(c);
				return;
			}
#ifdef _DEBUG
			cout << "Failed to add service: " << avahi_strerror(ret) << endl;
#endif
			CSESLog::WriteLine("TSD CSESPublishService::avahi_entry_group_add_service error");
			CSESLog::WriteLine(avahi_strerror(ret));

			bLoop = false;
			avahi_simple_poll_quit(m_simple_poll);
		}

		/* Tell the server to register the service */
		if ((ret = avahi_entry_group_commit(m_group)) < 0)
		{
#ifdef _DEBUG
			cout << "Failed to commit entry group: " << avahi_strerror(ret)
					<< endl;
#endif
			CSESLog::WriteLine("TSD CSESPublishService::avahi_entry_group_commit error");
			CSESLog::WriteLine(avahi_strerror(ret));
			bLoop = false;
			avahi_simple_poll_quit(m_simple_poll);
		}
	}
	bunDisconnected = true;
	bUpdate = true;
	return;
}

void CSESPublishService::entry_group_callback(AvahiEntryGroup *g,
		AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
{
	char   numStr[6];
	bzero(numStr,6);
	snprintf(numStr,5,"%d",state);
	CSESLog::WriteLine("TSD CSESPublishService::entry_group_callback state=");
	CSESLog::WriteLine(numStr);

	assert(g == m_group || m_group == NULL);
	m_group = g;

	/* Called whenever the entry group state changes */

	switch (state)
	{
	case AVAHI_ENTRY_GROUP_ESTABLISHED:
#ifdef _DEBUG
		cout << "Service: " << name << "is successfully established." << endl;
#endif
		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(name);
		avahi_free(name);
		//name = n;

		GenerateServiceName();
		name = avahi_strdup(m_Name.c_str());

#ifdef _DEBUG
		cout << "Service name collision, renaming service to: " << name << endl;
#endif

		/* And recreate the services */
		//avahi_entry_group_free(m_group);
		avahi_entry_group_reset(m_group);
		create_services(avahi_entry_group_get_client(g));
		break;
	}

	case AVAHI_ENTRY_GROUP_FAILURE:
#ifdef _DEBUG
		cout << "Entry group failure:" << avahi_strerror(avahi_client_errno(
				avahi_entry_group_get_client(g))) << endl;
#endif
		CSESLog::WriteLine("TSD CSESPublishService::entry_group_callback AVAHI_ENTRY_GROUP_FAILURE");
		CSESLog::WriteLine(avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));

		/* Some kind of failure happened while we were registering our services */
		bLoop = false;
		avahi_simple_poll_quit(m_simple_poll);
		break;

	case AVAHI_ENTRY_GROUP_UNCOMMITED:
	case AVAHI_ENTRY_GROUP_REGISTERING:
		;
	}
}
void CSESPublishService::GenerateServiceName()
{
	uuid_t out;
	uuid_generate(out);
	char str[40];
	memset(str,0,40);
	uuid_unparse(out,str);
	m_Name.clear();
	m_Name.append("SES");
	m_Name.append(str);
}
void CSESPublishService::UpdateServiceInfo(const string& Name,
		const string& Comment,
		int iPort,
		int iDownloadCAPort)
{
	//m_Name = Name + "/ - " + Comment;
	GenerateServiceName();
	bUpdate = true;
	m_iPort = iPort;
	m_iDownloadCAPort = iDownloadCAPort;
	stringstream stream;
	stream << iDownloadCAPort;
	m_Comment = Name + "/" + Comment + "/" + 	stream.str();

}
/*bool CSESPublishService::isIPListChanged(std::vector<HostInfo_t>& IPList)
{
	if(IPList.size() != m_IPList.size())
	{
		return true;
	}
	for(unsigned int i = 0; i < IPList.size(); i++)
	{
		bool isFound = false;
		for(unsigned int j = 0; j < m_IPList.size(); j++)
		{
			if ( 0 == ((m_IPList[j]).netcardname).compare((IPList[i]).netcardname) )
			{
				isFound = true;
				if ( 0 != ((m_IPList[j]).IP).compare((IPList[i]).IP) )
				{
					return true;
				}
			}
		}
		if ( false == isFound )
		{
			return true;
		}
	}
	return false;
}
void CSESPublishService::GetHostIPList(std::vector<HostInfo_t>& IPList)
{
    int s;
    struct ifconf conf;
    struct ifreq *ifr;
    char buff[BUFSIZ];
    int num;
    int i;

    s = socket(PF_INET, SOCK_DGRAM, 0);
    conf.ifc_len = BUFSIZ;
    conf.ifc_buf = buff;

    ioctl(s, SIOCGIFCONF, &conf);
    num = conf.ifc_len / sizeof(struct ifreq);
    ifr = conf.ifc_req;
    for(i=0;i < num;i++)
    {
            struct sockaddr_in *sin = (struct sockaddr_in *)(&ifr->ifr_addr);

            ioctl(s, SIOCGIFFLAGS, ifr);
            if(((ifr->ifr_flags & IFF_LOOPBACK) == 0) && (ifr->ifr_flags & IFF_UP))
            {
            	HostInfo_t hi;
            	hi.IP = inet_ntoa(sin->sin_addr);
            	hi.netcardname = ifr->ifr_name;
            	IPList.push_back(hi);
            }
            ifr++;
    }
}
std::string CSESPublishService::GetHostIP()
{
	char name[32];
	gethostname(name, 32);
	struct   hostent*   hs;
	struct   sockaddr_in   server;
	std::string IP = "";
	if ((hs=gethostbyname(name))!=NULL)
	{
		server.sin_addr=*((struct in_addr*)hs->h_addr);
		IP = inet_ntoa(server.sin_addr);
	}
	return IP;
}*/
void CSESPublishService::modify_callback(AVAHI_GCC_UNUSED AvahiTimeout *e,
		void *userdata)
{
	AvahiClient *client = (AvahiClient *) userdata;
	struct timeval tv;

	//std::string IP = GetHostIP();
//	std::vector< HostInfo_t > IPList;
//	GetHostIPList(IPList);
//	if ( true == isIPListChanged(IPList) )
//	{
//		//bUpdate = true;
//		m_IPList.clear();
//		m_IPList = IPList;
//		stop();
//		start();
//	}
	if (true == bUpdate)
	{

		avahi_free(name);
		if (true == bunDisconnected)
		{
			name = avahi_strdup(m_Name.c_str());
		}else
		{
			name = avahi_strdup("SES/*INVALID*/");
		}
		/* If the server is currently running, we need to remove our
		 * service and create it anew */
		if (avahi_client_get_state(client) == AVAHI_CLIENT_S_RUNNING)
		{

			/* Remove the old services */
			if (m_group)
			{
				avahi_entry_group_reset(m_group);
				//avahi_entry_group_free(m_group);
			}
			/* And create them again with the new name */
			create_services(client);

		}
		bUpdate = false;
	}

	if (false == bLoop && avahi_client_get_state(client)
			== AVAHI_CLIENT_S_RUNNING)
	{
		usleep(1000000);
		avahi_simple_poll_quit(m_simple_poll);
	}

	avahi_simple_poll_get(m_simple_poll)->timeout_new(avahi_simple_poll_get(
			m_simple_poll), avahi_elapse_time(&tv, 1000 * 5, 0),
			modify_callback, m_client);

	return;

}

bool CSESPublishService::start()
{
	CSESLog::WriteLine("CSESPublishService::start>>begin");
	bool bRet = true;
	if (false == bLoop)
	{
		bLoop = true;
		bUpdate = false;

		/* Allocate main loop object */
		if(NULL == m_simple_poll)
		{
			m_simple_poll = avahi_simple_poll_new();
			if (NULL == m_simple_poll)
			{
				CSESLog::WriteLine("CSESPublishService::start error<<avahi_simple_poll_new");
				goto fail;
			}
		}

		if (name)
		{
			avahi_free(name);
			name = NULL;
		}
		name = avahi_strdup(m_Name.c_str());

		if (NULL == m_client)
		{
			/* Allocate a new client */
			int error;
			m_client = avahi_client_new(avahi_simple_poll_get(m_simple_poll),
					(AvahiClientFlags) 0, client_callback, NULL, &error);

			/* Check wether creating the client object succeeded */
			if (NULL == m_client)
			{
				CSESLog::WriteLine("CSESPublishService::start error<<avahi_client_new");
				CSESLog::WriteLine(avahi_strerror(error));
				goto fail;
			}
		}

		struct timeval tv;
		avahi_simple_poll_get(m_simple_poll)->timeout_new(
				avahi_simple_poll_get(m_simple_poll), avahi_elapse_time(&tv,
						1000 * 5, 0), modify_callback, m_client);

		//m_group = NULL;

		int err;
		err = pthread_create(&SESPublisherThreadID, NULL, worker, NULL);
		if (0 != err)
		{
			CSESLog::WriteLine("CSESPublishService::start error<<pthread_create");
			goto fail;
		}
		CSESLog::WriteLine("CSESPublishService::start>>end");

	}else
	{
		if (name)
		{
			avahi_free(name);
		}
		name = avahi_strdup(m_Name.c_str());
		bunDisconnected = true;
		bUpdate = true;

//		/* If the server is currently running, we need to remove our
//		 * service and create it anew */
//		if (avahi_client_get_state(m_client) == AVAHI_CLIENT_S_RUNNING)
//		{
//
//			/* Remove the old services */
//			if (m_group)
//			{
//				avahi_entry_group_reset(m_group);
//				//avahi_entry_group_free(m_group);
//			}
//			/* And create them again with the new name */
//			create_services(m_client);
//
//		}

	}
	return bRet;

	fail:
#ifdef _DEBUG
	cout << "Cleanup things." << endl;
#endif
	/* Cleanup things */
	if (m_client)
	{
		avahi_client_free(m_client);
	}
	if (m_simple_poll)
	{
		avahi_simple_poll_free(m_simple_poll);
	}
	avahi_free(name);

	bLoop = false;
	bRet = false;

	return bRet;
}

void CSESPublishService::stop()
{
	if (true == bLoop)
	{

		//avahi_entry_group_reset(m_group);
		//bLoop = false;
		bunDisconnected = false;
		bUpdate = true;
		CSESLog::WriteLine("CSESPublishService::stop");
	}
}

void* CSESPublishService::worker(void *arg)
{
	CSESLog::WriteLine("CSESPublishService::worker << start");
	bLoop = true;
	avahi_simple_poll_loop(m_simple_poll);

	bLoop = false;

//	if(m_group)
//	{
//
//		avahi_entry_group_reset(m_group);
//		//avahi_entry_group_free(m_group);
//		//m_group = NULL;
//	}
	CSESLog::WriteLine("CSESPublishService::worker << end");
	pthread_exit(NULL);
}

