/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <errno.h>
#include "os/mynewt.h"
#include "console/console.h"
#include "shell/shell.h"
#include "hal/hal_gpio.h"
#include "bno055_priv.h"
#include "bno055/bno055.h"
#include "sensor/sensor.h"
#include "sensor/accel.h"
#include "sensor/mag.h"
#include "sensor/quat.h"
#include "sensor/euler.h"
#include "hal/hal_i2c.h"
#include "parse/parse.h"

#if MYNEWT_VAL(BNO055_CLI)

static int bno055_shell_cmd(int argc, char **argv);

static struct shell_cmd bno055_shell_cmd_struct = {
    .sc_cmd = "bno055",
    .sc_cmd_func = bno055_shell_cmd
};

static struct sensor_itf *g_sensor_itf;
static struct bno055 *g_bno055;

static int
bno055_shell_open_device(void)
{
    if (g_sensor_itf) {
        return 0;
    }

    g_bno055 = (struct bno055 *)os_dev_open(MYNEWT_VAL(BNO055_SHELL_DEV_NAME),
                                            1000, NULL);
    if (g_bno055) {
        g_sensor_itf = &g_bno055->sensor.s_itf;
        return 0;
    }

    return SYS_ENODEV;
}

static int
bno055_shell_err_too_many_args(char *cmd_name)
{
    console_printf("Error: too many arguments for command \"%s\"\n",
                   cmd_name);
    return EINVAL;
}

static int
bno055_shell_err_unknown_arg(char *cmd_name)
{
    console_printf("Error: unknown argument \"%s\"\n",
                   cmd_name);
    return EINVAL;
}

static int
bno055_shell_err_invalid_arg(char *cmd_name)
{
    console_printf("Error: invalid argument \"%s\"\n",
                   cmd_name);
    return EINVAL;
}

static int
bno055_shell_help(void)
{
    console_printf("%s cmd  [flags...]\n", bno055_shell_cmd_struct.sc_cmd);
    console_printf("cmd:\n");
    console_printf("\tr     [n_samples] [ 0-acc          | 1 -mag       | 2 -gyro    | 4 -temp   |\n"
                   "\t                    9-quat         | 26-linearacc | 27-gravity | 28-euler  ]\n\n");
    console_printf("\tmode  [0-config   | 1-acc          | 2 -mag       | 3 -gyro    | 4 -accmag |\n"
                   "\t       5-accgyro  | 6-maggyro      | 7 -amg       | 8 -imuplus | 9 -compass|\n"
                   "\t       9-m4g      |11-NDOF_FMC_OFF | 12-NDOF  ]\n");
    console_printf("\tchip_id\n");
    console_printf("\trev\n");
    console_printf("\treset\n");
    console_printf("\tpmode [0-normal   | 1-lowpower     | 2-suspend]\n");
    console_printf("\tsensor_offsets\n");
    console_printf("\tdumpreg [addr]\n");

    return 0;
}

