/*!
 * \file rtklib_preceph.cc
 * \brief precise ephemeris and clock functions
 * \authors <ul>
 *          <li> 2007-2013, T. Takasu
 *          <li> 2017, Javier Arribas
 *          <li> 2017, Carles Fernandez
 *          </ul>
 *
 * This is a derived work from RTKLIB http://www.rtklib.com/
 * The original source code at https://github.com/tomojitakasu/RTKLIB is
 * released under the BSD 2-clause license with an additional exclusive clause
 * that does not apply here. This additional clause is reproduced below:
 *
 * " The software package includes some companion executive binaries or shared
 * libraries necessary to execute APs on Windows. These licenses succeed to the
 * original ones of these software. "
 *
 * Neither the executive binaries nor the shared libraries are required by, used
 * or included in GNSS-SDR.
 *
 * -----------------------------------------------------------------------------
 * Copyright (C) 2007-2013, T. Takasu
 * Copyright (C) 2017, Javier Arribas
 * Copyright (C) 2017, Carles Fernandez
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 *
 * References :
 *    [1] S.Hilla, The Extended Standard Product 3 Orbit Format (SP3-c),
 *        12 February, 2007
 *    [2] J.Ray, W.Gurtner, RINEX Extensions to Handle Clock Information,
 *        27 August, 1998
 *    [3] D.D.McCarthy, IERS Technical Note 21, IERS Conventions 1996, July 1996
 *    [4] D.A.Vallado, Fundamentals of Astrodynamics and Applications 2nd ed,
 *        Space Technology Library, 2004
 *
 * -----------------------------------------------------------------------------
 */

#include "rtklib_preceph.h"
#include "rtklib_rtkcmn.h"
#include <cstring>
#include <vector>

/* satellite code to satellite system ----------------------------------------*/
int code2sys(char code)
{
    if (code == 'G' || code == ' ')
        {
            return SYS_GPS;
        }
    if (code == 'R')
        {
            return SYS_GLO;
        }
    if (code == 'E')
        {
            return SYS_GAL; /* extension to sp3-c */
        }
    if (code == 'J')
        {
            return SYS_QZS; /* extension to sp3-c */
        }
    if (code == 'C')
        {
            return SYS_BDS; /* extension to sp3-c */
        }
    if (code == 'L')
        {
            return SYS_LEO; /* extension to sp3-c */
        }
    return SYS_NONE;
}


/* read sp3 header -----------------------------------------------------------*/
int readsp3h(FILE *fp, gtime_t *time, char *type, int *sats,
    double *bfact, char *tsys)
{
    int i;
    int j;
    int k = 0;
    int ns = 0;
    int sys;
    int prn;
    char buff[1024];

    trace(3, "readsp3h:\n");

    for (i = 0; i < 22; i++)
        {
            if (!fgets(buff, sizeof(buff), fp))
                {
                    break;
                }

            if (i == 0)
                {
                    *type = buff[2];
                    if (str2time(buff, 3, 28, time))
                        {
                            return 0;
                        }
                }
            else if (2 <= i && i <= 6)
                {
                    if (i == 2)
                        {
                            ns = static_cast<int>(str2num(buff, 4, 2));
                        }
                    for (j = 0; j < 17 && k < ns; j++)
                        {
                            sys = code2sys(buff[9 + 3 * j]);
                            prn = static_cast<int>(str2num(buff, 10 + 3 * j, 2));
                            if (k < MAXSAT)
                                {
                                    sats[k++] = satno(sys, prn);
                                }
                        }
                }
            else if (i == 12)
                {
                    int ret = std::snprintf(tsys, 3, "%s", buff + 9);  // NOLINT(runtime/printf)
                    if (ret < 0 || ret > 3)
                        {
                            trace(3, "Error reading sp3 header\n");
                        }
                }
            else if (i == 14)
                {
                    bfact[0] = str2num(buff, 3, 10);
                    bfact[1] = str2num(buff, 14, 12);
                }
        }
    return ns;
}


