/*
 * OnCore object for the GPS packet monitor.
 *
 * This file is Copyright (c) 2010-2018 by the GPSD project
 * SPDX-License-Identifier: BSD-2-clause
 */

#include "gpsd_config.h"  /* must be before all includes */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>

#include "gpsd.h"
#include "bits.h"
#include "gpsmon.h"

#if defined(ONCORE_ENABLE) && defined(BINARY_ENABLE)
extern const struct gps_type_t driver_oncore;

static WINDOW *Ea1win, *Eawin, *Bbwin, *Enwin, *Bowin, *Aywin, *Aswin, *Atwin;
static unsigned char EaSVlines[8];

static const char *antenna[] = {
    "OK (conn)",
    "OC (short)",
    "UC (open)",
    "OU (short)"
};

static const char *sv_mode[] = {
    "srch",
    "acq",
    "AGCs",
    "pacq",
    "bits",
    "msgs",
    "satT",
    "epha",
    "avl"
};

static const char *pps_ctrl[] = {
    "off",
    "on",
    "on if >= 1 SV",
    "on if TRAIM ok"
};

static const char *pps_sync[] = {
    "UTC",
    "GPS"
};

static const char *traim_sol[] = {
    "OK",
    "ALARM",
    "UNKNOWN"
};

static const char *traim_status[] = {
    "detect & isolate",
    "detect",
    "insufficient"
};

static const char *pos_hold_mode[] = {
    "off",
    "on",
    "survey"
};

#define ONCTYPE(id2,id3) ((((unsigned int)id2)<<8)|(id3))

#define MAXTRACKSATS 	8	/* the most satellites being tracked */
#define MAXVISSATS 	12	/* the most satellites with known az/el */

static bool oncore_initialize(void)
{
    unsigned int i;

    Ea1win = subwin(devicewin, 5, 80, 1, 0);
    Eawin = subwin(devicewin, MAXTRACKSATS + 3, 27, 6, 0);
    Bbwin = subwin(devicewin, MAXVISSATS + 3, 22, 6, 28);
    Enwin = subwin(devicewin, 10, 29, 6, 51);
    Bowin = subwin(devicewin, 4, 11, 17, 0);
    Aywin = subwin(devicewin, 4, 15, 17, 12);
    Atwin = subwin(devicewin, 5, 9, 16, 51);
    Aswin = subwin(devicewin, 5, 19, 16, 61);

    if (Ea1win == NULL || Eawin == NULL || Bbwin == NULL || Enwin == NULL
	|| Bowin == NULL || Aswin == NULL || Atwin == NULL || Aywin == NULL)
	return false;

    (void)syncok(Ea1win, true);
    (void)syncok(Eawin, true);
    (void)syncok(Bbwin, true);
    (void)syncok(Enwin, true);
    (void)syncok(Bowin, true);
    (void)syncok(Aywin, true);
    (void)syncok(Aswin, true);
    (void)syncok(Atwin, true);

    (void)wborder(Ea1win, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Ea1win, A_BOLD);
    (void)mvwaddstr(Ea1win, 1, 1,
		    "Time:                                     Lat:              Lon:");
    (void)mvwaddstr(Ea1win, 2, 1,
		    "Antenna:             DOP:                 Speed:            Course:");
    (void)mvwaddstr(Ea1win, 3, 1,
		    "SV/vis:        Status:                                      Alt:");
    (void)mvwprintw(Ea1win, 4, 4, " @@Ea (pos) ");
    (void)wattrset(Ea1win, A_NORMAL);

    (void)wborder(Eawin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Eawin, A_BOLD);
    (void)mvwprintw(Eawin, 1, 1, "Ch PRN mode S/N ????????");
    (void)mvwprintw(Eawin, 10, 4, " @@Ea (sat) ");
    for (i = 0; i < 8; i++) {
	(void)mvwprintw(Eawin, (int)(i + 2), 1, "%2d", i);
    }
    (void)wattrset(Eawin, A_NORMAL);

    (void)wborder(Bbwin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Bbwin, A_BOLD);
    (void)mvwprintw(Bbwin, 1, 1, "PRN  Az El doppl ??");
    (void)mvwprintw(Bbwin, 14, 4, " @@Bb ");
    (void)wattrset(Bbwin, A_NORMAL);

    (void)wborder(Enwin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Enwin, A_BOLD);
    (void)mvwprintw(Enwin, 1, 1, "Time RAIM: ");
    (void)mvwprintw(Enwin, 2, 1, "Alarm limit:");
    (void)mvwprintw(Enwin, 3, 1, "PPS ctrl:");
    (void)mvwprintw(Enwin, 4, 1, "Pulse:");
    (void)mvwprintw(Enwin, 5, 1, "PPS sync:");
    (void)mvwprintw(Enwin, 6, 1, "TRAIM sol stat:");
    (void)mvwprintw(Enwin, 7, 1, "Status:");
    (void)mvwprintw(Enwin, 8, 1, "Time sol sigma:");
    (void)mvwprintw(Enwin, 9, 4, " @@En ");
    (void)wattrset(Enwin, A_NORMAL);

    (void)wborder(Bowin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Bowin, A_BOLD);
    (void)mvwprintw(Bowin, 1, 1, "UTC:");
    (void)mvwprintw(Bowin, 3, 2, " @@Bo ");
    (void)wattrset(Bowin, A_NORMAL);

    (void)wborder(Aywin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Aywin, A_BOLD);
    (void)mvwprintw(Aywin, 1, 1, "PPS offset:");
#ifndef PPS_ENABLE
    (void)mvwaddstr(Aywin, 1, 13, "N/A");
#endif /* PPS_ENABLE */
    (void)mvwprintw(Aywin, 3, 4, " @@Ay ");
    (void)wattrset(Aywin, A_NORMAL);

    (void)wborder(Atwin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Atwin, A_BOLD);
    (void)mvwprintw(Atwin, 1, 1, "PHold:");
    (void)mvwprintw(Atwin, 4, 1, " @@At ");
    (void)wattrset(Atwin, A_NORMAL);

    (void)wborder(Aswin, 0, 0, 0, 0, 0, 0, 0, 0),
	(void)wattrset(Aswin, A_BOLD);
    (void)mvwprintw(Aswin, 1, 1, "Lat:");
    (void)mvwprintw(Aswin, 2, 1, "Lon:");
    (void)mvwprintw(Aswin, 3, 1, "Alt:");
    (void)mvwprintw(Aswin, 4, 4, " @@As ");
    (void)wattrset(Aswin, A_NORMAL);

    memset(EaSVlines, 0, sizeof(EaSVlines));

    return true;
}

