/**
 * ======================================================================
 * NMEA parser (Version 0.0.1)
 * Reference documents:
 * ======================================================================
 * Copyright (c) 2010 Shinichiro Nakamura (CuBeatSystems)
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ======================================================================
 */

#include "nmea.h"

#define MAX_ARGS    (24)
#define MAX_CHAR    (16)

#define ABORT_GETC(N)   (N < 0)
#define ABORT_CBFUNC(N) (N < 0)

/**
 * @brief 与えられた16進数文字列を値にして返す。
 * @details 2桁の16進数文字列以外を与えた場合、不正な結果を返す。
 * @param s 2桁の16進数文字列。
 * @return 値。
 */
static int get_hexnum(const char *s)
{
    static const char *numidx = "0123456789ABCDEF";
    int a = -1, b = -1;
    char *p = (char *)numidx;
    int idx = 0;
    while (*p) {
        if (s[0] == *p) {
            a = idx;
        }
        if (s[1] == *p) {
            b = idx;
        }
        p++;
        idx++;
    }
    if ((a < 0) || (b < 0)) {
        return -1;
    }
    return (a * 16) + b;
}

/**
 * @brief NMEAセンテンスのパースを実行する。
 * @details この関数はnmea_getcかnmea_cbfuncが負値を返すまで処理を返さない。
 *
 * @param nmea_getc NMEA受信関数。
 *                  この関数が負値を返すと、その値をこの関数が返す。
 * @param nmea_cbfunc １センテンス受信した時に呼ばれるコールバック関数。
 *                    この関数が負値を返すと、その値をこの関数が返す。
 *
 * @return nmea_getcあるいは、nmea_cbfuncが返した値。
 */
int nmea_parse(
        int (*nmea_getc)(void),
        int (*nmea_cbfunc)(int argc, char **argv, void *extobj),
        void *extobj)
{
    char args[MAX_ARGS][MAX_CHAR];
    int pcnt = 0;
    int ccnt = 0;
    unsigned char csum = 0;
    int c = 0;
    do {
        pcnt = 0;
        ccnt = 0;
        csum = 0;

        /*
         * Waiting a header.
         */
        do {
            c = nmea_getc();
            if (ABORT_GETC(c)) {
                return c;
            }
            args[pcnt][ccnt] = c;
        } while (c != '$');
        ccnt++;

        /*
         * Waiting the end of the body.
         */
        do {
            c = nmea_getc();
            if (ABORT_GETC(c)) {
                return c;
            }
            if (c != '*') {
                csum ^= c;
            }
            if ((c == ',') || (c == '*')) {
                args[pcnt][ccnt] = '\0';
                pcnt++;
                ccnt = 0;
            } else {
                args[pcnt][ccnt] = c;
                ccnt++;
            }
        } while (c != '*');

        /*
         * Waiting the check sum.
         */
        do {
            c = nmea_getc();
            if (ABORT_GETC(c)) {
                return c;
            }
            if ((c == '\r') || (c == '\n')) {
                args[pcnt][ccnt] = '\0';
                pcnt++;
                ccnt = 0;
            } else {
                args[pcnt][ccnt] = c;
                ccnt++;
            }
        } while ((c != '\r') && (c != '\n'));

        /*
         * Callback the callback.
         */
        {
            int i;
            char *p[MAX_ARGS];
            for (i = 0; i < MAX_ARGS; i++) {
                p[i] = args[i];
            }
            /*
             * 最後のフィールドはチェックサム。
             * チェックサム検証が通ればコールバックを呼ぶ。
             */
            if (get_hexnum(args[pcnt - 1]) == csum) {
                int r = nmea_cbfunc(pcnt, p, extobj);
                if (ABORT_CBFUNC(r)) {
                    return r;
                }
            }
        }
    } while (1);

    return 0;
}