/* add precise ephemeris -----------------------------------------------------*/
int addpeph(nav_t *nav, peph_t *peph)
{
    peph_t *nav_peph;

    if (nav->ne >= nav->nemax)
        {
            nav->nemax += 256;
            if (!(nav_peph = static_cast<peph_t *>(realloc(nav->peph, sizeof(peph_t) * nav->nemax))))
                {
                    trace(1, "readsp3b malloc error n=%d\n", nav->nemax);
                    free(nav->peph);
                    nav->peph = nullptr;
                    nav->ne = nav->nemax = 0;
                    return 0;
                }
            nav->peph = nav_peph;
        }
    nav->peph[nav->ne++] = *peph;
    return 1;
}


/* read sp3 body -------------------------------------------------------------*/
void readsp3b(FILE *fp, char type, int *sats __attribute__((unused)), int ns, const double *bfact,
    char *tsys, int index, int opt, nav_t *nav)
{
    peph_t peph;
    gtime_t time;
    double val;
    double std;
    double base;
    int i;
    int j;
    int sat;
    int sys;
    int prn;
    int n = ns * (type == 'P' ? 1 : 2);
    int pred_o;
    int pred_c;
    int v;
    char buff[1024];

    trace(3, "readsp3b: type=%c ns=%d index=%d opt=%d\n", type, ns, index, opt);

    while (fgets(buff, sizeof(buff), fp))
        {
            if (!strncmp(buff, "EOF", 3))
                {
                    break;
                }

            if (buff[0] != '*' || str2time(buff, 3, 28, &time))
                {
                    trace(2, "sp3 invalid epoch %31.31s\n", buff);
                    continue;
                }
            if (!strcmp(tsys, "UTC"))
                {
                    time = utc2gpst(time); /* utc->gpst */
                }
            peph.time = time;
            peph.index = index;

            for (i = 0; i < MAXSAT; i++)
                {
                    for (j = 0; j < 4; j++)
                        {
                            peph.pos[i][j] = 0.0;
                            peph.std[i][j] = 0.0F;
                            peph.vel[i][j] = 0.0;
                            peph.vst[i][j] = 0.0F;
                        }
                    for (j = 0; j < 3; j++)
                        {
                            peph.cov[i][j] = 0.0F;
                            peph.vco[i][j] = 0.0F;
                        }
                }
            for (i = pred_o = pred_c = v = 0; i < n && fgets(buff, sizeof(buff), fp); i++)
                {
                    if (strlen(buff) < 4 || (buff[0] != 'P' && buff[0] != 'V'))
                        {
                            continue;
                        }

                    sys = buff[1] == ' ' ? SYS_GPS : code2sys(buff[1]);
                    prn = static_cast<int>(str2num(buff, 2, 2));
                    if (sys == SYS_SBS)
                        {
                            prn += 100;
                        }
                    else if (sys == SYS_QZS)
                        {
                            prn += 192; /* extension to sp3-c */
                        }

                    if (!(sat = satno(sys, prn)))
                        {
                            continue;
                        }

                    if (buff[0] == 'P')
                        {
                            pred_c = strlen(buff) >= 76 && buff[75] == 'P';
                            pred_o = strlen(buff) >= 80 && buff[79] == 'P';
                        }
                    for (j = 0; j < 4; j++)
                        {
                            /* read option for predicted value */
                            if (j < 3 && (opt & 1) && pred_o)
                                {
                                    continue;
                                }
                            if (j < 3 && (opt & 2) && !pred_o)
                                {
                                    continue;
                                }
                            if (j == 3 && (opt & 1) && pred_c)
                                {
                                    continue;
                                }
                            if (j == 3 && (opt & 2) && !pred_c)
                                {
                                    continue;
                                }

                            val = str2num(buff, 4 + j * 14, 14);
                            std = str2num(buff, 61 + j * 3, j < 3 ? 2 : 3);

                            if (buff[0] == 'P')
                                { /* position */
                                    if (val != 0.0 && fabs(val - 999999.999999) >= 1e-6)
                                        {
                                            peph.pos[sat - 1][j] = val * (j < 3 ? 1000.0 : 1e-6);
                                            v = 1; /* valid epoch */
                                        }
                                    if ((base = bfact[j < 3 ? 0 : 1]) > 0.0 && std > 0.0)
                                        {
                                            peph.std[sat - 1][j] = static_cast<float>(std::pow(base, std) * (j < 3 ? 1e-3 : 1e-12));
                                        }
                                }
                            else if (v)
                                { /* velocity */
                                    if (val != 0.0 && fabs(val - 999999.999999) >= 1e-6)
                                        {
                                            peph.vel[sat - 1][j] = val * (j < 3 ? 0.1 : 1e-10);
                                        }
                                    if ((base = bfact[j < 3 ? 0 : 1]) > 0.0 && std > 0.0)
                                        {
                                            peph.vst[sat - 1][j] = static_cast<float>(std::pow(base, std) * (j < 3 ? 1e-7 : 1e-16));
                                        }
                                }
                        }
                }
            if (v)
                {
                    if (!addpeph(nav, &peph))
                        {
                            return;
                        }
                }
        }
}


