/**
 * @file task_gps.c
 * @author Shinichiro Nakamura
 * @brief タスククロックの実装。
 */

/*
 * ===============================================================
 *  LPCXpresso Clock
 * ===============================================================
 * Copyright (c) 2010-2011 Shinichiro Nakamura
 *
 * 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 <kernel.h>
#include <t_syslog.h>
#include <t_stdlib.h>
#include <itron.h>
#include <target_syssvc.h>
#include <syssvc/serial.h>
#include "kernel_cfg.h"
#include "task_gps.h"
#include "task_display.h"
#include "nmea.h"
#include "ntlibc.h"
#include "gt_720f.h"

#define GT720F_CUSTOM_SETUP (0)
#define GPS_CMD_ACTMODE     (1)

#define HOST_PORTID (SIO1_PORTID)   /**< ホスト用シリアルポート。 */
#define GPS_PORTID  (SIO4_PORTID)   /**< GPS用シリアルポート。 */

#define IS_ACTMODE_THROUGH(N)   ((N) & (1 << 0))

typedef struct {
    uint8_t actmode;
} gps_param_actmode_t;

typedef struct {
    uint8_t actmode;
} gps_work_t;

static gps_work_t work;

/**
 * @brief データフォーマット構造体。
 * @details データ構造とコールバック関数の関係を表現している。
 */
typedef struct {
    char *header;   /**< ヘッダ名。 */
    int (*cbfunc)(int argc, char **argv, void *extobj);
} datfmt_t;

#define CBRETVAL    (-1)    /**< Callback Return Value. */

static int cb_gga(int argc, char **argv, void *extobj)
{
    int hh = ((argv[1][0] - '0') * 10) + ((argv[1][1] - '0') * 1);
    int mm = ((argv[1][2] - '0') * 10) + ((argv[1][3] - '0') * 1);
    int ss = ((argv[1][4] - '0') * 10) + ((argv[1][5] - '0') * 1);
    hh += 9;
    if (24 <= hh) {
        hh -= 24;
    }
    TSKAPI_DISPLAY_UPDATE(
            '0' + (hh / 10),
            '0' + (hh % 10),
            '0' + (mm / 10),
            '0' + (mm % 10),
            '0' + (ss / 10),
            '0' + (ss % 10),
            0);
    return CBRETVAL;
}