static void oncore_update(void)
{
    unsigned int i, off;
    unsigned char *buf;
    unsigned int type;

    assert(Eawin != NULL);
    buf = session.lexer.outbuffer;
    type = ONCTYPE(buf[2], buf[3]);
    switch (type) {
    case ONCTYPE('E', 'a'):
    {
	double lat, lon, alt;
	float speed, track;
	float dop;
	unsigned short year;
	unsigned char mon, day, hour, min, sec;
	unsigned int nsec;
	unsigned char dopt, nvis, nsat, status;
	char statusbuf[64];	/* 6+9+3+3+10+5+7+12+1=56 */

	mon = (unsigned char)getub(buf, 4);
	day = (unsigned char)getub(buf, 5);
	year = (unsigned short)getbeu16(buf, 6);
	hour = (unsigned char)getub(buf, 8);
	min = (unsigned char)getub(buf, 9);
	sec = (unsigned char)getub(buf, 10);
	nsec = (unsigned int)getbeu32(buf, 11);

	lat = (double)getbes32(buf, 15) / 3600000.0;
	lon = (double)getbes32(buf, 19) / 3600000.0;
	alt = (double)getbes32(buf, 23) / 100.0;
	speed = (float)(getbeu16(buf, 31) / 100.0);
	track = (float)(getbeu16(buf, 33) / 10.0);
	dop = (float)(getbeu16(buf, 35) / 10.0);
	dopt = (unsigned char)getub(buf, 37);
	nvis = (unsigned char)getub(buf, 38);
	nsat = (unsigned char)getub(buf, 39);
	status = (unsigned char)getub(buf, 72);

	(void)mvwprintw(Ea1win, 1, 7, "%04d-%02d-%02d %02d:%02d:%02d.%09d",
			year, mon, day, hour, min, sec, nsec);
	(void)mvwprintw(Ea1win, 1, 47, "%10.6lf %c",
			fabs(lat), lat < 0 ? 'S' : lat > 0 ? 'N' : ' ');
	(void)mvwprintw(Ea1win, 1, 66, "%10.6lf %c",
			fabs(lon), lon < 0 ? 'W' : lon > 0 ? 'E' : ' ');

	(void)mvwprintw(Ea1win, 2, 50, "%6.2f m/s", speed);
	(void)mvwprintw(Ea1win, 2, 70, "%5.1f", track);
	(void)mvwprintw(Ea1win, 3, 68, "%8.2f m", alt);

	(void)snprintf(statusbuf, sizeof(statusbuf), "%s%s%s%s%s%s%s%s%s",
		       (status & 0x80) ? "PProp " : "",
		       (status & 0x40) ? "PoorGeom " : "",
		       (status & 0x20) ? "3D " : "",
		       (status & 0x10) ? "2D " : "",
		       (status & 0x08) ? "Acq/PHold " : "",
		       (status & 0x04) ? "Diff " : "",
		       (status & 0x02) ? "Ins (<3 SV) " : "",
		       (status & 0x01) ? "BadAlm " : "",
		       (dopt   & 0x20) ? "survey " : "");

	(void)mvwprintw(Ea1win, 3, 24, "%-37s", statusbuf);

	(void)mvwprintw(Ea1win, 2, 10, "%-10s", antenna[dopt >> 6]);

	(void)mvwprintw(Ea1win, 2, 27, "%s %4.1f",
			(dopt & 1) ? "hdop" : "pdop", dop);

	(void)mvwprintw(Ea1win, 3, 10, "%d/%d ", nsat, nvis);
    }

	for (i = 0; i < 8; i++) {
	    unsigned char sv, mode, sn, status;

	    off = 40 + 4 * i;
	    sv = (unsigned char)getub(buf, off);
	    mode = (unsigned char)getub(buf, off + 1);
	    sn = (unsigned char)getub(buf, off + 2);
	    status = (unsigned char)getub(buf, off + 3);
	    (void)wmove(Eawin, (int)(i + 2), 3);
	    (void)wprintw(Eawin, " %3d", sv);
	    EaSVlines[i] = sv;
	    if (mode <= (unsigned char)8)
		(void)wprintw(Eawin, " %4s", sv_mode[mode]);
	    else
		(void)wprintw(Eawin, "    -");
	    (void)wprintw(Eawin, " %3d", sn);
	    (void)wprintw(Eawin, " %c%c%c%c%c%c%c%c", (status & 0x80) ? 'p' : ' ',	/* used for pos fix  */
			  (status & 0x40) ? 'M' : ' ',	/* momentum alert    */
			  (status & 0x20) ? 's' : ' ',	/* anti-spoof   */
			  (status & 0x10) ? 'U' : ' ',	/* unhealthy     */
			  (status & 0x08) ? 'I' : ' ',	/* inaccurate   */
			  (status & 0x04) ? 'S' : ' ',	/* spare             */
			  (status & 0x02) ? 't' : ' ',	/* used for time sol */
			  (status & 0x01) ? 'P' : ' ');	/* parity error      */
	}

	monitor_log("Ea =");
	break;

    case ONCTYPE('B', 'b'):
    {
	unsigned int Bblines[12];
	unsigned int Bblines_mask;
	unsigned int next_line;
	unsigned char sv;
	unsigned int ch;

	ch = (unsigned int)getub(buf, 4);
	if (ch > 12)
	    ch = 12;
	/* Try to align the entries for each SV of the Bb message at
	 * the same lines as in the Ea message.
	 */
	memset(Bblines, 0, sizeof(Bblines));
	Bblines_mask = 0;
	for (i = 0; i < ch; i++) {
	    unsigned int j;
	    off = 5 + 7 * i;
	    sv = (unsigned char)getub(buf, off);
	    for (j = 0; j < 8; j++)
		if (EaSVlines[j] == sv && !(Bblines_mask & (1 << (j + 2)))) {
		    Bblines[i] = j + 2;
		    Bblines_mask |= 1 << Bblines[i];
		}
	}
	/* SVs not seen in Ea fill lines left over. */
	next_line = 2;
	for (i = 0; i < ch; i++) {
	    if (Bblines[i] == 0) {
		while (Bblines_mask & (1 << next_line))
		    next_line++;
		Bblines[i] = next_line++;
		Bblines_mask |= 1 << Bblines[i];
	    }
	}
	/* Ready to print on precalculated lines. */
	for (i = 0; i < ch; i++) {
	    int doppl, el, az, health;

	    off = 5 + 7 * i;
	    sv = (unsigned char)getub(buf, off);
	    doppl = (int)getbes16(buf, off + 1);
	    el = (int)getub(buf, off + 3);
	    az = (int)getbeu16(buf, off + 4);
	    health = (int)getub(buf, off + 6);

	    (void)wmove(Bbwin, (int)Bblines[i], 1);
	    (void)wprintw(Bbwin, "%3d %3d %2d %5d %c%c", sv, az, el, doppl, (health & 0x02) ? 'U' : ' ',	/* unhealthy */
			  (health & 0x01) ? 'R' : ' ');	/* removed   */
	}

	for (i = 2; i < 14; i++)
	    if (!(Bblines_mask & (1 << i))) {
		(void)wmove(Bbwin, (int)i, 1);
		(void)wprintw(Bbwin, "                   ");
	    }
    }

	monitor_log("Bb =");
	break;

    case ONCTYPE('E', 'n'):
    {
	unsigned char traim, ctrl, pulse, sync, sol_stat, status;
	float alarm, sigma;

	traim = (unsigned char)getub(buf, 5);
	alarm = (float)(getbeu16(buf, 6) / 10.);
	ctrl = (unsigned char)getub(buf, 8);
	pulse = (unsigned char)getub(buf, 19);
	sync = (unsigned char)getub(buf, 20);
	sol_stat = (unsigned char)getub(buf, 21);
	status = (unsigned char)getub(buf, 22);
	sigma = (float)(getbeu16(buf, 23));

	(void)mvwprintw(Enwin, 1, 24, "%3s", traim ? "on" : "off");
	(void)mvwprintw(Enwin, 2, 18, "%6.1f us", alarm);
	(void)mvwprintw(Enwin, 3, 13, "%14s", pps_ctrl[ctrl]);
	(void)mvwprintw(Enwin, 4, 24, "%3s", pulse ? "on" : "off");
	/* coverity_submit[tainted_data] */
	(void)mvwprintw(Enwin, 5, 24, "%3s", pps_sync[sync]);
	(void)mvwprintw(Enwin, 6, 20, "%7s", traim_sol[sol_stat]);
	(void)mvwprintw(Enwin, 7, 11, "%16s", traim_status[status]);
	(void)mvwprintw(Enwin, 8, 18, "%6.3f us", sigma * 0.001);
    }

	monitor_log("En =");
	break;

    case ONCTYPE('B', 'o'):
    {
	unsigned char utc_offset;

	utc_offset = (unsigned char)getub(buf, 4);

	if (utc_offset != (unsigned char)0)
	    (void)mvwprintw(Bowin, 2, 2, "GPS%+4d", utc_offset);
	else
	    (void)mvwprintw(Bowin, 2, 2, "unknown", utc_offset);
    }

	monitor_log("Bo =");
	break;

    case ONCTYPE('A', 'y'):
    {
	double pps_offset;

        /* note this is the GPS calc'ed offset
         * not a PPS offset gpsmon calculated */
	pps_offset = (double)getbes32(buf, 4) / 1000000.0;

	(void)mvwprintw(Aywin, 2, 2, " %7.3f ms", pps_offset);
    }

	monitor_log("Ay =");
	break;

    case ONCTYPE('A', 't'):
    {
	unsigned char mode;

	mode = (unsigned char)getub(buf, 4);

	(void)mvwprintw(Atwin, 2, 1, "%6s", pos_hold_mode[mode]);
    }

	monitor_log("At =");
	break;

    case ONCTYPE('A', 's'):
    {
	double lat, lon, alt;

	lat = (double)getbes32(buf, 4) / 3600000.0;
	lon = (double)getbes32(buf, 8) / 3600000.0;
	alt = (double)getbes32(buf, 12) / 100.0;

	(void)mvwprintw(Aswin, 1, 5, "%10.6lf %c",
			fabs(lat), lat < 0 ? 'S' : lat > 0 ? 'N' : ' ');
	(void)mvwprintw(Aswin, 2, 5, "%10.6lf %c",
			fabs(lon), lon < 0 ? 'W' : lon > 0 ? 'E' : ' ');
	(void)mvwprintw(Aswin, 3, 7, "%8.2f m", alt);
    }

	monitor_log("As =");
	break;

    default:
	monitor_log("%c%c =", buf[2], buf[3]);
	break;
    }
}

static int oncore_command(char line[]UNUSED)
{
    return COMMAND_UNKNOWN;
}

static void oncore_wrap(void)
{
    (void)delwin(Ea1win);
    (void)delwin(Eawin);
    (void)delwin(Bbwin);
    (void)delwin(Enwin);
    (void)delwin(Bowin);
    (void)delwin(Aywin);
    (void)delwin(Atwin);
    (void)delwin(Aswin);
}

const struct monitor_object_t oncore_mmt = {
    .initialize = oncore_initialize,
    .update = oncore_update,
    .command = oncore_command,
    .wrap = oncore_wrap,
    .min_y = 20,.min_x = 80,	/* size of the device window */
    .driver = &driver_oncore,
};

#endif /* defined(ONCORE_ENABLE) && defined(BINARY_ENABLE) */