/* compare precise ephemeris -------------------------------------------------*/
int cmppeph(const void *p1, const void *p2)
{
    const auto *q1 = static_cast<const peph_t *>(p1);
    const auto *q2 = static_cast<const peph_t *>(p2);
    double tt = timediff(q1->time, q2->time);
    return tt < -1e-9 ? -1 : (tt > 1e-9 ? 1 : q1->index - q2->index);
}


/* combine precise ephemeris -------------------------------------------------*/
void combpeph(nav_t *nav, int opt)
{
    int i;
    int j;
    int k;
    int m;

    trace(3, "combpeph: ne=%d\n", nav->ne);

    qsort(nav->peph, nav->ne, sizeof(peph_t), cmppeph);

    if (opt & 4)
        {
            return;
        }

    for (i = 0, j = 1; j < nav->ne; j++)
        {
            if (fabs(timediff(nav->peph[i].time, nav->peph[j].time)) < 1e-9)
                {
                    for (k = 0; k < MAXSAT; k++)
                        {
                            if (norm_rtk(nav->peph[j].pos[k], 4) <= 0.0)
                                {
                                    continue;
                                }
                            for (m = 0; m < 4; m++)
                                {
                                    nav->peph[i].pos[k][m] = nav->peph[j].pos[k][m];
                                }
                            for (m = 0; m < 4; m++)
                                {
                                    nav->peph[i].std[k][m] = nav->peph[j].std[k][m];
                                }
                            for (m = 0; m < 4; m++)
                                {
                                    nav->peph[i].vel[k][m] = nav->peph[j].vel[k][m];
                                }
                            for (m = 0; m < 4; m++)
                                {
                                    nav->peph[i].vst[k][m] = nav->peph[j].vst[k][m];
                                }
                        }
                }
            else if (++i < j)
                {
                    nav->peph[i] = nav->peph[j];
                }
        }
    nav->ne = i + 1;

    trace(4, "combpeph: ne=%d\n", nav->ne);
}


/* read sp3 precise ephemeris file ---------------------------------------------
 * read sp3 precise ephemeris/clock files and set them to navigation data
 * args   : char   *file       I   sp3-c precise ephemeris file
 *                                 (wind-card * is expanded)
 *          nav_t  *nav        IO  navigation data
 *          int    opt         I   options (1: only observed + 2: only predicted +
 *                                 4: not combined)
 * return : none
 * notes  : see ref [1]
 *          precise ephemeris is appended and combined
 *          nav->peph and nav->ne must by properly initialized before calling the
 *          function
 *          only files with extensions of .sp3, .SP3, .eph* and .EPH* are read
 *-----------------------------------------------------------------------------*/