static int cb_gll(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static int cb_gsa(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static int cb_gsv(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static int cb_rmc(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static int cb_vtg(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static int cb_zda(int argc, char **argv, void *extobj)
{
    return CBRETVAL;
}

static const datfmt_t fmtlist[] = {
    { "$GPGGA", cb_gga },
    { "$GPGLL", cb_gll },
    { "$GPGSA", cb_gsa },
    { "$GPGSV", cb_gsv },
    { "$GPRMC", cb_rmc },
    { "$GPVTG", cb_vtg },
    { "$GPZDA", cb_zda },
    {     NULL, NULL   },
};

static int nmea_getc(void)
{
    char_t c;
    serial_rea_dat(GPS_PORTID, &c, 1);
    return c;
}

static int nmea_cbfunc(int argc, char **argv, void *extobj)
{
    /*
     * スルー処理。
     */
    if (IS_ACTMODE_THROUGH(work.actmode)) {
        int i;
        for (i = 0; i < argc; i++) {
            if ((0 < i) && (i < argc - 1)) {
                serial_wri_dat(HOST_PORTID, (char_t *)",", 1);
            } else {
                if (i == argc - 1) {
                    serial_wri_dat(HOST_PORTID, (char_t *)"*", 1);
                }
            }
            serial_wri_dat(HOST_PORTID, (char_t *)argv[i], ntlibc_strlen(argv[i]));
        }
        serial_wri_dat(HOST_PORTID, (char_t *)"\n", 1);
    }

    /*
     * 通常処理。
     */
    {
        datfmt_t *p = (datfmt_t *)&fmtlist[0];
        while (p->header != NULL) {
            if (ntlibc_strcmp(argv[0], p->header) == 0) {
                if (p->cbfunc != NULL) {
                    return p->cbfunc(argc, argv, NULL);
                }
            }
            p++;
        }
        syslog(LOG_NOTICE, "Unknown header: %s(%d)", argv[0], argc);
    }
    return CBRETVAL;
}

/**
 * @brief GPS用リード関数。
 *
 * @param buf バッファ。
 * @param len 読み込み長さ。
 *
 * @retval 0 成功。
 * @retval !0 失敗。
 */
static int gps_read(unsigned char *buf, int len)
{
    return serial_rea_dat(GPS_PORTID, (char_t *)buf, len);
}

/**
 * @brief GPS用ライト関数。
 *
 * @param buf バッファ。
 * @param len 読み込み長さ。
 *
 * @retval 0 成功。
 * @retval !0 失敗。
 */
static int gps_write(unsigned char *buf, int len)
{
    return serial_wri_dat(GPS_PORTID, (const char_t *)buf, len);
}

void cmd_actmode(gps_param_actmode_t *p)
{
    work.actmode = p->actmode;
}

/**
 * @brief クロックタスクの本体。
 * @param exinf タスクパラメータ。
 */
void task_gps(intptr_t exinf)
{
    /*
     * 開始処理。
     */
    syslog(LOG_NOTICE, "GPS task initialized.");
    serial_opn_por(GPS_PORTID);
    serial_ctl_por(GPS_PORTID, 0);

    work.actmode = 0;

#if (GT720F_CUSTOM_SETUP != 0)
    unsigned char gps_gga = 1;
    unsigned char gps_gsa = 0;
    unsigned char gps_gsv = 0;
    unsigned char gps_gll = 0;
    unsigned char gps_rmc = 0;
    unsigned char gps_vtg = 0;
    unsigned char gps_zda = 0;

    gt_720f_t gt_720f;
    GT_720F_SETUP(&gt_720f, gps_write, gps_read);

    gt_720f_configure_message_type(&gt_720f, MessageTypeNoOutput);
    syslog(LOG_NOTICE, "GT-720F:Message type changed to 'No Output'.");
    tslp_tsk(1000);

    gt_720f_configure_nmea_message_interval(
            &gt_720f,
            gps_gga,
            gps_gsa,
            gps_gsv,
            gps_gll,
            gps_rmc,
            gps_vtg,
            gps_zda);
    syslog(LOG_NOTICE, "GT-720F:NMEA message type changed.");

    gt_720f_configure_message_type(&gt_720f, MessageTypeNMEA);
    syslog(LOG_NOTICE, "GT-720F:Message type changed to 'NMEA'.");
#endif

    /*
     * タスクループ。
     */
    gps_msg_t *p;
    while(1) {
        /*
         * 1センテンスをパースする。
         * nmea_getcかnmea_cbfuncが負値を返すと
         * 処理を返す性質を利用して実装されている。
         */
        nmea_parse(nmea_getc, nmea_cbfunc, NULL);

        /*
         * メールボックスの状態を観察し、
         * メッセージがあれば処理を実行する。
         */
        if (prcv_mbx(MBX_GPS, (T_MSG **)&p) == E_OK) {
            uint8_t cmd = ((gps_msg_t *)p)->cmd;
            void *param = ((gps_msg_t *)p)->param;
            switch (cmd) {
                case GPS_CMD_ACTMODE:
                    cmd_actmode((gps_param_actmode_t *)param);
                    break;
                default:
                    syslog(LOG_NOTICE, "Unknown command 0x%x.", cmd);
                    break;
            }
            rel_mpf(MPF_GPS, (VP)p);
        }
    }

    /*
     * 終了処理。
     */
    serial_cls_por(GPS_PORTID);
    syslog(LOG_NOTICE, "GPS task terminated.");
    ext_ker();
    assert(0);
}

void tskapi_gps_actmode(uint8_t actmode)
{
    VP vp;

    /*
     * メモリプールからバッファを取得する。
     */
    get_mpf(MPF_GPS, &vp);

    /*
     * コマンドとパラメータを設定する。
     */
    ((gps_msg_t *)vp)->cmd = GPS_CMD_ACTMODE;
    gps_param_actmode_t *param =
        (gps_param_actmode_t *)&(((gps_msg_t *)vp)->param);
    param->actmode = actmode;

    /*
     * メールボックスに送信する。
     */
    snd_mbx(MBX_GPS, vp);
}

