#include <string>
#include <cstring>
#include <sstream>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cmath>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <limits.h>
#include <errno.h>
#include <cstdlib>

#include "chirpstack-ns-client.h"

#include "argtable3/argtable3.h"
#include "platform.h"
#include "utilstring.h"
#include "daemonize.h"

#include "errlist.h"
#include "utillora.h"
#include "utilstring.h"

const std::string progname = "chirpstack-ns-log";
#define DEF_CONFIG_FILE_NAME ".chirpstack-ns-log"
#define DEF_TIME_FORMAT "%FT%T"
#define DEF_NS_SERVICE	"localhost:8000"
#define DEF_AS_SERVICE	"localhost:8001"
#define DEF_EXT_SERVICE	"localhost:8080"

static ChirpstackNsClient *client = NULL;

static void done()
{

	if (client) {
		if (client->verbosity > 1)
			std::cerr << MSG_STOPPED << std::endl;
		delete client;
	}
	client = NULL;
	exit(0);
}

static void stop()
{
	if (client)
		client->disconnect();
}

void signalHandler(int signal)
{
	switch (signal)
	{
	case SIGINT:
		std::cerr << MSG_INTERRUPTED << std::endl;
		stop();
		done();
		break;
	default:
		break;
	}
}

#ifdef _MSC_VER

void setSignalHandler()
{
}
#else
void setSignalHandler()
{
	struct sigaction action;
	memset(&action, 0, sizeof(struct sigaction));
	action.sa_handler = &signalHandler;
	sigaction(SIGINT, &action, NULL);
	sigaction(SIGHUP, &action, NULL);
}
#endif

int parseCmd(
	ChirpstackNsClient *client,
	int argc,
	char *argv[])
{

  	struct arg_str *a_ns_service = arg_str0("n", "ns", "host:port", "Default " DEF_NS_SERVICE);
	struct arg_str *a_as_service = arg_str0("a", "as", "host:port", "Default " DEF_AS_SERVICE);
	struct arg_str *a_ex_service = arg_str0("e", "ext", "host:port", "Default " DEF_EXT_SERVICE);
	struct arg_str *a_gateway_id = arg_str0("g", "gateway", "id", "e.g. 00006cc3743eed46");
	struct arg_str *a_device_eui = arg_str0("d", "device", "eui", "e.g. 1122334455667788");
	struct arg_str *a_apikey = arg_str0("k", "key", "key", "API key string e.g. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e...");
	struct arg_str *a_apikey_file = arg_str0("K", "keyfile", "file", "API key file name e,g, api.key");

	struct arg_str *a_logfilename = arg_str0("l", "logfile", "<file>", "log file");
	struct arg_lit *a_daemonize = arg_lit0("d", "daemonize", "run daemon");
	struct arg_lit *a_verbosity = arg_litn("v", "verbose", 0, 3, "Set verbosity level");
	struct arg_lit *a_help = arg_lit0("?", "help", "Show this help");
	struct arg_end *a_end = arg_end(20);

	void *argtable[] = {
		a_ns_service, a_as_service, a_ex_service, a_gateway_id, a_device_eui, a_apikey, a_apikey_file,
		a_logfilename, a_daemonize, a_verbosity, a_help, a_end };

	int nerrors;

	if (arg_nullcheck(argtable) != 0)
	{
		arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
		return 1;
	}

	nerrors = arg_parse(argc, argv, argtable);

	client->daemonize = a_daemonize->count > 0;
	client->verbosity = a_verbosity->count;
	if (a_ns_service->count)
	{
		std::string s(*a_ns_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setNsService(s);
		}
	} else {
		client->setNsService(DEF_NS_SERVICE);
	}

	if (a_as_service->count)
	{
		std::string s(*a_as_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setAsService(s);
		}
	} else {
		client->setAsService(DEF_AS_SERVICE);
	}

	if (a_ex_service->count)
	{
		std::string s(*a_ex_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setExtService(s);
		}
	} else {
		client->setExtService(DEF_EXT_SERVICE);
	}

	if (a_apikey_file->count) {
		std::string s = file2string(*a_apikey_file->sval);
		client->setToken(trim(s));
	} else {
		if (a_apikey->count) {
			client->setToken(*a_apikey->sval);
		}
	}

	if (a_gateway_id->count)
	{
		std::string s(*a_gateway_id->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_GATEWAY_ID << std::endl;
			nerrors++;
		}
		else
		{
			client->setGatewayId(s);
		}
	}

	if (a_device_eui->count)
	{
		std::string s(*a_device_eui->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_DEVICE_EUI << std::endl;
			nerrors++;
		}
		else
		{
			client->setDeviceEUI(s);
		}
	}

	if (a_logfilename->count)
	{
		client->logfilename = *a_logfilename->sval;
		client->logstream = new std::fstream(*a_logfilename->sval, std::ostream::out);
		if (!client->logstream || client->logstream->bad())
		{
			std::cerr << ERR_INVALID_PAR_LOG_FILE << std::endl;
			nerrors++;
			if (client->logstream)
			{
				delete client->logstream;
				client->logstream = NULL;
			}
		}
	}
	else
	{
		client->logstream = &std::cerr;
		client->logfilename = "";
	}

	if ((a_help->count) || nerrors)
	{
		if (nerrors)
			arg_print_errors(stderr, a_end, progname.c_str());
		std::cerr << "Usage: " << progname << std::endl;
		arg_print_syntax(stderr, argtable, "\n");
		std::cerr << "UDP client" << std::endl;
		arg_print_glossary(stderr, argtable, "  %-25s %s\n");
		arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
		return 1;
	}

	arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
	return 0;
}

