/****************************************************************************
 * apps/testing/drivertest/drivertest_block.c
 *
 * 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/mount.h>
#include <sys/ioctl.h>

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <fcntl.h>
#include <cmocka.h>
#include <nuttx/crc32.h>

/****************************************************************************
 * Private Type
 ****************************************************************************/

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define BLKTEST_MAXLEN  255
#define BLKTEST_LOOPS   100

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct pre_build
{
  FAR const char *mountpt;
};

struct test_state_s
{
  FAR char *context[BLKTEST_MAXLEN];
  size_t len[BLKTEST_LOOPS];
  uint32_t crc[BLKTEST_LOOPS];
  int fd;
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: show_usage
 ****************************************************************************/

static void show_usage(FAR const char *progname, int exitcode)
{
  printf("Usage: %s -m <mountpt>\n", progname);
  printf("Where:\n");
  printf("  -m <mountpt> Block device or mtd device"
         " mount location[default location: dev/ram10].\n");
  exit(exitcode);
}

/****************************************************************************
 * Name: parse_commandline
 ****************************************************************************/

static void parse_commandline(int argc, FAR char **argv,
                              FAR struct pre_build *pre_build)
{
  int option;

  while ((option = getopt(argc, argv, "m:")) != ERROR)
    {
      switch (option)
        {
          case 'm':
            pre_build->mountpt = optarg;
            break;
          case '?':
            printf("Unknown option: %c\n", optopt);
            show_usage(argv[0], EXIT_FAILURE);
            break;
        }
    }
}

/****************************************************************************
 * Name: blktest_randchar
 ****************************************************************************/

static inline char blktest_randchar(void)
{
  int value = rand() % 63;
  if (value == 0)
    {
      return '0';
    }
  else if (value <= 10)
    {
      return value + '0' - 1;
    }
  else if (value <= 36)
    {
      return value + 'a' - 11;
    }
  else
    {
      return value + 'A' - 37;
    }
}

/****************************************************************************
 * Name: blktest_randcontext
 ****************************************************************************/

static void blktest_randcontext(FAR struct test_state_s *test_state)
{
  int i;
  int j;
  int rnd;

  for (i = 0; i < BLKTEST_LOOPS; i++)
    {
      rnd = (rand() % BLKTEST_MAXLEN) + 1;
      test_state->context[i] = malloc(rnd + 1);
      assert_true(test_state->context[i] != NULL);
      for (j = 0; j < rnd; j++)
        {
          test_state->context[i][j] = blktest_randchar();
        }

      test_state->context[i][rnd] = '\0';
      size_t len = strlen(test_state->context[i]);
      test_state->len[i] = len;
      test_state->crc[i] = crc32((FAR uint8_t *)test_state->context[i], len);
    }
}

/****************************************************************************
 * Name: setup
 ****************************************************************************/

static int setup(FAR void **state)
{
  FAR struct test_state_s *test_state;
  FAR struct pre_build    *pre_build;
  time_t t;

  pre_build = (FAR struct pre_build *)*state;

  /* Allocate memory space and initialize */

  test_state = zalloc(sizeof(struct test_state_s));
  assert_false(test_state == NULL);

  /* Seed the random number generated */

  srand((unsigned)time(&t));

  /* Open */

  test_state->fd = open(pre_build->mountpt, O_RDWR | O_DIRECT);
  assert_false(test_state->fd < 0);
  *state = test_state;

  return 0;
}

/****************************************************************************
 * Name: blktest_stress
 ****************************************************************************/

static void blktest_stress(FAR void **state)
{
  FAR struct test_state_s *test_state;
  int i;
  int ret;
  char *output;
  uint32_t output_crc;

  test_state = (FAR struct test_state_s *)*state;

  /* Create some random context */

  blktest_randcontext(test_state);

  /* Writes all text to the block device bypassing the buffer cache,
   * ensuring that reads are read from the block device.
   * Get the value of crc32 after reading and compare it with the crc32
   * of the previously written text.
   */

  for (i = 0; i < BLKTEST_LOOPS; i++)
    {
      ret = write(test_state->fd, test_state->context[i],
                  test_state->len[i]);
      assert_true(ret == test_state->len[i]);
      fsync(test_state->fd);
    }

  /* Reset read and write position */

  lseek(test_state->fd, 0, SEEK_SET);

  output = malloc (BLKTEST_MAXLEN);
  assert_true(output != NULL);

  for (i = 0; i < BLKTEST_LOOPS; i++)
    {
      memset(output, 0, BLKTEST_MAXLEN);
      ret = read(test_state->fd, output, test_state->len[i]);
      assert_int_equal(ret, test_state->len[i]);

      output_crc = crc32((FAR uint8_t *)output, test_state->len[i]);
      assert_false(output_crc != test_state->crc[i]);
    }

  free(output);
}

/****************************************************************************
 * Name: teardown
 ****************************************************************************/

static int teardown(FAR void **state)
{
  FAR struct test_state_s *test_state;

  test_state = (FAR struct test_state_s *)*state;

  close(test_state->fd);
  free(test_state);

  return 0;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: blktest_main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  FAR struct pre_build pre_build = {
    .mountpt = "dev/ram10"
  };

  parse_commandline(argc, argv, &pre_build);
  const struct CMUnitTest tests[] =
    {
      cmocka_unit_test_prestate_setup_teardown(blktest_stress, setup,
                                               teardown, &pre_build),
    };

  return cmocka_run_group_tests(tests, NULL, NULL);
}