/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nspr.h"
#include "prinrval.h"
#include "plgetopt.h"
#include "pprthred.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

PRMonitor* mon;
PRInt32 count, iterations, alive;

PRBool debug_mode = PR_FALSE, passed = PR_TRUE;

void PR_CALLBACK ReallyDumbThread(void* arg) { return; }

void PR_CALLBACK DumbThread(void* arg) {
  PRInt32 tmp = (PRInt32)arg;
  PRThreadScope scope = (PRThreadScope)tmp;
  PRThread* thr;

  thr = PR_CreateThread(PR_USER_THREAD, ReallyDumbThread, NULL,
                        PR_PRIORITY_NORMAL, scope, PR_JOINABLE_THREAD, 0);

  if (!thr) {
    if (debug_mode) {
      printf("Could not create really dumb thread (%d, %d)!\n", PR_GetError(),
             PR_GetOSError());
    }
    passed = PR_FALSE;
  } else {
    PR_JoinThread(thr);
  }
  PR_EnterMonitor(mon);
  alive--;
  PR_Notify(mon);
  PR_ExitMonitor(mon);
}

static void CreateThreads(PRThreadScope scope1, PRThreadScope scope2) {
  PRThread* thr;
  int n;

  alive = 0;
  mon = PR_NewMonitor();

  alive = count;
  for (n = 0; n < count; n++) {
    thr = PR_CreateThread(PR_USER_THREAD, DumbThread, (void*)scope2,
                          PR_PRIORITY_NORMAL, scope1, PR_UNJOINABLE_THREAD, 0);
    if (!thr) {
      if (debug_mode) {
        printf("Could not create dumb thread (%d, %d)!\n", PR_GetError(),
               PR_GetOSError());
      }
      passed = PR_FALSE;
      alive--;
    }

    PR_Sleep(0);
  }

  /* Wait for all threads to exit */
  PR_EnterMonitor(mon);
  while (alive) {
    PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
  }

  PR_ExitMonitor(mon);
  PR_DestroyMonitor(mon);
}

static void CreateThreadsUU(void) {
  CreateThreads(PR_LOCAL_THREAD, PR_LOCAL_THREAD);
}

static void CreateThreadsUK(void) {
  CreateThreads(PR_LOCAL_THREAD, PR_GLOBAL_THREAD);
}

static void CreateThreadsKU(void) {
  CreateThreads(PR_GLOBAL_THREAD, PR_LOCAL_THREAD);
}

static void CreateThreadsKK(void) {
  CreateThreads(PR_GLOBAL_THREAD, PR_GLOBAL_THREAD);
}

/************************************************************************/

static void Measure(void (*func)(void), const char* msg) {
  PRIntervalTime start, stop;
  double d;

  start = PR_IntervalNow();
  (*func)();
  stop = PR_IntervalNow();

  if (debug_mode) {
    d = (double)PR_IntervalToMicroseconds(stop - start);
    printf("%40s: %6.2f usec\n", msg, d / count);
  }
}

int main(int argc, char** argv) {
  int index;

  PR_Init(PR_USER_THREAD, PR_PRIORITY_HIGH, 0);

  {
    PLOptStatus os;
    PLOptState* opt = PL_CreateOptState(argc, argv, "dc:i:");
    while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
      if (PL_OPT_BAD == os) {
        continue;
      }
      switch (opt->option) {
        case 'd': /* debug mode */
          debug_mode = PR_TRUE;
          break;
        case 'c': /* loop counter */
          count = atoi(opt->value);
          break;
        case 'i': /* loop counter */
          iterations = atoi(opt->value);
          break;
        default:
          break;
      }
    }
    PL_DestroyOptState(opt);
  }

  if (0 == count) {
    count = 50;
  }
  if (0 == iterations) {
    iterations = 10;
  }

  if (debug_mode) {
    printf(
        "\
** Tests lots of thread creations.  \n\
** Create %ld native threads %ld times. \n\
** Create %ld user threads %ld times \n",
        iterations, count, iterations, count);
  }

  for (index = 0; index < iterations; index++) {
    Measure(CreateThreadsUU, "Create user/user threads");
    Measure(CreateThreadsUK, "Create user/native threads");
    Measure(CreateThreadsKU, "Create native/user threads");
    Measure(CreateThreadsKK, "Create native/native threads");
  }

  if (debug_mode) {
    printf("\nNow switch to recycling threads \n\n");
  }
  PR_SetThreadRecycleMode(1);

  for (index = 0; index < iterations; index++) {
    Measure(CreateThreadsUU, "Create user/user threads");
    Measure(CreateThreadsUK, "Create user/native threads");
    Measure(CreateThreadsKU, "Create native/user threads");
    Measure(CreateThreadsKK, "Create native/native threads");
  }

  printf("%s\n", ((passed) ? "PASS" : "FAIL"));

  PR_Cleanup();

  if (passed) {
    return 0;
  } else {
    return 1;
  }
}
