/***********************************************************************
 *
 * Copyrigth: Karl Hammar, Aspö Data, LGPL
 *
 */

#include "davis_vantage.h"

#include <aspoutil.h>

#include <ctype.h>
#include <unistd.h>

/******************************
 * I Introdution, p. 4
 */

uint16_t davis_value_u16(uint8_t *buf) {
  return buf[1] << 8 | buf[0];
}

/******************************
 * VI. Command formats, p. 6

  Example:

  "\n\rOK\n\r1.82\n\r" -> "1.82"
  len 12               -> len 4 + '\0
 */

int davis_ok_cleanup(uint8_t *buf, size_t len) {
  uint8_t *end;

  LOG_IF(!buf, LOG_CRIT) return -1;
  LOG_IF(len < DAVIS_OK_DATA, LOG_ERR) return -1;
  LOG_IF(!DAVIS_OK_RESPONSE(buf), LOG_ERR) return -1;

  end = buf + len - 1;
  while( isspace(*end) ) end--;
  LOG_VIF(end - buf < DAVIS_OK_DATA, LOG_ERR, "%s", "no data except whitespace") return -1;

  len = end - buf + 1;
  len -= DAVIS_OK_DATA;
  memmove(buf, buf+DAVIS_OK_DATA, len);
  buf[len] = '\0'; /* since we shrunk buf, there will be space for this */

  return len;
}

/******************************
 * VIII. Command Details, p8-20

  WRD response data, p. 9
 */

static const struct intstr Davis_station_type[] = {
  { 0, "Wizard III" },
  { 1, "Wizard II" },
  { 2, "Monitor" },
  { 3, "Perception" },
  { 4, "GroWeather" },
  { 5, "Energy Enviomonitor" },
  { 6, "Health Eviromonitor" },
  { 16, "Vantage Pro" },
};

const struct intstr * const davis_station_type = Davis_station_type;
const size_t davis_station_type_sz = INTSTR_SZ(Davis_station_type);

/******************************
 * IX. Data Formats
 */
const char *davis_loop_headers[] = {
  "barometer",
  "inside_temperature",
  "outside_temperature",
  "wind_direction",
  "rain_rate",
  "solar_radiation",
  "storm_rain",
  /*
  "start_date_storm",
  "day_rain",
  "month_rain",
  "year_rain",
  "day_et",
  "month_et",
  "year_et",
  "outside_alarm",
  */
  "console_battery_voltage",
  /*
  "time_sunrise",
  "time_sunset",
  */

  "inside_humidity",
  "wind_speed",
  "extra_temperature0",
  "extra_temperature1",
  "extra_temperature2",
  "extra_temperature3",
  "extra_temperature4",
  "extra_temperature5",
  "extra_temperature6",
  "soil_temperature0",
  "soil_temperature1",
  "soil_temperature2",
  "soil_temperature3",
  "leaf_temperature0",
  "leaf_temperature1",
  "leaf_temperature2",
  "leaf_temperature3",
  "outside_humidity",
  "extra_humidity0",
  "extra_humidity1",
  "extra_humidity2",
  "extra_humidity3",
  "extra_humidity4",
  "extra_humidity5",
  "extra_humidity6",
  "uv",
  "soil_moisture0",
  "soil_moisture1",
  "soil_moisture2",
  "soil_moisture3",
  "leaf_wetness0",
  "leaf_wetness1",
  "leaf_wetness2",
  "leaf_wetness3",
  /*
  "inside_alarm",
  "rain_alarm",
  "extra_temp_hum_alarm0",
  "extra_temp_hum_alarm1",
  "extra_temp_hum_alarm2",
  "extra_temp_hum_alarm3",
  "extra_temp_hum_alarm4",
  "extra_temp_hum_alarm5",
  "extra_temp_hum_alarm6",
  "extra_temp_hum_alarm7",
  "soil_leaf_alarm0",
  "soil_leaf_alarm1",
  "soil_leaf_alarm2",
  "soil_leaf_alarm3",
  */
  "transmitter_battery_status",
  /*
  "forecast_icon",
  "forecast_rule_number",
  */
  NULL /* marks the en of list */
};

const char *davis_loop_hdr[] = {
  "baromet.",
  "ins_temp",
  "out_temp",
  "wind_dir",
  "rain_rate",
  "sol_rad",
  "st_rain",
  /*
  "st_d_st",
  "day_rain",
  "mon_rain",
  "yea_rain",
  "day_et",
  "mon_et",
  "yea_et",
  "out_alrm",
  */
  "cons_batt",
  /*
  "sunrise",
  "sunset",
  */

  "ins_hum",
  "wind_sp",
  "x_temp0",
  "x_temp1",
  "x_temp2",
  "x_temp3",
  "x_temp4",
  "x_temp5",
  "x_temp6",
  "soil_t0",
  "soil_t1",
  "soil_t2",
  "soil_t3",
  "leaf_t0",
  "leaf_t1",
  "leaf_t2",
  "leaf_t3",
  "out_hum",
  "x_hum0",
  "x_hum1",
  "x_hum2",
  "x_hum3",
  "x_hum4",
  "x_hum5",
  "x_hum6",
  "uv",
  "soil_m0",
  "soil_m1",
  "soil_m2",
  "soil_m3",
  "leaf_w0",
  "leaf_w1",
  "leaf_w2",
  "leaf_w3",
  /*
  "ins_alrm",
  "rain_alrm",
  "x_alrm0",
  "x_alrm1",
  "x_alrm2",
  "x_alrm3",
  "x_alrm4",
  "x_alrm5",
  "x_alrm6",
  "s_l_alrm0",
  "s_l_alrm1",
  "s_l_alrm2",
  "s_l_alrm3",
  */
  "tr_batt",
  /*
  "icon",
  "rule",
  */
  NULL /* marks the en of list */
};

