/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
#include "td/utils/port/config.h"
#include "td/utils/common.h"
#include "td/utils/Status.h"
#if TD_PORT_POSIX
#include 
#endif
#include 
namespace td {
class RwMutex {
 public:
  RwMutex() {
    init();
  }
  RwMutex(const RwMutex &) = delete;
  RwMutex &operator=(const RwMutex &) = delete;
  RwMutex(RwMutex &&other) {
    init();
    other.clear();
  }
  RwMutex &operator=(RwMutex &&other) {
    other.clear();
    return *this;
  }
  ~RwMutex() {
    clear();
  }
  bool empty() const {
    return !is_valid_;
  }
  void init();
  void clear();
  struct ReadUnlock {
    void operator()(RwMutex *ptr) {
      ptr->unlock_read_unsafe();
    }
  };
  struct WriteUnlock {
    void operator()(RwMutex *ptr) {
      ptr->unlock_write_unsafe();
    }
  };
  using ReadLock = std::unique_ptr;
  using WriteLock = std::unique_ptr;
  Result lock_read() TD_WARN_UNUSED_RESULT {
    lock_read_unsafe();
    return ReadLock(this);
  }
  Result lock_write() TD_WARN_UNUSED_RESULT {
    lock_write_unsafe();
    return WriteLock(this);
  }
  void lock_read_unsafe();
  void lock_write_unsafe();
  void unlock_read_unsafe();
  void unlock_write_unsafe();
 private:
  bool is_valid_ = false;
#if TD_PORT_POSIX
  pthread_rwlock_t mutex_;
#elif TD_PORT_WINDOWS
  unique_ptr mutex_;
#endif
};
inline void RwMutex::init() {
  CHECK(empty());
  is_valid_ = true;
#if TD_PORT_POSIX
  pthread_rwlock_init(&mutex_, nullptr);
#elif TD_PORT_WINDOWS
  mutex_ = make_unique();
  InitializeSRWLock(mutex_.get());
#endif
}
inline void RwMutex::clear() {
  if (is_valid_) {
#if TD_PORT_POSIX
    pthread_rwlock_destroy(&mutex_);
#elif TD_PORT_WINDOWS
    mutex_.release();
#endif
    is_valid_ = false;
  }
}
inline void RwMutex::lock_read_unsafe() {
  CHECK(!empty());
// TODO error handling
#if TD_PORT_POSIX
  pthread_rwlock_rdlock(&mutex_);
#elif TD_PORT_WINDOWS
  AcquireSRWLockShared(mutex_.get());
#endif
}
inline void RwMutex::lock_write_unsafe() {
  CHECK(!empty());
#if TD_PORT_POSIX
  pthread_rwlock_wrlock(&mutex_);
#elif TD_PORT_WINDOWS
  AcquireSRWLockExclusive(mutex_.get());
#endif
}
inline void RwMutex::unlock_read_unsafe() {
  CHECK(!empty());
#if TD_PORT_POSIX
  pthread_rwlock_unlock(&mutex_);
#elif TD_PORT_WINDOWS
  ReleaseSRWLockShared(mutex_.get());
#endif
}
inline void RwMutex::unlock_write_unsafe() {
  CHECK(!empty());
#if TD_PORT_POSIX
  pthread_rwlock_unlock(&mutex_);
#elif TD_PORT_WINDOWS
  ReleaseSRWLockExclusive(mutex_.get());
#endif
}
}  // namespace td