/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef __OB_COMMON_TEST_BASE_H__
#define __OB_COMMON_TEST_BASE_H__

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

inline uint64_t rand64(uint64_t h)
{
  if (0 == h)
    return 1;
  h ^= h >> 33;
  h *= 0xff51afd7ed558ccd;
  h ^= h >> 33;
  h *= 0xc4ceb9fe1a85ec53;
  h ^= h >> 33;
  // h &= ~(1ULL<<63);
  return h;
}

int64_t get_us()
{
  struct timeval time_val;
  gettimeofday(&time_val, NULL);
  return time_val.tv_sec * 1000000 + time_val.tv_usec;
}

#define profile(expr, n)                                                                     \
  {                                                                                          \
    int64_t start = get_usec();                                                              \
    expr;                                                                                    \
    int64_t end = get_usec();                                                                \
    printf("%s: 1000000*%ld/%ld=%ld\n", #expr, n, end - start, 1000000 * n / (end - start)); \
  }

struct Callable {
  Callable() : stop_(false)
  {}
  virtual ~Callable()
  {}
  virtual int call(pthread_t thread, int64_t idx) = 0;
  volatile bool stop_;
};

typedef void* (*pthread_handler_t)(void*);
class BaseWorker {
public:
  static const int64_t MAX_N_THREAD = 16;
  struct WorkContext {
    WorkContext() : callable_(NULL), idx_(0)
    {}
    ~WorkContext()
    {}
    WorkContext& set(Callable* callable, int64_t idx)
    {
      callable_ = callable;
      idx_ = idx;
      return *this;
    }
    Callable* callable_;
    pthread_t thread_;
    int64_t idx_;
  };

public:
  BaseWorker() : n_thread_(0), thread_running_(false)
  {}
  ~BaseWorker()
  {
    wait();
  }

public:
  BaseWorker& set_thread_num(int64_t n)
  {
    n_thread_ = n;
    return *this;
  }
  int start(Callable* callable, int64_t idx = -1)
  {
    int err = 0;
    for (int64_t i = 0; i < n_thread_; i++) {
      if (idx > 0 && idx != i) {
        continue;
      }
      fprintf(stderr, "worker[%ld] start.\n", i);
      pthread_create(&ctx_[i].thread_, NULL, (pthread_handler_t)do_work, (void*)(&ctx_[i].set(callable, i)));
    }
    thread_running_ = true;
    return err;
  }

  int wait(int64_t idx = -1)
  {
    int err = 0;
    int64_t ret = 0;
    for (int64_t i = 0; thread_running_ && i < n_thread_; i++) {
      if (idx > 0 && idx != i) {
        continue;
      }
      pthread_join(ctx_[i].thread_, (void**)&ret);
      if (ret != 0) {
        fprintf(stderr, "thread[%ld] => %ld\n", i, ret);
      } else {
        fprintf(stderr, "thread[%ld] => OK.\n", i);
      }
    }
    thread_running_ = false;
    return err;
  }

  static int do_work(WorkContext* ctx)
  {
    int err = 0;
    if (NULL == ctx || NULL == ctx->callable_) {
      err = -EINVAL;
    } else {
      err = ctx->callable_->call(ctx->thread_, ctx->idx_);
    }
    return err;
  }
  int par_do(Callable* callable, int64_t duration)
  {
    int err = 0;
    if (0 != (err = start(callable))) {
      fprintf(stderr, "start()=>%d\n", err);
    } else {
      usleep(static_cast<__useconds_t>(duration));
      callable->stop_ = true;
    }
    if (0 != (err = wait())) {
      fprintf(stderr, "wait()=>%d\n", err);
    }
    return err;
  }

protected:
  int64_t n_thread_;
  bool thread_running_;
  WorkContext ctx_[MAX_N_THREAD];
};

int PARDO(int64_t thread_num, Callable* call, int64_t duration)
{
  BaseWorker worker;
  fprintf(stderr, "thread_num=%ld\n", thread_num);
  return worker.set_thread_num(thread_num).par_do(call, duration);
}

#if 0
struct SimpleCallable: public Callable
{
  int64_t n_items_;
  SimpleCallable &set(int64_t n_items)
  {
    n_items_ = n_items;
    return *this;
  }
  int call(pthread_t thread, int64_t idx)
  {
    int err = 0;
    fprintf(stdout, "worker[%ld] run\n", idx);
    if (idx % 2) {
      err = -EPERM;
    }
    return err;
  }
};

int main(int argc, char **argv)
{
  int err = 0;
  BaseWorker worker;
  SimpleCallable callable;
  int64_t n_thread = 0;
  int64_t n_items = 0;
  if (argc != 3) {
    err = -EINVAL;
    fprintf(stderr, "%s n_thread n_item\n", argv[0]);
  } else {
    n_thread = atoll(argv[1]);
    n_items = atoll(argv[2]);
    profile(worker.set_thread_num(n_thread).par_do(&callable.set(n_items), 10000000), n_items);
  }
}
#endif

class RWT : public Callable {
  typedef void* (*pthread_handler_t)(void*);
  struct Thread {
    int set(RWT* self, int64_t idx)
    {
      self_ = self, idx_ = idx;
      return 0;
    }
    pthread_t thread_;
    RWT* self_;
    int64_t idx_;
  };

public:
  RWT() : n_read_thread_(0), n_write_thread_(0), n_admin_thread_(0)
  {}
  virtual ~RWT()
  {}

public:
  int64_t get_thread_num()
  {
    return 1 + n_read_thread_ + n_write_thread_ + n_admin_thread_;
  }
  RWT& set(const int64_t n_read, const int64_t n_write, const int64_t n_admin = 0)
  {
    n_read_thread_ = n_read;
    n_write_thread_ = n_write;
    n_admin_thread_ = n_admin;
    return *this;
  }
  int report_loop()
  {
    int err = 0;
    int64_t report_interval = 1000 * 1000;
    while (!stop_ && 0 == err) {
      usleep(static_cast<__useconds_t>(report_interval));
      err = report();
    }
    return err;
  }
  virtual int call(pthread_t thread, const int64_t idx_)
  {
    int err = 0;
    int64_t idx = idx_;
    (void)(thread);
    fprintf(stderr, "rwt.start(idx=%ld)\n", idx_);
    if (idx < 0) {
      err = -EINVAL;
    }
    if (0 == err && idx >= 0) {
      if (idx == 0) {
        err = report_loop();
      }
      idx -= 1;
    }
    if (0 == err && idx >= 0) {
      if (idx < n_read_thread_) {
        err = read(idx);
      }
      idx -= n_read_thread_;
    }
    if (0 == err && idx >= 0) {
      if (idx < n_write_thread_) {
        err = write(idx);
      }
      idx -= n_write_thread_;
    }
    if (0 == err && idx >= 0) {
      if (idx < n_admin_thread_) {
        err = admin(idx);
      }
      idx -= n_admin_thread_;
    }
    if (0 == err && idx >= 0) {
      err = -EINVAL;
    }
    if (0 != err) {
      stop_ = true;
    }
    fprintf(stderr, "rwt.start(idx=%ld)=>%d\n", idx_, err);
    return err;
  }
  virtual int report()
  {
    return 0;
  }
  virtual int read(const int64_t idx) = 0;
  virtual int write(const int64_t idx) = 0;
  virtual int admin(const int64_t idx)
  {
    (void)(idx);
    return 0;
  }

protected:
  int64_t n_read_thread_;
  int64_t n_write_thread_;
  int64_t n_admin_thread_;
};

#define _cfg(k, v) getenv(k) ?: v
#define _cfgi(k, v) atoll(getenv(k) ?: v)

inline int64_t rand_range(int64_t s, int64_t e)
{
  return s + random() % (e - s);
}

#define RWT_def(base)                                      \
  TEST_F(base, Rand)                                       \
  {                                                        \
    ASSERT_EQ(0, PARDO(get_thread_num(), this, duration)); \
    ASSERT_EQ(0, check_error());                           \
  }

#include "gtest/gtest.h"
#include "common/data_buffer.h"
#include "lib/allocator/ob_allocator.h"
#include "lib/allocator/ob_malloc.h"
#include "lib/regex/regex/utils.h"

using namespace oceanbase::common;

struct BufHolder {
  BufHolder(int64_t limit)
  {
    buf_ = (char*)ob_malloc(limit, ObModIds::TEST);
  }
  ~BufHolder()
  {
    ob_free((void*)buf_);
  }
  char* buf_;
};

struct BaseConfig {
  static const int64_t buf_limit = 1 << 21;
  BufHolder buf_holder;
  ObDataBuffer buf;
  int64_t duration;
  const char* schema;
  int64_t table_id;
  BaseConfig() : buf_holder(buf_limit)
  {
    buf.set_data(buf_holder.buf_, buf_limit);
    duration = _cfgi("duration", "3000000");
    schema = "./test.schema";
    table_id = 1002;
  }
};

class FixedAllocator : public ObIAllocator {
public:
  FixedAllocator(char* buf, int64_t limit) : buf_(buf), limit_(limit), pos_(0)
  {}
  virtual ~FixedAllocator()
  {}

public:
  void reset()
  {
    pos_ = 0;
  }
  virtual void* alloc(const int64_t sz)
  {
    void* ptr = NULL;
    int64_t pos = 0;
    if ((pos = __sync_add_and_fetch(&pos_, sz)) > limit_) {
      __sync_add_and_fetch(&pos_, -sz);
    } else {
      ptr = buf_ + pos;
    }
    return ptr;
  }
  virtual void free(void* ptr)
  {
    UNUSED(ptr);
  }

private:
  char* buf_;
  int64_t limit_;
  int64_t pos_;
};

#endif /* __OB_COMMON_TEST_BASE_H__ */