void readsp3(const char *file, nav_t *nav, int opt)
{
    FILE *fp;
    gtime_t time = {0, 0};
    double bfact[2] = {};
    int i;
    int j;
    int n;
    int ns;
    std::vector<int> sats(MAXSAT);
    char *efiles[MAXEXFILE];
    char *ext;
    char type = ' ';
    char tsys[4] = "";

    trace(3, "readpephs: file=%s\n", file);

    for (i = 0; i < MAXEXFILE; i++)
        {
            if (!(efiles[i] = static_cast<char *>(malloc(MAXSTRPATH + 255))))
                {
                    for (i--; i >= 0; i--)
                        {
                            free(efiles[i]);
                        }
                    return;
                }
        }
    /* expand wild card in file path */
    n = expath(file, efiles, MAXEXFILE);

    for (i = j = 0; i < n; i++)
        {
            if (!(ext = strrchr(efiles[i], '.')))
                {
                    continue;
                }

            if (!strstr(ext + 1, "sp3") && !strstr(ext + 1, ".SP3") &&
                !strstr(ext + 1, "eph") && !strstr(ext + 1, ".EPH"))
                {
                    continue;
                }

            if (!(fp = fopen(efiles[i], "re")))
                {
                    trace(2, "sp3 file open error %s\n", efiles[i]);
                    continue;
                }
            /* read sp3 header */
            ns = readsp3h(fp, &time, &type, sats.data(), bfact, tsys);

            /* read sp3 body */
            readsp3b(fp, type, sats.data(), ns, bfact, tsys, j++, opt, nav);

            fclose(fp);
        }
    for (i = 0; i < MAXEXFILE; i++)
        {
            free(efiles[i]);
        }

    /* combine precise ephemeris */
    if (nav->ne > 0)
        {
            combpeph(nav, opt);
        }
}


/* read satellite antenna parameters -------------------------------------------
 * read satellite antenna parameters
 * args   : char   *file       I   antenna parameter file
 *          gtime_t time       I   time
 *          nav_t  *nav        IO  navigation data
 * return : status (1:ok,0:error)
 * notes  : only support antex format for the antenna parameter file
 *-----------------------------------------------------------------------------*/
int readsap(const char *file, gtime_t time, nav_t *nav)
{
    pcvs_t pcvs{};
    pcv_t pcv0{};
    pcv_t *pcv;
    int i;

    trace(3, "readsap : file=%s time=%s\n", file, time_str(time, 0));

    if (!readpcv(file, &pcvs))
        {
            return 0;
        }

    for (i = 0; i < MAXSAT; i++)
        {
            pcv = searchpcv(i + 1, "", time, &pcvs);
            nav->pcvs[i] = pcv ? *pcv : pcv0;
        }
    free(pcv);
    return 1;
}


