/****************************************************************************
 * apps/testing/ostest/timedmutex.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 <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "ostest.h"

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

/****************************************************************************
 * Private Data
 ****************************************************************************/

static pthread_mutex_t g_mutex;
static bool g_running;
static int g_result;

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

static void *thread_func(FAR void *parameter)
{
  struct timespec ts;
  int status;

  printf("pthread:  Started\n");
  g_running = true;

  for (; ; )
    {
      printf("pthread:  Waiting for lock or timeout\n");

      /* Get the current time */

      status = clock_gettime(CLOCK_REALTIME, &ts);
      if (status < 0)
        {
          int errcode = errno;
          fprintf(stderr, "pthread: "
                  "ERROR clock_gettime() failed: %d\n", errcode);
          ASSERT(false);
          g_result = errcode;
          break;
        }

      /* Get a time two seconds in the future (we presume that we cannot be
       * blocked for two seconds here!)
       */

      ts.tv_sec += 2;

      /* Now wait until either we get the lock or until the timeout occurs */

      status = pthread_mutex_timedlock(&g_mutex, &ts);
      if (status != 0)
        {
          if (status == ETIMEDOUT)
            {
              printf("pthread:  Got the timeout.  Terminating\n");
            }
          else
            {
              fprintf(stderr, "pthread: "
                      "ERROR pthread_mutex_timedlock() failed: %d\n",
                      status);
              ASSERT(false);
            }

          g_result = status;
          break;
        }

      printf("pthread:  Got the lock\n");

      /* Release the lock and wait a bit in case the main thread wants it. */

      pthread_mutex_unlock(&g_mutex);
      usleep(500 * 1000);
    }

  g_running = false;
  return NULL;
}

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

void timedmutex_test(void)
{
  pthread_t thread;
#ifdef SDCC
  pthread_addr_t result;
#endif
  int status;

  /* Initialize the mutex */

  printf("mutex_test: Initializing mutex\n");
  pthread_mutex_init(&g_mutex, NULL);

  /* Lock the mutex */

  status = pthread_mutex_lock(&g_mutex);
  if (status != OK)
    {
      fprintf(stderr, "mutex_test: ERROR Failed to get mutex: %d\n", status);
      ASSERT(false);
      goto errout_with_mutex;
    }

  /* Start a thread */

  printf("mutex_test: Starting thread\n");
#ifdef SDCC
  pthread_attr_init(&attr);
  status = pthread_create(&thread, &attr, thread_func, (pthread_addr_t)0);
#else
  status = pthread_create(&thread, NULL, thread_func, (pthread_addr_t)0);
#endif
  if (status != 0)
    {
      fprintf(stderr, "mutex_test: ERROR in thread creation: %d\n", status);
      ASSERT(false);
      goto errout_with_lock;
    }

  /* Wait a bit to assure that the thread gets a chance to start */

  usleep(500 * 1000);

  /* Then unlock the mutex.  This should wake up the pthread. */

  printf("mutex_test: Unlocking\n");
  pthread_mutex_unlock(&g_mutex);

  /* Wait a bit to assure that the thread gets a chance to lock the mutex at
   * least once (it may probably loop and retake the mutex several times)
   */

  usleep(500 * 1000);

  /* The re-lock the mutex.  The pthread should now be waiting for the lock
   * or a timeout.
   */

  status = pthread_mutex_lock(&g_mutex);
  if (status != OK)
    {
      fprintf(stderr, "mutex_test: ERROR Failed to get mutex: %d\n", status);
      ASSERT(false);
      goto errout_with_mutex;
    }

  /* The pthread should timeout in two seconds.  Let's wait four.  At that
   * time, the pthread should no longer be running.
   */

  sleep(4);

  /* The pthread should no longer be running and it should have terminated
   * because of EAGAIN.
   */

  if (g_running)
    {
      fprintf(stderr, "mutex_test: ERROR: The pthread is still running!\n");
      ASSERT(false);
    }
  else if (g_result != ETIMEDOUT)
    {
      fprintf(stderr, "mutex_test: ERROR: Result was not ETIMEDOUT: %d\n",
              g_result);
      ASSERT(false);
    }
  else
    {
      printf("mutex_test: PASSED\n");
    }

  /* Let's reap any join droppings left from the pthread */

#ifdef SDCC
  pthread_join(thread, &result);
#else
  pthread_join(thread, NULL);
#endif

errout_with_lock:
  pthread_mutex_unlock(&g_mutex);
errout_with_mutex:
  pthread_mutex_destroy(&g_mutex);
}