/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple 4G modem location application
 *
 * 2024/07/01
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

#include <cJSON.h>

#define TTY_4G_FILE          "/tmp/4g_modem_tty"
#define TTY_4G_SIGLEVEL      "/etc/signal_4g.txt"
#define TTY_4G_LOCFILE       "/etc/location_4g.txt"
#define CHINA_MOBILE         0
#define CHINA_UNICOM         1
#define CHINA_TELECOM        11
#define TTY_4G_BUFSIZ        16384

struct cell_info {
	int                      ci_lac;
	int                      ci_ci;
	int                      operator;
	int                      siglevel;
};

void msleep_for4g(unsigned int msec)
{
	struct timespec t;

	t.tv_sec = (time_t) (msec / 1000);
	t.tv_nsec = (long) ((msec % 1000) * 1000000);
	nanosleep(&t, NULL);
}

/*
 * AT+COPS?
 * +COPS: 0,0,"CHN-CT",7
 */

char * skip_spaces_quote(char * s)
{
	char cha;
	for (;;) {
		cha = *s;
		if (cha != ' ' && cha != '\t' && cha != '"' && cha != '\'')
			break;
		s++;
	}
	return s;
}

static int cell_info_find(char * tbuf, size_t tlen, struct cell_info * pci)
{
	int i, error;
	char * fields[6], * p;
	char * cgreg, * saveptr;

	cgreg = (char *) memmem(tbuf, tlen, "CGREG:", 6);
	if (cgreg == NULL)
		return -1;

	fields[0] = fields[1] = fields[2] = NULL;
	fields[3] = fields[4] = fields[5] = NULL;
	cgreg += 6;
	saveptr = NULL;
	for (i = 0; i < 6; ++i) {
		char * p;
		p = strtok_r(cgreg, ",", &saveptr);
		if (p == NULL)
			break;

		fields[i] = p;
		cgreg = NULL;
	}

	if (fields[2] == NULL || fields[3] == NULL)
		return -1;

	p = skip_spaces_quote(fields[2]);
	errno = 0;
	saveptr = NULL;
	i = (int) strtol(p, &saveptr, 16);
	error = errno;
	if (error || p == saveptr) {
		fprintf(stderr, "Error, invalid location area code: %s\n", fields[2]);
		fflush(stderr);
		return -1;
	}
	pci->ci_lac = i;

	p = skip_spaces_quote(fields[3]);
	errno = 0;
	saveptr = NULL;
	i = (int) strtol(p, &saveptr, 16);
	error = errno;
	if (error || p == saveptr) {
		fprintf(stderr, "Error, invalid cell ID: %s\n", fields[3]);
		fflush(stderr);
		return -1;
	}
	pci->ci_ci = i;
	return 0;
}