/* read dcb parameters file --------------------------------------------------*/
int readdcbf(const char *file, nav_t *nav, const sta_t *sta)
{
    FILE *fp;
    double cbias;
    char buff[256];
    char str1[32];
    char str2[32];
    int i;
    int j;
    int sat;
    int type = 0; /* 0 = none, 1=P1-P2, 2=P1-C1, 3=P2-C2 */

    trace(3, "readdcbf: file=%s\n", file);

    if (!(fp = fopen(file, "r")))
        {
            trace(2, "dcb parameters file open error: %s\n", file);
            return 0;
        }

    while (fgets(buff, sizeof(buff), fp))
        {
            /* Detect section headers */
            if (strstr(buff, "DIFFERENTIAL (P1-P2) CODE BIASES"))
                {
                    type = 1;
                    continue;
                }
            else if (strstr(buff, "DIFFERENTIAL (P1-C1) CODE BIASES"))
                {
                    type = 2;
                    continue;
                }
            else if (strstr(buff, "DIFFERENTIAL (P2-C2) CODE BIASES"))
                {
                    type = 3;
                    continue;
                }

            /* Skip lines until we are in a known section */
            if (!type)
                {
                    continue;
                }

            /* Parse tokens */
            str1[0] = '\0';
            str2[0] = '\0';
            if (sscanf(buff, "%31s %31s", str1, str2) < 1)
                {
                    continue;
                }

            /* Parse bias value */
            cbias = str2num(buff, 26, 9);
            if (fabs(cbias) < 1e-12)
                {
                    continue; /* skip zero/near-zero biases */
                }

            if (sta && (!strcmp(str1, "G") || !strcmp(str1, "R")))
                {
                    /* Receiver DCB  */
                    for (i = 0; i < MAXRCV; i++)
                        {
                            if (!strcmp(sta[i].name, str2))
                                {
                                    j = !strcmp(str1, "G") ? 0 : 1;
                                    nav->rbias[i][j][type - 1] = cbias * 1e-9 * SPEED_OF_LIGHT_M_S;  // ns -> m
                                    break;
                                }
                        }
                }
            else if ((sat = satid2no(str1)) > 0)
                {
                    /* Satellite DCB */
                    nav->cbias[sat - 1][type - 1] = cbias * 1e-9 * SPEED_OF_LIGHT_M_S;  // ns -> m
                }

            /* reset type when a blank line is found (end of section) */
            if (buff[0] == '\n' || buff[0] == '\r')
                {
                    type = 0;
                }
        }
    fclose(fp);

    return 1;
}


/* read dcb parameters ---------------------------------------------------------
 * read differential code bias (dcb) parameters
 * args   : char   *file       I   dcb parameters file (wild-card * expanded)
 *          nav_t  *nav        IO  navigation data
 *          sta_t  *sta        I   station info data to import receiver dcb
 *                                 (NULL: no use)
 * return : status (1:ok,0:error)
 * notes  : currently only p1-c1 bias of code *.dcb file
 *-----------------------------------------------------------------------------*/
int readdcb(const char *file, nav_t *nav, const sta_t *sta)
{
    int i;
    int j;
    int n;
    char *efiles[MAXEXFILE] = {};

    trace(3, "readdcb : file=%s\n", file);

    for (i = 0; i < MAXSAT; i++)
        {
            for (j = 0; j < 3; j++)
                {
                    nav->cbias[i][j] = 0.0;
                }
        }
    for (i = 0; i < MAXEXFILE; i++)
        {
            if (!(efiles[i] = static_cast<char *>(malloc(MAXSTRPATH + 255))))
                {
                    for (i--; i >= 0; i--)
                        {
                            free(efiles[i]);
                        }
                    return 0;
                }
        }
    n = expath(file, efiles, MAXEXFILE);

    for (i = 0; i < n; i++)
        {
            readdcbf(efiles[i], nav, sta);
        }
    for (i = 0; i < MAXEXFILE; i++)
        {
            free(efiles[i]);
        }

    return 1;
}


/* polynomial interpolation by Neville's algorithm ---------------------------*/
double interppol(const double *x, double *y, int n)
{
    int i;
    int j;

    for (j = 1; j < n; j++)
        {
            for (i = 0; i < n - j; i++)
                {
                    y[i] = (x[i + j] * y[i] - x[i] * y[i + 1]) / (x[i + j] - x[i]);
                }
        }
    return y[0];
}


