#include <gtest/gtest.h>

#include <pthread.h>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <sys/xattr.h>
#include <unistd.h>

using namespace std;

static const string kTestDirPath = "/data/pa_tests/concurrent_call/";
static const string kTestFilePath = kTestDirPath + "stest_concurrent_call";
static const string kTestFilePrefix = kTestDirPath + "concurrent_call_test_file";
static const string kPaXattrName = "user.pa";
static const string pa_signer = "pa_hmac_signer";
static const string kTestApplication = "canary";
static const string kStestClient = kTestDirPath + "stest_client";

enum RunningEntity {
  kClient,
  kPaSigner
};

static pthread_mutex_t mtx;

struct tc {
  string command;
  RunningEntity entity;
  int status;
};

static void RunCommand(tc *thread_info, bool is_debug=false) {
  if (is_debug) {
    cout << "Run command: " << thread_info->command << endl;
  }

  string& run_cmd = thread_info->command;
  if (!is_debug) {
    run_cmd += " &> /dev/null";
  }

  int ret = system(run_cmd.c_str());
  if (!ret) {
    thread_info->status = 0;
  }
}


/**
 * @brief Synchronization of clients execution
 * @param [in, out] thread_info pointer on data structure
 *                  with thread initial parameters
 * @note  Clients should be synchronized. That's multibuild requirment.
 *        TRUSTLET_INSTANCES is set to 1 in tbase-trustlet-params.mk file
 */
static void SynchronizeClients(tc* thread_info) {
  if (thread_info->entity == kClient) {
      pthread_mutex_lock(&mtx);
      RunCommand(thread_info);
      pthread_mutex_unlock(&mtx);
    } else {
      RunCommand(thread_info);
  }
}

static void* thread_subroutine(void* param) {
  tc *thread_info = (tc*)param;
  SynchronizeClients(thread_info);
  return NULL;
}

TEST(ConcurrentCall, RunPaSignerWithCanary) {
  pthread_t thr_1, thr_2;

  string pa_signer_cmd = pa_signer + " " + kTestFilePath;

  tc cmd1 = {kTestApplication, kClient, 1};
  pthread_create(&thr_1, NULL, &thread_subroutine, &cmd1);

  tc cmd2 = {pa_signer_cmd, kPaSigner, 1};
  removexattr(kTestFilePath.c_str(), kPaXattrName.c_str());
  pthread_create(&thr_2, NULL, &thread_subroutine, &cmd2);

  pthread_join(thr_1, NULL);
  pthread_join(thr_2, NULL);

  EXPECT_EQ(0, cmd1.status);
  EXPECT_EQ(0, cmd2.status);
}

TEST(ConcurrentCall, RunMultiplePaSignersWithCanaryForDiferentFiles) {
  const int num_files = 7;
  const int num_threads = num_files * 2;
  pthread_t thread[num_threads];
  tc cmd_signer[num_files];
  tc cmd_client[num_files];
  string pa_signer_command_string[num_files];
  string client_commmand_string[num_files];

  char idx[2];

  for (int thread_count = 0, i = 0; i < num_files; ++i) {
    sprintf(idx, "%d", i+1);
    string test_file_name = kTestFilePrefix + "_" + idx;
    removexattr(test_file_name.c_str(), kPaXattrName.c_str());

    pa_signer_command_string[i] = pa_signer + " " + test_file_name;
    client_commmand_string[i] = kTestApplication;

    cmd_signer[i] = {pa_signer_command_string[i], kPaSigner, 1};
    cmd_client[i] = {client_commmand_string[i], kClient, 1};

    pthread_create(&thread[thread_count++], NULL, &thread_subroutine, &cmd_signer[i]);
    pthread_create(&thread[thread_count++], NULL, &thread_subroutine, &cmd_client[i]);
  }

  for (int i = 0; i < num_threads; ++i) {
    pthread_join(thread[i], NULL);
  }

  for (int i = 0; i < num_files; ++i) {
    EXPECT_EQ(0, cmd_signer[i].status);
    EXPECT_EQ(0, cmd_client[i].status);
  }
}

TEST(ConcurrentCall, ThreeSwdClientsCall200Authentication) {
  string command = kStestClient + " 1";
  int res = system(command.c_str());
  EXPECT_EQ(0, res);
}

TEST(ConcurrentCall, SwdClientCalls1000AuthenticationWithFiveHandlers) {
  string command = kStestClient + " 2";
  int res = system(command.c_str());
  EXPECT_EQ(0, res);
}