int davis_loop_cnv(uint8_t *buf, struct davis_loop_decoded *pkt) {
  int ix;
  LOG_IF( !buf || !pkt, LOG_CRIT) return -1;

  pkt->barometer                   = davis_value_u16(buf+DAVIS_BAROMETER);
  pkt->inside_temperature          = davis_value_u16(buf+DAVIS_INSIDE_TEMPERATURE);
  pkt->outside_temperature         = davis_value_u16(buf+DAVIS_OUTSIDE_TEMPERATURE);
  pkt->wind_direction              = davis_value_u16(buf+DAVIS_WIND_DIRECTION);
  pkt->rain_rate                   = davis_value_u16(buf+DAVIS_RAIN_RATE);
  pkt->solar_radiation             = davis_value_u16(buf+DAVIS_SOLAR_RADIATION);
  pkt->storm_rain                  = davis_value_u16(buf+DAVIS_STORM_RAIN);
  /*
  pkt->start_date_storm            = davis_value_u16(buf+DAVIS_START_DATE_STORM);
  pkt->day_rain                    = davis_value_u16(buf+DAVIS_DAY_RAIN);
  pkt->month_rain                  = davis_value_u16(buf+DAVIS_MONTH_RAIN);
  pkt->year_rain                   = davis_value_u16(buf+DAVIS_YEAR_RAIN);
  pkt->day_et                      = davis_value_u16(buf+DAVIS_DAY_ET);
  pkt->month_et                    = davis_value_u16(buf+DAVIS_MONTH_ET);
  pkt->year_et                     = davis_value_u16(buf+DAVIS_YEAR_ET);
  pkt->outside_alarm               = davis_value_u16(buf+DAVIS_OUTSIDE_ALARM);
  */
  pkt->console_battery_voltage     = davis_value_u16(buf+DAVIS_CONSOLE_BATTERY_VOLTAGE);
  /*
  pkt->time_sunrise                = davis_value_u16(buf+DAVIS_TIME_SUNRISE);
  pkt->time_sunset                 = davis_value_u16(buf+DAVIS_TIME_SUNSET);
  pkt->time_sunset                 = davis_value_u16(buf+DAVIS_TIME_SUNSET);
  */

  pkt->inside_humidity               = buf[DAVIS_INSIDE_HUMIDITY];
  pkt->wind_speed                    = buf[DAVIS_WIND_SPEED];
  for (ix = 0; ix < 7; ++ix) {
    pkt->extra_temperature[ix]       = buf[DAVIS_EXTRA_TEMPERATURE+ix];
  }
  for (ix = 0; ix < 4; ++ix) {
    pkt->soil_temperature[ix]        = buf[DAVIS_SOIL_TEMPERATURE+ix];
  }
  for (ix = 0; ix < 4; ++ix) {
    pkt->leaf_temperature[ix]        = buf[DAVIS_LEAF_TEMPERATURE+ix];
  }
  pkt->outside_humidity              = buf[DAVIS_OUTSIDE_HUMIDITY];
  for (ix = 0; ix < 7; ++ix) {
    pkt->extra_humidity[ix]          = buf[DAVIS_EXTRA_HUMIDITY+ix];
  }
  pkt->uv                            = buf[DAVIS_UV];
  for (ix = 0; ix < 4; ++ix) {
    pkt->soil_moisture[ix]           = buf[DAVIS_SOIL_MOISTURE+ix];
  }
  for (ix = 0; ix < 4; ++ix) {
    pkt->leaf_wetness[ix]            = buf[DAVIS_LEAF_WETNESS+ix];
  }
  /*
  pkt->inside_alarm                  = buf[DAVIS_INSIDE_ALARM];
  pkt->rain_alarm                    = buf[DAVIS_RAIN_ALARM];
  for (ix = 0; ix < 8; ++ix) {
    pkt->extra_temp_hum_alarm[ix]    = buf[DAVIS_EXTRA_TEMP_HUM_ALARM+ix];
  }
  for (ix = 0; ix < 4; ++ix) {
    pkt->soil_leaf_alarm[ix]         = buf[DAVIS_SOIL_LEAF_ALARM+ix];
  }
  */
  pkt->transmitter_battery_status    = buf[DAVIS_TRANSMITTER_BATTERY_STATUS];
  /*
  pkt->forecast_icon                 = buf[DAVIS_FORECAST_ICON];
  pkt->forecast_rule_number          = buf[DAVIS_FORECAST_RULE_NUMBER];
  */

  return 0;
}