/* satellite position by precise ephemeris -----------------------------------*/
int pephpos(gtime_t time, int sat, const nav_t *nav, double *rs,
    double *dts, double *vare, double *varc)
{
    double t[NMAX + 1];
    double p[3][NMAX + 1];
    double c[2];
    double *pos;
    double std = 0.0;
    double s[3];
    double sinl;
    double cosl;
    int i;
    int j;
    int k;
    int index;

    trace(4, "pephpos : time=%s sat=%2d\n", time_str(time, 3), sat);

    rs[0] = rs[1] = rs[2] = dts[0] = 0.0;

    if (nav->ne < NMAX + 1 ||
        timediff(time, nav->peph[0].time) < -MAXDTE ||
        timediff(time, nav->peph[nav->ne - 1].time) > MAXDTE)
        {
            trace(3, "no prec ephem %s sat=%2d\n", time_str(time, 0), sat);
            return 0;
        }
    /* binary search */
    for (i = 0, j = nav->ne - 1; i < j;)
        {
            k = (i + j) / 2;
            if (timediff(nav->peph[k].time, time) < 0.0)
                {
                    i = k + 1;
                }
            else
                {
                    j = k;
                }
        }
    index = i <= 0 ? 0 : i - 1;

    /* polynomial interpolation for orbit */
    i = index - (NMAX + 1) / 2;
    if (i < 0)
        {
            i = 0;
        }
    else if (i + NMAX >= nav->ne)
        {
            i = nav->ne - NMAX - 1;
        }

    for (j = 0; j <= NMAX; j++)
        {
            t[j] = timediff(nav->peph[i + j].time, time);
            if (norm_rtk(nav->peph[i + j].pos[sat - 1], 3) <= 0.0)
                {
                    trace(3, "prec ephem outage %s sat=%2d\n", time_str(time, 0), sat);
                    return 0;
                }
        }
    for (j = 0; j <= NMAX; j++)
        {
            pos = nav->peph[i + j].pos[sat - 1];
#if 0
            p[0][j] = pos[0];
            p[1][j] = pos[1];
#else
            /* correciton for earh rotation ver.2.4.0 */
            sinl = sin(GNSS_OMEGA_EARTH_DOT * t[j]);
            cosl = cos(GNSS_OMEGA_EARTH_DOT * t[j]);
            p[0][j] = cosl * pos[0] - sinl * pos[1];
            p[1][j] = sinl * pos[0] + cosl * pos[1];
#endif
            p[2][j] = pos[2];
        }
    for (i = 0; i < 3; i++)
        {
            rs[i] = interppol(t, p[i], NMAX + 1);
        }
    if (vare)
        {
            for (i = 0; i < 3; i++)
                {
                    s[i] = nav->peph[index].std[sat - 1][i];
                }
            std = norm_rtk(s, 3);

            /* extrapolation error for orbit */
            if (t[0] > 0.0)
                {
                    std += EXTERR_EPH * std::pow(t[0], 2.0) / 2.0;
                }
            else if (t[NMAX] < 0.0)
                {
                    std += EXTERR_EPH * std::pow(t[NMAX], 2.0) / 2.0;
                }
            *vare = std::pow(std, 2.0);
        }
    /* linear interpolation for clock */
    t[0] = timediff(time, nav->peph[index].time);
    t[1] = timediff(time, nav->peph[index + 1].time);
    c[0] = nav->peph[index].pos[sat - 1][3];
    c[1] = nav->peph[index + 1].pos[sat - 1][3];

    if (t[0] <= 0.0)
        {
            if ((dts[0] = c[0]) != 0.0)
                {
                    std = nav->peph[index].std[sat - 1][3] * SPEED_OF_LIGHT_M_S - EXTERR_CLK * t[0];
                }
        }
    else if (t[1] >= 0.0)
        {
            if ((dts[0] = c[1]) != 0.0)
                {
                    std = nav->peph[index + 1].std[sat - 1][3] * SPEED_OF_LIGHT_M_S + EXTERR_CLK * t[1];
                }
        }
    else if (c[0] != 0.0 && c[1] != 0.0)
        {
            dts[0] = (c[1] * t[0] - c[0] * t[1]) / (t[0] - t[1]);
            i = t[0] < -t[1] ? 0 : 1;
            std = nav->peph[index + i].std[sat - 1][3] + EXTERR_CLK * fabs(t[i]);
        }
    else
        {
            dts[0] = 0.0;
        }
    if (varc)
        {
            *varc = std::pow(std, 2.0);
        }
    return 1;
}


