#include "mgos.h"
#include "test.h"
#include "timespec.h"

static void test_timespec_pass(void) {
  struct mgos_timespec *ts;
  static const char *   tests[] = {
    "08-20",
    "8-20",
    "08:01-20:59",
    "8:00-20",
    "08:00:00-20:00:00",
    "8:00:00-20:00:00",
    "0-0:01",
    "0-23:59:59",
    "08-20:30",
    "20-08",
    "20:01:02-08:01:02",
    "00:00-00:00",
    NULL
  };

  ts = timespec_create();
  ASSERT(ts, "Created timespec");
  for (int i = 0; tests[i]; i++) {
    ASSERT(timespec_add_spec(ts, tests[i]), tests[i]);
  }
  ASSERT(timespec_destroy(&ts), "Destroyed timespec");
}

static void test_timespec_fail(void) {
  struct mgos_timespec *ts;
  static const char *   tests[] = {
    "08",                 // No endtime given
    "08-",                // No endtime given
    "-20",                // No begintime given
    "08:00:00:00-20",     // Malformed begintime
    "08:00-20:00:00:00",  // Malformed endtime
    "24-01",              // Hours >23
    "08:60-20:00",        // Minutes >59
    "08:59:60-20:00",     // Seconds >59
    "08:59:59-24:00:00",  // Hours >23
    "08:59:59-23:60:00",  // Minutes >59
    "08:59:59-23:59:60",  // Seconds >59
    "abc",                // Invalid chars
    "",                   // Empty spec
    "-",                  // No times specified
    NULL
  };

  ts = timespec_create();
  ASSERT(ts, "Created timespec");
  for (int i = 0; tests[i]; i++) {
    ASSERT(!timespec_add_spec(ts, tests[i]), tests[i]);
  }
  ASSERT(timespec_destroy(&ts), "Destroyed timespec");
}

static void test_timespec_flips_and_seconds(const struct mgos_timespec *ts, const uint32_t expected_flips, const uint32_t expected_seconds_on) {
  struct tm target;
  bool      state             = false;
  int       state_flips       = 0;
  int       state_seconds_on  = 0;
  int       state_seconds_off = 0;

  for (int i = 0; i < 86400; i++) {
    target.tm_sec  = i % 60;
    target.tm_min  = (i / 60) % 60;
    target.tm_hour = (i / 3600) % 24;
    if (state != timespec_match(ts, &target)) {
      state = !state;
      state_flips++;
      LOG(LL_DEBUG, ("State flipped to %s at %02d:%02d:%02d", state ? "On" : "Off", target.tm_hour, target.tm_min, target.tm_sec));
    }
    if (state) {
      state_seconds_on++;
    } else{
      state_seconds_off++;
    }
  }
  LOG(LL_DEBUG, ("state_flips=%d state_seconds_on=%d", state_flips, state_seconds_on));

  ASSERT(state_flips == expected_flips, "Want to see expected_flips number of state flips");
  ASSERT(state_seconds_on == expected_seconds_on, "State seconds on should be expected_seconds_on");
  ASSERT(state_seconds_on + state_seconds_off == 86400, "State seconds on + off should be 86400 seconds");
}