static int
bno055_shell_cmd_sensor_offsets(int argc, char **argv)
{
    int i;
    int rc;
    struct bno055_sensor_offsets bso;
    uint16_t val;
    uint16_t offsetdata[11] = {0};
    char *tok;
    char *tok_ptr;

    rc = 0;
    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the chip id */
    if (argc == 2) {
        rc = bno055_get_sensor_offsets(g_sensor_itf, &bso);
        if (rc) {
            console_printf("Read failed %d\n", rc);
            goto err;
        }
        console_printf("Offsets:\n");
        console_printf("      \tacc \t |    gyro\t |    mag \t \n"
                       "\tx  :0x%02X\t :  0x%02X\t :  0x%02X\t \n"
                       "\ty  :0x%02X\t :  0x%02X\t :  0x%02X\t \n"
                       "\tz  :0x%02X\t :  0x%02X\t :  0x%02X\t \n"
                       "\trad:0x%02X\t :        \t :  0x%02X\t \n",
                       bso.bso_acc_off_x, bso.bso_mag_off_x,
                       bso.bso_gyro_off_x, bso.bso_acc_off_y,
                       bso.bso_mag_off_y, bso.bso_gyro_off_y,
                       bso.bso_acc_off_z, bso.bso_mag_off_z,
                       bso.bso_gyro_off_z, bso.bso_acc_radius,
                       bso.bso_mag_radius);
    } else if (argc == 3) {
        tok = strtok_r(argv[2], ":", &tok_ptr);
        i = 0;
        do {
            val = parse_ll_bounds(tok, 0, UINT16_MAX, &rc);
            if (rc) {
                return bno055_shell_err_invalid_arg(argv[2]);
            }
            offsetdata[i] = val;
            tok = strtok_r(NULL, ":", &tok_ptr);
        } while(++i < 11 && tok);

        bso.bso_acc_off_x  = offsetdata[0];
        bso.bso_acc_off_y  = offsetdata[1];
        bso.bso_acc_off_z  = offsetdata[2];
        bso.bso_gyro_off_x = offsetdata[3];
        bso.bso_gyro_off_y = offsetdata[4];
        bso.bso_gyro_off_z = offsetdata[5];
        bso.bso_mag_off_x  = offsetdata[6];
        bso.bso_mag_off_y  = offsetdata[7];
        bso.bso_mag_off_z  = offsetdata[8];
        bso.bso_acc_radius = offsetdata[9];
        bso.bso_mag_radius = offsetdata[10];

        rc = bno055_set_sensor_offsets(g_sensor_itf, &bso);
        if (rc) {
            goto err;
        }
    }

    return 0;
err:
    return rc;
}

static int
bno055_shell_cmd_get_chip_id(int argc, char **argv)
{
    uint8_t id;
    int rc;

    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the chip id */
    if (argc == 2) {
        rc = bno055_get_chip_id(g_sensor_itf, &id);
        if (rc) {
            console_printf("Read failed %d", rc);
        }
        console_printf("0x%02X\n", id);
    }

    return 0;
}

static int
bno055_shell_cmd_get_rev_info(int argc, char **argv)
{
    int rc;
    struct bno055_rev_info ri;

    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the rev ids */
    if (argc == 2) {
        rc = bno055_get_rev_info(g_sensor_itf, &ri);
        if (rc) {
            console_printf("Read failed %d", rc);
        }
        console_printf("accel_rev:0x%02X\nmag_rev:0x%02X\ngyro_rev:0x%02X\n"
                       "sw_rev:0x%02X\nbl_rev:0x%02X\n", ri.bri_accel_rev,
                       ri.bri_mag_rev, ri.bri_gyro_rev, ri.bri_sw_rev,
                       ri.bri_bl_rev);
    }

    return 0;
}