/* satellite clock by precise clock ------------------------------------------*/
int pephclk(gtime_t time, int sat, const nav_t *nav, double *dts,
    double *varc)
{
    double t[2];
    double c[2];
    double std;
    int i;
    int j;
    int k;
    int index;

    trace(4, "pephclk : time=%s sat=%2d\n", time_str(time, 3), sat);

    if (nav->nc < 2 ||
        timediff(time, nav->pclk[0].time) < -MAXDTE ||
        timediff(time, nav->pclk[nav->nc - 1].time) > MAXDTE)
        {
            trace(3, "no prec clock %s sat=%2d\n", time_str(time, 0), sat);
            return 1;
        }
    /* binary search */
    for (i = 0, j = nav->nc - 1; i < j;)
        {
            k = (i + j) / 2;
            if (timediff(nav->pclk[k].time, time) < 0.0)
                {
                    i = k + 1;
                }
            else
                {
                    j = k;
                }
        }
    index = i <= 0 ? 0 : i - 1;

    /* linear interpolation for clock */
    t[0] = timediff(time, nav->pclk[index].time);
    t[1] = timediff(time, nav->pclk[index + 1].time);
    c[0] = nav->pclk[index].clk[sat - 1][0];
    c[1] = nav->pclk[index + 1].clk[sat - 1][0];

    if (t[0] <= 0.0)
        {
            if ((dts[0] = c[0]) == 0.0)
                {
                    return 0;
                }
            std = nav->pclk[index].std[sat - 1][0] * SPEED_OF_LIGHT_M_S - EXTERR_CLK * t[0];
        }
    else if (t[1] >= 0.0)
        {
            if ((dts[0] = c[1]) == 0.0)
                {
                    return 0;
                }
            std = nav->pclk[index + 1].std[sat - 1][0] * SPEED_OF_LIGHT_M_S + EXTERR_CLK * t[1];
        }
    else if (c[0] != 0.0 && c[1] != 0.0)
        {
            dts[0] = (c[1] * t[0] - c[0] * t[1]) / (t[0] - t[1]);
            i = t[0] < -t[1] ? 0 : 1;
            std = nav->pclk[index + i].std[sat - 1][0] * SPEED_OF_LIGHT_M_S + EXTERR_CLK * fabs(t[i]);
        }
    else
        {
            trace(3, "prec clock outage %s sat=%2d\n", time_str(time, 0), sat);
            return 0;
        }
    if (varc)
        {
            *varc = std::pow(std, 2.0);
        }
    return 1;
}


/* satellite antenna phase center offset ---------------------------------------
 * compute satellite antenna phase center offset in ecef
 * args   : gtime_t time       I   time (gpst)
 *          double *rs         I   satellite position and velocity (ecef)
 *                                 {x,y,z,vx,vy,vz} (m|m/s)
 *          int    sat         I   satellite number
 *          nav_t  *nav        I   navigation data
 *          double *dant       I   satellite antenna phase center offset (ecef)
 *                                 {dx,dy,dz} (m) (iono-free LC value)
 * return : none
 *-----------------------------------------------------------------------------*/