static void test_timespec_state(void) {
  struct mgos_timespec *ts = timespec_create();
  struct tm             target;

  ASSERT(ts, "Created timespec");
  ASSERT(timespec_add_spec(ts, "08-10"), "Added timespec");
  ASSERT(timespec_add_spec(ts, "12:00-13:00"), "Added timespec");
  ASSERT(timespec_add_spec(ts, "14:11:05-14:59:52"), "Added timespec");
  ASSERT(timespec_add_spec(ts, "23:01:02-01:02:03"), "Added timespec");
  ASSERT(!timespec_add_spec(ts, "14:60-15:01"), "Illegal timespec");

  target.tm_sec = 59; target.tm_min = 59; target.tm_hour = 7;
  ASSERT(!timespec_match(ts, &target), "No match at 07:59:59");

  target.tm_sec = 0; target.tm_min = 0; target.tm_hour = 8;
  ASSERT(timespec_match(ts, &target), "Match at 08:00:00");

  target.tm_sec = 59; target.tm_min = 59; target.tm_hour = 9;
  ASSERT(timespec_match(ts, &target), "Match at 09:59:59");

  target.tm_sec = 0; target.tm_min = 0; target.tm_hour = 10;
  ASSERT(!timespec_match(ts, &target), "No Match at 10:00:00");

  target.tm_sec = 59; target.tm_min = 59; target.tm_hour = 11;
  ASSERT(!timespec_match(ts, &target), "No match at 11:59:59");

  target.tm_sec = 0; target.tm_min = 0; target.tm_hour = 12;
  ASSERT(timespec_match(ts, &target), "Match at 12:00:00");

  target.tm_sec = 59; target.tm_min = 59; target.tm_hour = 12;
  ASSERT(timespec_match(ts, &target), "Match at 12:59:59");

  target.tm_sec = 0; target.tm_min = 0; target.tm_hour = 13;
  ASSERT(!timespec_match(ts, &target), "No match at 13:00:00");

  target.tm_sec = 4; target.tm_min = 11; target.tm_hour = 14;
  ASSERT(!timespec_match(ts, &target), "No match at 14:11:04");

  target.tm_sec = 5; target.tm_min = 11; target.tm_hour = 14;
  ASSERT(timespec_match(ts, &target), "Match at 14:11:05");

  target.tm_sec = 51; target.tm_min = 59; target.tm_hour = 14;
  ASSERT(timespec_match(ts, &target), "Match at 14:59:51");

  target.tm_sec = 52; target.tm_min = 59; target.tm_hour = 14;
  ASSERT(!timespec_match(ts, &target), "No match at 14:59:52");

  target.tm_sec = 1; target.tm_min = 1; target.tm_hour = 23;
  ASSERT(!timespec_match(ts, &target), "No match at 23:01:01");

  target.tm_sec = 2; target.tm_min = 1; target.tm_hour = 23;
  ASSERT(timespec_match(ts, &target), "Match at 23:01:02");

  target.tm_sec = 59; target.tm_min = 59; target.tm_hour = 23;
  ASSERT(timespec_match(ts, &target), "Match at 23:59:59");

  target.tm_sec = 0; target.tm_min = 0; target.tm_hour = 0;
  ASSERT(timespec_match(ts, &target), "Match at 00:00:00");

  target.tm_sec = 2; target.tm_min = 2; target.tm_hour = 1;
  ASSERT(timespec_match(ts, &target), "Match at 01:02:02");

  target.tm_sec = 3; target.tm_min = 2; target.tm_hour = 1;
  ASSERT(!timespec_match(ts, &target), "No match at 01:02:03");

  ASSERT(timespec_destroy(&ts), "Destroyed timespec");

  // Some time counting tests
  ts = timespec_create();
  ASSERT(ts, "Created timespec");
  ASSERT(timespec_add_spec(ts, "08-10"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 2, 7200);

  // Zero seconds long
  ASSERT(timespec_add_spec(ts, "00:00:00-00:00:00"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 2, 7200 + 0);

  // Add two hours wrapping midnight
  ASSERT(timespec_add_spec(ts, "23:00:00-01:00:00"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 5, 7200 + 0 + 7200);

  // Add single second
  ASSERT(timespec_add_spec(ts, "02:00:00-02:00:01"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 7, 7200 + 0 + 7200 + 1);

  // Add an hour, a minute and a second
  ASSERT(timespec_add_spec(ts, "03:02:03-04:03:04"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 9, 7200 + 0 + 7200 + 1 + 3661);

  ASSERT(timespec_destroy(&ts), "Destroyed timespec");

  // Some other time counting tests
  ts = timespec_create();
  ASSERT(ts, "Created timespec");
  ASSERT(timespec_add_spec(ts, "00-12"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 2, 12 * 60 * 60);
  ASSERT(timespec_add_spec(ts, "12-00"), "Added timespec");
  test_timespec_flips_and_seconds(ts, 1, 24 * 60 * 60);

  ASSERT(timespec_destroy(&ts), "Destroyed timespec");
}

void test_timespec() {
  test_timespec_pass();
  test_timespec_fail();
  test_timespec_state();
}