static void run()
{
	if (client) {
		client->connect();
		int r = client->run();
		if (r) {
			std::cerr << "Error " << r << ": " << strerror_client(r) << std::endl;
		}
	}
}

int main(
	int argc,
	char *argv[]
)
{
	client = new ChirpstackNsClient();

	if (parseCmd(client, argc, argv) != 0)
	{
		exit(ERR_CODE_COMMAND_LINE);
	}
#ifdef _MSC_VER
#else
	setSignalHandler();
#endif

	if (client->daemonize)
	{
		char wd[PATH_MAX];
		std::string progpath = getcwd(wd, PATH_MAX);
		if (client->verbosity > 1)
			std::cerr << MSG_DAEMON_STARTED << progpath << "/" << progname << MSG_DAEMON_STARTED_1 << std::endl;
		OPENSYSLOG()
		Daemonize daemonize(progname, progpath, run, stop, done);

	}
	else
	{
		setSignalHandler();
		run();
		done();
	}
	return 0;
}
#include <string>
#include <cstring>
#include <sstream>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cmath>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <limits.h>
#include <errno.h>
#include <cstdlib>

#include "chirpstack-ns-client.h"

#include "argtable3/argtable3.h"
#include "platform.h"
#include "utilstring.h"
#include "daemonize.h"

#include "errlist.h"
#include "utillora.h"
#include "utilstring.h"

const std::string progname = "chirpstack-ns-log";
#define DEF_CONFIG_FILE_NAME ".chirpstack-ns-log"
#define DEF_TIME_FORMAT "%FT%T"
#define DEF_NS_SERVICE	"localhost:8000"
#define DEF_AS_SERVICE	"localhost:8001"
#define DEF_EXT_SERVICE	"localhost:8080"

static ChirpstackNsClient *client = NULL;

static void done()
{
	// destroy and free all
	if (client) {
		if (client->verbosity > 1)
			std::cerr << MSG_STOPPED << std::endl;
		delete client;
	}
	client = NULL;
	exit(0);
}

static void stop()
{
	if (client)
		client->disconnect();
}

void signalHandler(int signal)
{
	switch (signal)
	{
	case SIGINT:
		std::cerr << MSG_INTERRUPTED << std::endl;
		stop();
		done();
		break;
	default:
		break;
	}
}

#ifdef _MSC_VER
// TODO
void setSignalHandler()
{
}
#else
void setSignalHandler()
{
	struct sigaction action;
	memset(&action, 0, sizeof(struct sigaction));
	action.sa_handler = &signalHandler;
	sigaction(SIGINT, &action, NULL);
	sigaction(SIGHUP, &action, NULL);
}
#endif

/**
 * Parse command line
 * Return 0- success
 *        1- show help and exit, or command syntax error
 *        2- output file does not exists or can not open to write
 **/