static int
bno055_shell_cmd_read(int argc, char **argv)
{
    uint16_t samples = 1;
    uint16_t val;
    int rc;
    union {
        struct sensor_quat_data sqd;
        struct sensor_euler_data sed;
        struct sensor_accel_data sad;
    } data;
    uint8_t temp;
    int type;
    char tmpstr[13];

    type = SENSOR_TYPE_NONE;
    if (argc > 4) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Check if more than one sample requested */
    if (argc == 4) {
        val = parse_ll_bounds(argv[2], 0, UINT16_MAX, &rc);
        if (rc) {
            return bno055_shell_err_invalid_arg(argv[2]);
        }
        samples = (uint16_t)val;

        val = parse_ll_bounds(argv[3], 0, UINT16_MAX, &rc);
        if (rc) {
            return bno055_shell_err_invalid_arg(argv[3]);
        }
        type = (int)(1 << val);
    } else {
        console_printf("Usage:\n");
        console_printf("\tr     [n_samples] [ 0-acc          | 1 -mag       | 2 -gyro    | 4 -temp   |\n"
                       "\t                    9-quat         | 26-linearacc | 27-gravity | 28-euler  ]\n\n");
    }
    while(samples--) {

        if (type == SENSOR_TYPE_ROTATION_VECTOR) {
            rc = bno055_get_quat_data(g_sensor_itf, &data.sqd);
            if (rc) {
                console_printf("Read failed: %d\n", rc);
                goto err;
            }

            console_printf("x:%s ", sensor_ftostr(data.sqd.sqd_x, tmpstr, 13));
            console_printf("y:%s ", sensor_ftostr(data.sqd.sqd_y, tmpstr, 13));
            console_printf("z:%s ", sensor_ftostr(data.sqd.sqd_z, tmpstr, 13));
            console_printf("w:%s\n", sensor_ftostr(data.sqd.sqd_w, tmpstr, 13));

        } else if (type == SENSOR_TYPE_EULER) {
            rc = bno055_get_vector_data(g_sensor_itf, &data.sed, type);
            if (rc) {
                console_printf("Read failed: %d\n", rc);
                goto err;
            }

            console_printf("h:%s ", sensor_ftostr(data.sed.sed_h, tmpstr, 13));
            console_printf("r:%s ", sensor_ftostr(data.sed.sed_r, tmpstr, 13));
            console_printf("p:%s\n", sensor_ftostr(data.sed.sed_p, tmpstr, 13));

        } else if (type == SENSOR_TYPE_TEMPERATURE) {
            rc = bno055_get_temp(g_sensor_itf, &temp);
            if (rc) {
                console_printf("Read failed: %d\n", rc);
                goto err;
            }

            console_printf("Temperature:%u\n", temp);
        } else {
            rc = bno055_get_vector_data(g_sensor_itf, &data.sad, type);
            if (rc) {
                console_printf("Read failed: %d\n", rc);
                goto err;
            }

            console_printf("x:%s ", sensor_ftostr(data.sad.sad_x, tmpstr, 13));
            console_printf("y:%s ", sensor_ftostr(data.sad.sad_y, tmpstr, 13));
            console_printf("z:%s\n", sensor_ftostr(data.sad.sad_z, tmpstr, 13));
        }
    }

    rc = 0;
err:
    return rc;
}

static int
bno055_shell_cmd_opr_mode(int argc, char **argv)
{
    uint8_t val;
    int rc;

    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the mode */
    if (argc == 2) {
        rc = bno055_get_opr_mode(g_sensor_itf, (uint8_t *)&val);
        if (rc) {
            goto err;
        }
        console_printf("%u\n", ((unsigned int)(*(uint8_t *)&val)));
    }

    /* Update the mode */
    if (argc == 3) {
        val = parse_ll_bounds(argv[2], 0, BNO055_OPR_MODE_NDOF, &rc);
        if (rc) {
            return bno055_shell_err_invalid_arg(argv[2]);
        }
        /* Make sure mode is valid */
        if (val > BNO055_OPR_MODE_NDOF) {
            return bno055_shell_err_invalid_arg(argv[2]);
        }

        rc = bno055_set_opr_mode(g_sensor_itf, val);
        if (rc) {
            goto err;
        }
    }

    return 0;
err:
    return rc;
}

static int
bno055_shell_cmd_pwr_mode(int argc, char **argv)
{
    uint8_t val;
    int rc;

    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the mode */
    if (argc == 2) {
        rc = bno055_get_pwr_mode(g_sensor_itf, (uint8_t *)&val);
        if (rc) {
            goto err;
        }
        console_printf("%u\n", (unsigned int)val);
    }

    /* Update the mode */
    if (argc == 3) {
        val = parse_ll_bounds(argv[2], 0, BNO055_PWR_MODE_SUSPEND, &rc);
        if (rc) {
            return bno055_shell_err_invalid_arg(argv[2]);
        }

        rc = bno055_set_pwr_mode(g_sensor_itf, val);
        if (rc) {
            goto err;
        }
    }

    return 0;
err:
    return rc;
}