void satantoff(gtime_t time, const double *rs, int sat, const nav_t *nav,
    double *dant)
{
    const double *lam = nav->lam[sat - 1];
    const pcv_t *pcv = nav->pcvs + sat - 1;
    double ex[3];
    double ey[3];
    double ez[3];
    double es[3];
    double r[3];
    double rsun[3];
    double gmst;
    double erpv[5] = {};
    double gamma;
    double C1;
    double C2;
    double dant1;
    double dant2;
    int i;
    int j = 0;
    int k = 1;

    trace(4, "satantoff: time=%s sat=%2d\n", time_str(time, 3), sat);

    /* sun position in ecef */
    sunmoonpos(gpst2utc(time), erpv, rsun, nullptr, &gmst);

    /* unit vectors of satellite fixed coordinates */
    for (i = 0; i < 3; i++)
        {
            r[i] = -rs[i];
        }
    if (!normv3(r, ez))
        {
            return;
        }
    for (i = 0; i < 3; i++)
        {
            r[i] = rsun[i] - rs[i];
        }
    if (!normv3(r, es))
        {
            return;
        }
    cross3(ez, es, r);
    if (!normv3(r, ey))
        {
            return;
        }
    cross3(ey, ez, ex);

    if (NFREQ >= 3 && (satsys(sat, nullptr) & (SYS_GAL | SYS_SBS)))
        {
            k = 2;
        }

    if (NFREQ < 2 || lam[j] == 0.0 || lam[k] == 0.0)
        {
            return;
        }

    gamma = std::pow(lam[k], 2.0) / std::pow(lam[j], 2.0);
    C1 = gamma / (gamma - 1.0);
    C2 = -1.0 / (gamma - 1.0);

    /* iono-free LC */
    for (i = 0; i < 3; i++)
        {
            dant1 = pcv->off[j][0] * ex[i] + pcv->off[j][1] * ey[i] + pcv->off[j][2] * ez[i];
            dant2 = pcv->off[k][0] * ex[i] + pcv->off[k][1] * ey[i] + pcv->off[k][2] * ez[i];
            dant[i] = C1 * dant1 + C2 * dant2;
        }
}


/* satellite position/clock by precise ephemeris/clock -------------------------
 * compute satellite position/clock with precise ephemeris/clock
 * args   : gtime_t time       I   time (gpst)
 *          int    sat         I   satellite number
 *          nav_t  *nav        I   navigation data
 *          int    opt         I   sat position option
 *                                 (0: center of mass, 1: antenna phase center)
 *          double *rs         O   sat position and velocity (ecef)
 *                                 {x,y,z,vx,vy,vz} (m|m/s)
 *          double *dts        O   sat clock {bias,drift} (s|s/s)
 *          double *var        IO  sat position and clock error variance (m)
 *                                 (NULL: no output)
 * return : status (1:ok,0:error or data outage)
 * notes  : clock includes relativistic correction but does not contain code bias
 *          before calling the function, nav->peph, nav->ne, nav->pclk and
 *          nav->nc must be set by calling readsp3(), readrnx() or readrnxt()
 *          if precise clocks are not set, clocks in sp3 are used instead
 *-----------------------------------------------------------------------------*/
int peph2pos(gtime_t time, int sat, const nav_t *nav, int opt,
    double *rs, double *dts, double *var)
{
    double rss[3];
    double rst[3];
    double dtss[1];
    double dtst[1];
    double dant[3] = {};
    double vare = 0.0;
    double varc = 0.0;
    double tt = 1e-3;
    int i;

    trace(4, "peph2pos: time=%s sat=%2d opt=%d\n", time_str(time, 3), sat, opt);

    if (sat <= 0 || MAXSAT < sat)
        {
            return 0;
        }

    /* satellite position and clock bias */
    if (!pephpos(time, sat, nav, rss, dtss, &vare, &varc) ||
        !pephclk(time, sat, nav, dtss, &varc))
        {
            return 0;
        }

    time = timeadd(time, tt);
    if (!pephpos(time, sat, nav, rst, dtst, nullptr, nullptr) ||
        !pephclk(time, sat, nav, dtst, nullptr))
        {
            return 0;
        }

    /* satellite antenna offset correction */
    if (opt)
        {
            satantoff(time, rss, sat, nav, dant);
        }
    for (i = 0; i < 3; i++)
        {
            rs[i] = rss[i] + dant[i];
            rs[i + 3] = (rst[i] - rss[i]) / tt;
        }
    /* relativistic effect correction */
    if (dtss[0] != 0.0)
        {
            dts[0] = dtss[0] - 2.0 * dot(rs, rs + 3, 3) / SPEED_OF_LIGHT_M_S / SPEED_OF_LIGHT_M_S;
            dts[1] = (dtst[0] - dtss[0]) / tt;
        }
    else
        { /* no precise clock */
            dts[0] = dts[1] = 0.0;
        }
    if (var)
        {
            *var = vare + varc;
        }

    return 1;
}
