/*
 * 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 "os/mynewt.h"

#if MYNEWT_VAL(DRV2605_CLI)
#include <string.h>
#include <errno.h>
#include "console/console.h"
#include "shell/shell.h"
#include "drv2605_priv.h"
#include "drv2605/drv2605.h"
#include "sensor/sensor.h"
#include "parse/parse.h"

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

static struct shell_cmd drv2605_shell_cmd_struct = {
    .sc_cmd = "drv2605",
    .sc_cmd_func = drv2605_shell_cmd
};

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

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

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

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

static int
drv2605_shell_help(void)
{
    console_printf("%s cmd  [flags...]\n", drv2605_shell_cmd_struct.sc_cmd);
    console_printf("cmd:\n");
    console_printf("\tchip_id\n");
    console_printf("\tload_cal [brake_factor loop_gain lra_sample_time lra_blanking_time lra_idiss_time auto_cal_time lra_zc_det_time]\n");
    console_printf("\tload_rom [up to 8 uint8_t]\n");
    console_printf("\top_mode [reset | rom | pwm | analog | rtp | diag | cal]\n");
    console_printf("\tpower_mode [deep | standby | active]\n");
    console_printf("\ttrigger\n");
    console_printf("\tpeek [reg]\n");
    console_printf("\tpoke [reg value]\n");
    console_printf("\tdump_cal\n");
    console_printf("\tdump_all\n");
    return 0;
}

static int
drv2605_shell_cmd_load_rom(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    uint8_t waveform[8] = {0};
    uint8_t len = argc-2;
    uint8_t i;
    struct sensor_itf *itf;

    if (argc > 10) {
        return drv2605_shell_err_too_many_args(argv[1]);
    } else if (argc < 3) {
        return drv2605_shell_err_too_few_args(argv[1]);
    }

    for (i = 0; i<len;i++) {
        waveform[i] = parse_ll_bounds(argv[i+2], 0, 255, &rc);
        if (rc != 0) {
            goto err;
        }
    }

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_load_rom(itf, waveform, len);
    if (rc) {
        console_printf("load failed %d\n", rc);
    }else{
        console_printf("load succeeded\n");
    }

    return 0;
err:
    return rc;
}

static int
drv2605_shell_cmd_trigger_rom(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    struct sensor_itf *itf;

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

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_trigger_rom(itf);
    if (rc) {
        console_printf("trigger failed %d\n", rc);
    }else{
        console_printf("trigger succeeded\n");
    }

    return rc;
}

static int
drv2605_shell_cmd_get_chip_id(int argc, char **argv, struct drv2605 *drv2605)
{
    uint8_t id;
    int rc;
    struct sensor_itf *itf;

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

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

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

    return 0;
}

static int
drv2605_shell_cmd_dump_cal(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    uint8_t tmp[3];
    struct sensor_itf *itf;

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

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_readlen(itf, DRV2605_AUTO_CALIBRATION_COMPENSATION_RESULT_ADDR, &tmp[0], sizeof(tmp));
    if (rc) {
        console_printf("dump failed %d\n", rc);
        goto err;
    }
    console_printf("\nDRV2605_CALIBRATED_COMP: 0x%02X\nDRV2605_CALIBRATED_BEMF: 0x%02X\nDRV2605_CALIBRATED_BEMF_GAIN: %0d\n", tmp[0], tmp[1], tmp[2] & 0x03);

    return 0;
err:
    return rc;
}

static int
drv2605_shell_cmd_peek(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    uint8_t value;
    uint8_t reg;
    struct sensor_itf *itf;

    if (argc > 3) {
        return drv2605_shell_err_too_many_args(argv[1]);
    } else if (argc < 3) {
        return drv2605_shell_err_too_few_args(argv[1]);
    }

    reg = parse_ll_bounds(argv[2], 0, 34, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_read8(itf, reg, &value);
    if (rc) {
        console_printf("peek failed %d\n", rc);
    }else{
        console_printf("value: 0x%02X\n", value);
    }

    return 0;
}

static int
drv2605_shell_cmd_poke(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    uint8_t reg;
    uint8_t value;
    struct sensor_itf *itf;

    if (argc > 4) {
        return drv2605_shell_err_too_many_args(argv[1]);
    } else if (argc < 4) {
        return drv2605_shell_err_too_few_args(argv[1]);
    }

    reg = parse_ll_bounds(argv[2], 0, 34, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    value = parse_ll_bounds(argv[3], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[3]);
    }

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_write8(itf, reg, value);
    if (rc) {
        console_printf("poke failed %d\n", rc);
    }else{
        console_printf("wrote: 0x%02X to 0x%02X\n", value, reg);
    }

    return 0;
}

static int
drv2605_shell_cmd_dump_all(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    uint8_t value;
    int i;
    struct sensor_itf *itf;

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

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    for (i=0; i<=34; i++){
        rc = drv2605_read8(itf, i, &value);
        if (rc) {
            console_printf("dump failed %d\n", rc);
            goto err;
        }else{
            console_printf("reg 0x:%02X = 0x%02X\n", i, value);
        }
    }

    return 0;
err:
    return rc;
}

static int
drv2605_shell_load_cal(int argc, char **argv, struct drv2605_cal *cal)
{
    int rc;

    cal->brake_factor = parse_ll_bounds(argv[2], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->loop_gain = parse_ll_bounds(argv[3], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->lra_sample_time = parse_ll_bounds(argv[4], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->lra_blanking_time = parse_ll_bounds(argv[5], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->lra_idiss_time = parse_ll_bounds(argv[6], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->auto_cal_time = parse_ll_bounds(argv[7], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    cal->lra_zc_det_time = parse_ll_bounds(argv[8], 0, 255, &rc);
    if (rc != 0) {
        return drv2605_shell_err_invalid_arg(argv[2]);
    }

    return 0;
}

static int
drv2605_shell_cmd_load_cal(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;

    if(argc == 2) {
        return drv2605_default_cal(&drv2605->cfg.cal);
    }else if (argc > 9) {
        return drv2605_shell_err_too_many_args(argv[1]);
    } else if (argc < 9) {
        return drv2605_shell_err_too_few_args(argv[1]);
    }

    rc = drv2605_shell_load_cal(argc, argv, &drv2605->cfg.cal);
    if (rc) {
        console_printf("load_cal failed %d\n", rc);
        goto err;
    }else{
        console_printf("load_cal succeeded\n");
    }

    return 0;
err:
    return rc;
}

static int
drv2605_shell_cmd_power_mode(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;
    enum drv2605_power_mode mode;
    struct sensor_itf *itf;

    if(strcmp(argv[2],"off") == 0) {
        mode = DRV2605_POWER_OFF;
    }else if(strcmp(argv[2],"standby") == 0) {
        mode = DRV2605_POWER_STANDBY;
    }else if(strcmp(argv[2],"active") == 0) {
        mode = DRV2605_POWER_ACTIVE;
    }else {
        return drv2605_shell_err_unknown_arg(argv[2]);
    }

    itf = SENSOR_GET_ITF(&(drv2605->sensor));

    rc = drv2605_set_power_mode(itf, mode);
    if (rc) {
        console_printf("power_mode failed %d\n", rc);
        goto err;
    }else{
        console_printf("power_mode succeeded\n");
    }

    return 0;
err:
    return rc;
}

static int
drv2605_shell_cmd_op_mode(int argc, char **argv, struct drv2605 *drv2605)
{
    int rc;

    if(strcmp(argv[2],"rom") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_ROM;
    }else if(strcmp(argv[2],"reset") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_RESET;
    }else if(strcmp(argv[2],"pwm") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_PWM;
    }else if(strcmp(argv[2],"analog") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_ANALOG;
    }else if(strcmp(argv[2],"rtp") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_RTP;
    }else if(strcmp(argv[2],"diag") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_DIAGNOSTIC;
    }else if(strcmp(argv[2],"cal") == 0) {
        drv2605->cfg.op_mode = DRV2605_OP_CALIBRATION;
    }else {
        return drv2605_shell_err_unknown_arg(argv[2]);
    }

    rc = drv2605_config(drv2605, &drv2605->cfg);
    if (rc) {
        console_printf("op_mode failed %d\n", rc);
        goto err;
    }else{
        console_printf("op_mode succeeded\n");
    }

    return 0;
err:
    return rc;
}

static int
drv2605_shell_cmd(int argc, char **argv)
{
    struct os_dev * dev;
    struct drv2605 * drv2605;

    dev = os_dev_open("drv2605_0", OS_TIMEOUT_NEVER, NULL);
    if (dev == NULL) {
        console_printf("failed to open drv2605_0 device\n");
        return ENODEV;
    }

    drv2605 = (struct drv2605 *)dev;

    if (argc == 1) {
        return drv2605_shell_help();
    }

    if (argc > 1 && strcmp(argv[1], "load_cal") == 0) {
        return drv2605_shell_cmd_load_cal(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "dump_cal") == 0) {
        return drv2605_shell_cmd_dump_cal(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "dump_all") == 0) {
        return drv2605_shell_cmd_dump_all(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "chip_id") == 0) {
        return drv2605_shell_cmd_get_chip_id(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "op_mode") == 0) {
        return drv2605_shell_cmd_op_mode(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "power_mode") == 0) {
        return drv2605_shell_cmd_power_mode(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "load_rom") == 0) {
        return drv2605_shell_cmd_load_rom(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "trigger") == 0) {
        return drv2605_shell_cmd_trigger_rom(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "peek") == 0) {
        return drv2605_shell_cmd_peek(argc, argv, drv2605);
    }

    if (argc > 1 && strcmp(argv[1], "poke") == 0) {
        return drv2605_shell_cmd_poke(argc, argv, drv2605);
    }

    return drv2605_shell_err_unknown_arg(argv[1]);
}

int
drv2605_shell_init(void)
{
    int rc;

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

    return rc;
}

#endif