int parseCmd(
	ChirpstackNsClient *client,
	int argc,
	char *argv[])
{
	// service 
  	struct arg_str *a_ns_service = arg_str0("n", "ns", "host:port", "Default " DEF_NS_SERVICE);
	struct arg_str *a_as_service = arg_str0("a", "as", "host:port", "Default " DEF_AS_SERVICE);
	struct arg_str *a_ex_service = arg_str0("e", "ext", "host:port", "Default " DEF_EXT_SERVICE);
	struct arg_str *a_gateway_id = arg_str0("g", "gateway", "id", "e.g. 00006cc3743eed46");
	struct arg_str *a_device_eui = arg_str0("d", "device", "eui", "e.g. 1122334455667788");
	struct arg_str *a_apikey = arg_str0("k", "key", "key", "API key string e.g. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e...");
	struct arg_str *a_apikey_file = arg_str0("K", "keyfile", "file", "API key file name e,g, api.key");

	struct arg_str *a_logfilename = arg_str0("l", "logfile", "<file>", "log file");
	struct arg_lit *a_daemonize = arg_lit0("d", "daemonize", "run daemon");
	struct arg_lit *a_verbosity = arg_litn("v", "verbose", 0, 3, "Set verbosity level");
	struct arg_lit *a_help = arg_lit0("?", "help", "Show this help");
	struct arg_end *a_end = arg_end(20);

	void *argtable[] = {
		a_ns_service, a_as_service, a_ex_service, a_gateway_id, a_device_eui, a_apikey, a_apikey_file,
		a_logfilename, a_daemonize, a_verbosity, a_help, a_end };

	int nerrors;

	// verify the argtable[] entries were allocated successfully
	if (arg_nullcheck(argtable) != 0)
	{
		arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
		return 1;
	}
	// Parse the command line as defined by argtable[]
	nerrors = arg_parse(argc, argv, argtable);

	client->daemonize = a_daemonize->count > 0;
	client->verbosity = a_verbosity->count;
	if (a_ns_service->count)
	{
		std::string s(*a_ns_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setNsService(s);
		}
	} else {
		client->setNsService(DEF_NS_SERVICE);
	}

	if (a_as_service->count)
	{
		std::string s(*a_as_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setAsService(s);
		}
	} else {
		client->setAsService(DEF_AS_SERVICE);
	}

	if (a_ex_service->count)
	{
		std::string s(*a_ex_service->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_SERVICE << std::endl;
			nerrors++;
		}
		else
		{
			client->setExtService(s);
		}
	} else {
		client->setExtService(DEF_EXT_SERVICE);
	}

	if (a_apikey_file->count) {
		std::string s = file2string(*a_apikey_file->sval);
		client->setToken(trim(s));
	} else {
		if (a_apikey->count) {
			client->setToken(*a_apikey->sval);
		}
	}

	if (a_gateway_id->count)
	{
		std::string s(*a_gateway_id->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_GATEWAY_ID << std::endl;
			nerrors++;
		}
		else
		{
			client->setGatewayId(s);
		}
	}

	if (a_device_eui->count)
	{
		std::string s(*a_device_eui->sval);
		if (s.length() == 0)
		{
			std::cerr << ERR_INVALID_DEVICE_EUI << std::endl;
			nerrors++;
		}
		else
		{
			client->setDeviceEUI(s);
		}
	}

	if (a_logfilename->count)
	{
		client->logfilename = *a_logfilename->sval;
		client->logstream = new std::fstream(*a_logfilename->sval, std::ostream::out);
		if (!client->logstream || client->logstream->bad())
		{
			std::cerr << ERR_INVALID_PAR_LOG_FILE << std::endl;
			nerrors++;
			if (client->logstream)
			{
				delete client->logstream;
				client->logstream = NULL;
			}
		}
	}
	else
	{
		client->logstream = &std::cerr;
		client->logfilename = "";
	}

	// special case: '--help' takes precedence over error reporting
	if ((a_help->count) || nerrors)
	{
		if (nerrors)
			arg_print_errors(stderr, a_end, progname.c_str());
		std::cerr << "Usage: " << progname << std::endl;
		arg_print_syntax(stderr, argtable, "\n");
		std::cerr << "UDP client" << std::endl;
		arg_print_glossary(stderr, argtable, "  %-25s %s\n");
		arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
		return 1;
	}

	arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
	return 0;
}

static void run()
{
	if (client) {
		client->connect();
		int r = client->run();
		if (r) {
			std::cerr << "Error " << r << ": " << strerror_client(r) << std::endl;
		}
	}
}

int main(
	int argc,
	char *argv[]
)
{
	client = new ChirpstackNsClient();

	if (parseCmd(client, argc, argv) != 0)
	{
		exit(ERR_CODE_COMMAND_LINE);
	}
#ifdef _MSC_VER
#else
	setSignalHandler();
#endif

	if (client->daemonize)
	{
		char wd[PATH_MAX];
		std::string progpath = getcwd(wd, PATH_MAX);
		if (client->verbosity > 1)
			std::cerr << MSG_DAEMON_STARTED << progpath << "/" << progname << MSG_DAEMON_STARTED_1 << std::endl;
		OPENSYSLOG()
		Daemonize daemonize(progname, progpath, run, stop, done);
		// CLOSESYSLOG()
	}
	else
	{
		setSignalHandler();
		run();
		done();
	}
	return 0;
}