static int cell_info_read(const char * pdev, struct cell_info * pci)
{
	ssize_t rval, tlen;
	int fd, error, i;
	char * ttybuf;
	const char * atcmds[] = {
		"AT+CGREG=2\r\n", "AT+COPS?\r\n", "AT+CGREG?\r\n",
		"AT+CSQ?\r\n", "AT+CSQ\r\n", NULL, /* Get signal level */
	};

	tlen = 0;
	ttybuf = NULL;

	do { // setup 115200 8N1 for 4G modem
		int status;
		pid_t rpid, pid;

		pid = fork();
		if (pid < 0) {
			error = errno;
			fprintf(stderr, "Error, failed to fork: %s\n", strerror(error));
			fflush(stderr);
			return -1;
		}

		if (pid == 0) {
			execlp("stty", "stty", "-F", pdev, "raw", "115200",
				"cs8", "-hupcl", "-onlcr", "-iexten", "-echo",
				"-echoe", "-echok", "-echoctl", "-echoke", NULL);
			error = errno;
			fprintf(stderr, "Error, failed to invoke stty: %s\n", strerror(error));
			fflush(stderr);
			_exit(1);
		}

		for (;;) {
			status = 0;
			rpid = waitpid(pid, &status, 0);
			if (rpid == pid)
				break;

			error = errno;
			if (error != EINTR) {
				fprintf(stderr, "Error, failed to wait stty: %s\n",
					strerror(error));
				fflush(stderr);
				break;
			}
		}
	} while (0);

	fd = open(pdev, O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
	if (fd < 0) {
		error = errno;
		fprintf(stderr, "Error, failed to open(%s): %s\n", pdev, strerror(error));
		fflush(stderr);
		return -1;
	}

	ttybuf = (char *) malloc(TTY_4G_BUFSIZ + 1);
	if (ttybuf == NULL) {
		fputs("Error, out of memory!\n", stderr);
		fflush(stderr);
		close(fd);
		return -1;
	}

	for (;;) {
		/* clear pending buffers */
		rval = read(fd, ttybuf, TTY_4G_BUFSIZ);
		if (rval <= 0)
			break;
	}

	for (i = 0; i < 0x5; ++i) {
		size_t atlen;
		const char * atcmd;

		atcmd = atcmds[i];
		if (atcmd == NULL)
			break;

		atlen = strlen(atcmd);
		rval = write(fd, atcmd, atlen);
		if (rval != (ssize_t) atlen) {
			error = errno;
			fprintf(stderr, "Error, failed to write command: %s => %s",
				strerror(error), atcmd);
			fflush(stderr);

			close(fd);
			return -1;
		}

		msleep_for4g(300);
		while ((tlen + 256) < TTY_4G_BUFSIZ) {
			rval = read(fd, ttybuf + tlen, (size_t) (TTY_4G_BUFSIZ - tlen));
			if (rval <= 0)
				break;
			tlen += (ssize_t) rval;
		}
	}

	if (tlen == 0) {
		close(fd);
		free(ttybuf);
		return -1;
	}

	close(fd); fd = -1;
	if (tlen >= TTY_4G_BUFSIZ)
		tlen = TTY_4G_BUFSIZ - 1;
	ttybuf[tlen] = '\0';

	/* find signal level */
	do {
		char cha;
		int level = -1;
		char * csqp = (char *) memmem(ttybuf, (size_t) tlen, "+CSQ:", 5);
		if (csqp != NULL) {
			csqp += 0x5;
			cha = *csqp;
			while (cha != '\0') {
				if (cha != ' ' && cha != '\t')
					break;
				csqp++;
				cha = *csqp;
			}

			if (cha != '\0')
				level = (int) strtol(csqp, NULL, 0);
		}

		pci->siglevel = level;
		fprintf(stdout, "4G modem signal level: %d\n", level);
		fflush(stdout);
	} while (0);

	if (memmem(ttybuf, (size_t) tlen, "UNICOM", 6) != NULL)
		pci->operator = CHINA_UNICOM;
	else if (memmem(ttybuf, (size_t) tlen, "CHN-CT", 6) != NULL)
		pci->operator = CHINA_TELECOM;
	else if (memmem(ttybuf, (size_t) tlen, "CMCC", 4) != NULL)
		pci->operator = CHINA_MOBILE;
	else if (memmem(ttybuf, (size_t) tlen, "CHINA MOBILE", 12) != NULL)
		pci->operator = CHINA_MOBILE;
	else if (memmem(ttybuf, (size_t) tlen, "\"46000\"", 7) != NULL)
		pci->operator = CHINA_MOBILE;
	else
		pci->operator = CHINA_UNICOM;

	error = cell_info_find(ttybuf, (size_t) tlen, pci);
	free(ttybuf);
	return error;
}

static int get_4gtty(char * output, size_t len)
{
	int fd;
	FILE * fp;
	char buff[128];
	const char * tty4g = TTY_4G_FILE;

	fp = NULL;
	output[0] = '\0';
	fd = open(tty4g, O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		return -1;
	fp = fdopen(fd, "rb");
	if (fp == NULL) {
		int error = errno;
		fprintf(stderr, "Error, failed to open %d: %s\n",
			fd, strerror(error));
		fflush(stderr);
		close(fd);
		return -1;
	}

	for (;;) {
		size_t l;
		memset(buff, 0, sizeof(buff));
		if (fgets(buff, sizeof(buff) - 1, fp) == NULL)
			break;

		l = strlen(buff);
		while (l > 0) {
			char cha = buff[l - 1];
			if (cha != '\r' && cha != '\n')
				break;
			buff[l - 1] = '\0';
			l--;
		}

		if (strncmp(buff, "/dev/", 5) == 0) {
			fclose(fp);
			strncpy(output, buff, len);
			return 0;
		}
	}

	fclose(fp);
	return -1;
}

static int parse_location_file(const char * tmpfile)
{
	int fd, rval, error;
	cJSON * json, * res;
	cJSON * lat, * lng;
	char * pbuf;
	ssize_t rlen;

	fd = -1;
	rval = 0;
	json = NULL;
	pbuf = (char *) malloc(TTY_4G_BUFSIZ);
	if (pbuf == NULL) {
		rval = -1;
		fputs("Error, system out of memory!\n", stderr);
		fflush(stderr);
		goto err0;
	}

	fd = open(tmpfile, O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		error = errno;
		fprintf(stderr, "Error, failed to open '%s': %s\n",
			tmpfile, strerror(error));
		fflush(stderr);
		rval = -1;
		goto err0;
	}

	rlen = read(fd, pbuf, TTY_4G_BUFSIZ - 1);
	close(fd);
	if (rlen <= 0) {
		fprintf(stderr, "Error, failed to read '%s'\n", tmpfile);
		fflush(stderr);
		rval = -1;
		goto err0;
	}

	pbuf[rlen] = '\0';
	json = cJSON_Parse(pbuf);
	if (json == NULL || cJSON_IsObject(json) == 0) {
		fprintf(stderr, "Error, failed to decode as JSON: %s\n", pbuf);
		fflush(stderr);
		rval = -1;
		goto err0;
	}

	res = cJSON_GetObjectItem(json, "result");
	if (res == NULL || cJSON_IsObject(json) == 0) {
		fprintf(stderr, "Error, result not found: %s\n", pbuf);
		fflush(stderr);
		rval = -1;
		goto err0;
	}

	lat = cJSON_GetObjectItem(res, "lat");
	lng = cJSON_GetObjectItem(res, "lng");
	if (lat != NULL && lng != NULL) {
		FILE * fp;
		rval = 0;
		fp = fopen(TTY_4G_LOCFILE, "wb");
		if (fp != NULL) {
			fprintf(fp, "%f,%f", lng->valuedouble, lat->valuedouble);
			fclose(fp);
		}
	} else {
		rval = -1;
		fprintf(stderr, "Error, cannot find location: %s\n", pbuf);
		fflush(stderr);
	}

err0:
	if (pbuf != NULL)
		free(pbuf);
	if (json != NULL)
		cJSON_Delete(json);
	return rval;
}

static int get_4g_location(const struct cell_info * pci)
{
	int i, status, rval, error;
	pid_t pid;
	char * args[11];
	char body[256];
	const char * tmpfile = "/tmp/location_4g.out";

	rval = 0;
	unlink(tmpfile); // Remove temporary file
	snprintf(body, sizeof(body), "mcc=460&mnc=%d&lac=%d&ci=%d&hex=10",
		pci->operator, pci->ci_lac, pci->ci_ci);

	args[0] = strdup("curl");
	args[1] = strdup("-s");
	args[2] = strdup("--http1.1");
	args[3] = strdup("-X");
	args[4] = strdup("POST");
	args[5] = strdup("--data-binary");
	args[6] = strdup(body);
	args[7] = strdup("-o");
	args[8] = strdup(tmpfile);
	args[9] = strdup("http://openluat.lnxall.com/openluat/get_gps");
	args[10] = NULL;

	pid = fork();
	if (pid < 0) {
		error = errno;
		fprintf(stderr, "Error, failed to fork: %s\n", strerror(error));
		fflush(stderr);

		rval = -1;
		goto err0;
	}

	if (pid == 0) {
		execvp(args[0], args);
		error = errno;
		fprintf(stderr, "Error, failed to invoke curl: %s\n", strerror(error));
		fflush(stderr);
		_exit(1);
	}

	for (;;) {
		pid_t rpid;
		status = 0;
		rpid = waitpid(pid, &status, 0);
		if (rpid == pid)
			break;

		error = errno;
		if (error != EINTR) {
			status = -1;
			fprintf(stderr, "Error, failed to wait %ld: %s\n",
				(long) pid, strerror(error));
			fflush(stderr);
			break;
		}
	}

	if (status != 0) {
		rval = -1;
		unlink(tmpfile);
		goto err0;
	}

	rval = parse_location_file(tmpfile);
	unlink(tmpfile);

err0:
	for (i = 0; i < 11; ++i) {
		if (args[i] != NULL) {
			free(args[i]);
			args[i] = NULL;
		}
	}
	return rval;
}

int main(int argc, char *argv[])
{
	int ret, gotit;
	char tty[32];
	struct timespec tspec;
	struct cell_info cinfo;
	struct cell_info bakci;

	tspec.tv_sec = 0;
	tspec.tv_nsec = 0;
	if (clock_gettime(CLOCK_BOOTTIME, &tspec) == 0 && (tspec.tv_sec < 120))
		msleep_for4g((120 - tspec.tv_sec) * 1000);

	gotit = 0;
	memset(&bakci, 0, sizeof(bakci));
	signal(SIGINT, SIG_DFL);
	for (;;) {
		ret = get_4gtty(tty, sizeof(tty));
		if (ret < 0)
			goto next;

		if (access(tty, F_OK) != 0)
			goto next;

		memset(&cinfo, 0, sizeof(cinfo));
		ret = cell_info_read(tty, &cinfo);
		/* write 4G modem signal level */
		if (cinfo.siglevel > 0) {
			FILE * fp;
			const char * pfile;
			pfile = TTY_4G_SIGLEVEL;
			fp = fopen(pfile, "wb");
			if (fp != NULL) {
				fprintf(fp, "%d", cinfo.siglevel);
				fflush(fp);
				fclose(fp);
			}
		}

		if (ret < 0) {
			fprintf(stderr, "Error, failed to get cell info: %d\n", ret);
			fflush(stderr);
			goto next;
		}

		if (memcmp(&cinfo, &bakci, sizeof(cinfo)) != 0) {
			if (get_4g_location(&cinfo) >= 0) {
				gotit = 1;
				memcpy(&bakci, &cinfo, sizeof(cinfo));
			}
		}
next:
		if (gotit != 0)
			msleep_for4g(86400 * 1000);
		else
			msleep_for4g(3600 * 1000);
		signal(SIGINT, SIG_DFL);
	}
	return 0;
}