int davis_loop_pr_hdr(void) {
  const char **pp = davis_loop_hdr;
  char buf[500];
  char *bp = buf;
  size_t len = 500;
  int cnt;

  cnt = snprintf(bp, len, "%14s", "time");
  bp += cnt;
  len -= cnt;

  while (*pp) {
    cnt = snprintf(bp, len, " %9s", *pp);
    bp += cnt;
    len -= cnt;
    ++pp;
  }
  cnt = snprintf(bp, len, "\n");
  log_puts(buf);

  return 0;
}
int davis_loop_pr_cnv(struct davis_loop_decoded *pkt) {
  char buf[500];
  char *bp = buf;
  size_t len = 500;
  int cnt;
  int ix;

  cnt = snprintf(bp, len, "%11ld.%02ld", timespec_current.tv_sec, timespec_current.tv_nsec/(1000*1000*10));
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->barometer);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->inside_temperature);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->outside_temperature);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->wind_direction);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->rain_rate);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->solar_radiation);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->storm_rain);
  bp += cnt;
  len -= cnt;
  /*
    cnt = snprintf(bp, len, "%10d", pkt->start_date_storm);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->day_rain);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->month_rain);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->year_rain);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->day_et);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->month_et);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->year_et);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->outside_alarm);
    bp += cnt;
    len -= cnt;
  */
  cnt = snprintf(bp, len, "%10d", pkt->console_battery_voltage);
  bp += cnt;
  len -= cnt;
  /*
    cnt = snprintf(bp, len, "%10d", pkt->time_sunrise);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->time_sunset);
    bp += cnt;
    len -= cnt;
  */

  cnt = snprintf(bp, len, "%10d", pkt->inside_humidity);
  bp += cnt;
  len -= cnt;
  cnt = snprintf(bp, len, "%10d", pkt->wind_speed);
  bp += cnt;
  len -= cnt;
  for (ix = 0; ix < 7; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->extra_temperature[ix]);
    bp += cnt;
    len -= cnt;
  }
  for (ix = 0; ix < 4; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->soil_temperature[ix]);
    bp += cnt;
    len -= cnt;
  }
  for (ix = 0; ix < 4; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->leaf_temperature[ix]);
    bp += cnt;
    len -= cnt;
  }
  cnt = snprintf(bp, len, "%10d", pkt->outside_humidity);
  bp += cnt;
  len -= cnt;
  for (ix = 0; ix < 7; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->extra_humidity[ix]);
    bp += cnt;
    len -= cnt;
  }
  cnt = snprintf(bp, len, "%10d", pkt->uv);
  bp += cnt;
  len -= cnt;
  for (ix = 0; ix < 4; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->soil_moisture[ix]);
    bp += cnt;
    len -= cnt;
  }
  for (ix = 0; ix < 4; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->leaf_wetness[ix]);
    bp += cnt;
    len -= cnt;
  }
  /*
    cnt = snprintf(bp, len, "%10d", pkt->inside_alarm);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->rain_alarm);
    bp += cnt;
    len -= cnt;
    for (ix = 0; ix < 8; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->extra_temp_hum_alarm[ix]);
    bp += cnt;
    len -= cnt;
    }
    for (ix = 0; ix < 4; ++ix) {
    cnt = snprintf(bp, len, "%10d", pkt->soil_leaf_alarm[ix]);
    bp += cnt;
    len -= cnt;
    }
  */
  cnt = snprintf(bp, len, "%10d", pkt->transmitter_battery_status);
  bp += cnt;
  len -= cnt;
  /*
    cnt = snprintf(bp, len, "%10d", pkt->forecast_icon);
    bp += cnt;
    len -= cnt;
    cnt = snprintf(bp, len, "%10d", pkt->forecast_rule_number);
    bp += cnt;
    len -= cnt;
  */

  cnt = snprintf(bp, len, "\n");
  log_puts(buf);

  return 0;
}

/******************************
 * XI. CRC calculation, p. 32-34
 */

const uint16_t Davis_crc[] = {
  0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
  0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
  0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
  0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
  0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
  0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
  0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
  0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
  0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
  0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
  0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
  0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
  0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
  0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
  0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
  0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
  0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
  0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
  0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
  0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
  0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
  0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
  0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
  0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
  0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
  0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
  0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
  0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
  0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
  0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
  0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
};

const uint16_t * const davis_crc_table = Davis_crc;
const int davis_crc_table_sz = sizeof(Davis_crc)/sizeof(uint16_t);

uint16_t davis_crc(uint8_t data, uint16_t crc) {
  return Davis_crc[(crc >> 8) ^ data] ^ (crc << 8);
}

int davis_crc_buf(uint8_t *buf, size_t len) {
  uint16_t crc = 0;
  size_t ix;

  for (ix = 0; ix < len; ++ix) {
    crc = davis_crc(buf[ix], crc);
  }
  return (int) crc;
}