static int
bno055_shell_units_cmd(int argc, char **argv)
{
    uint8_t val;
    int rc;

    if (argc > 3) {
        return bno055_shell_err_too_many_args(argv[1]);
    }

    /* Display the units */
    if (argc == 2) {
        rc = bno055_get_units(g_sensor_itf, (uint8_t *)&val);
        console_printf("Acc, linear acc, gravity: %s\n"
                       "Mag field strength: Micro Tesla\n"
                       "Ang rate: %s\n"
                       "Euler ang: %s\n",
                       (uint8_t)val & BNO055_ACC_UNIT_MG ? "mg":"m/s^2",
                       (uint8_t)val & BNO055_ANGRATE_UNIT_RPS ? "Rps":"Dps",
                       (uint8_t)val & BNO055_EULER_UNIT_RAD ? "Rad":"Deg");
        console_printf("Quat: Quat units\n"
                       "Temp: %s\n"
                       "Fusion data output: %s\n",
                       (uint8_t)val & BNO055_TEMP_UNIT_DEGF ? "Deg F":"Deg C",
                       (uint8_t)val & BNO055_DO_FORMAT_ANDROID ? "Android":"Windows");
    }

    /* Update the units */
    if (argc == 3) {
        val = parse_ll_bounds(argv[2], 0, UINT8_MAX, &rc);
        if (rc) {
            return bno055_shell_err_invalid_arg(argv[2]);
        }

        rc = bno055_set_units(g_sensor_itf, val);
        if (rc) {
            goto err;
        }
    }

    return 0;
err:
    return rc;
}

static int
bno055_shell_cmd_dumpreg(int argc, char **argv)
{
    uint8_t addr;
    uint8_t val;
    int rc;

    addr = parse_ll_bounds(argv[2], 0, UINT8_MAX, &rc);
    if (rc) {
        return bno055_shell_err_invalid_arg(argv[2]);
    }

    rc = bno055_read8(g_sensor_itf, addr, &val);
    if (rc) {
        goto err;
    }
    console_printf("0x%02X (ADDR): 0x%02X", addr, val);

    return 0;
err:
    return rc;
}

static int
bno055_shell_cmd_reset(int argc, char **argv)
{
    int rc;
    /* Reset sensor */
    rc = bno055_write8(g_sensor_itf, BNO055_SYS_TRIGGER_ADDR, BNO055_SYS_TRIGGER_RST_SYS);
    if (rc) {
        goto err;
    }

    return 0;
err:
    return rc;
}

static int
bno055_shell_cmd(int argc, char **argv)
{
    if (argc == 1) {
        return bno055_shell_help();
    }

    if (bno055_shell_open_device()) {
        console_printf("Error: device not found \"%s\"\n",
                       MYNEWT_VAL(BNO055_SHELL_DEV_NAME));
    }
    /* Read command (get a new data sample) */
    if (argc > 1 && strcmp(argv[1], "r") == 0) {
        return bno055_shell_cmd_read(argc, argv);
    }

    /* Mode command */
    if (argc > 1 && strcmp(argv[1], "mode") == 0) {
        return bno055_shell_cmd_opr_mode(argc, argv);
    }

    /* Chip ID command */
    if (argc > 1 && strcmp(argv[1], "chip_id") == 0) {
        return bno055_shell_cmd_get_chip_id(argc, argv);
    }

    /* Rev command */
    if (argc > 1 && strcmp(argv[1], "rev") == 0) {
        return bno055_shell_cmd_get_rev_info(argc, argv);
    }
    /* Reset command */
    if (argc > 1 && strcmp(argv[1], "reset") == 0) {
        return bno055_shell_cmd_reset(argc, argv);
    }

    /* Power mode command */
    if (argc > 1 && strcmp(argv[1], "pmode") == 0) {
        return bno055_shell_cmd_pwr_mode(argc, argv);
    }

    /* Dump Registers command */
    if (argc > 1 && strcmp(argv[1], "dumpreg") == 0) {
        return bno055_shell_cmd_dumpreg(argc, argv);
    }

    if (argc > 1 && strcmp(argv[1], "units") == 0) {
        return bno055_shell_units_cmd(argc, argv);
    }

    if (argc > 1 && strcmp(argv[1], "sensor_offsets") == 0) {
        return bno055_shell_cmd_sensor_offsets(argc, argv);
    }

    return bno055_shell_err_unknown_arg(argv[1]);
}

int
bno055_shell_init(void)
{
    int rc;

    rc = shell_cmd_register(&bno055_shell_cmd_struct);
    SYSINIT_PANIC_ASSERT(rc == 0);

    return rc;
}

#endif
