/* ES40 emulator.
 * Copyright (C) 2007-2008 by the ES40 Emulator Project
 *
 * WWW    : https://github.com/gdwnldsKSC/es40
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Although this is not required, the author would appreciate being notified of, 
 * and receiving any modifications you may make to the source code that might serve
 * the general public.
 *
 * Parts of this file based upon the Poco C++ Libraries, which is Copyright (C) 
 * 2004-2006, Applied Informatics Software Engineering GmbH. and Contributors.
 */

//
// RWLock.h
//
// $Id$
//
// Library: Foundation
// Package: Threading
// Module:  RWLock
//
// Definition of the RWLock class.
//
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
// 
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//


#ifndef ES40_RWLOCK_INCLUDED
#define ES40_RWLOCK_INCLUDED

#include <shared_mutex>
#include <atomic>
#include <chrono>
#include <thread>
#include <cstdio>
#include <stdexcept>

#include "Mutex.h"   

class CScopedRWLock;

class CRWLock
{
public:
  typedef CScopedRWLock CScopedLock;

  CRWLock() : lockName(const_cast<char*>("?")) {}

  explicit CRWLock(const char* lName)
    : lockName(const_cast<char*>(lName)) {
  }

  ~CRWLock() = default;

  void readLock()
  {
#if defined(DEBUG_LOCKS)
    printf("   READ LOCK mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    _mutex.lock_shared();
    _exclusiveHeld.store(false, std::memory_order_release);
#if defined(DEBUG_LOCKS)
    printf(" READ LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
  }

  void writeLock()
  {
#if defined(DEBUG_LOCKS)
    printf("  WRITE LOCK mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    _mutex.lock();
    _exclusiveHeld.store(true, std::memory_order_release);
#if defined(DEBUG_LOCKS)
    printf("WRITE LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
  }

  void readLock(long milliseconds)
  {
#if defined(DEBUG_LOCKS)
    printf("   READ LOCK mutex %s from thread %s (%ld ms).\n", lockName, CURRENT_THREAD_NAME, milliseconds);
#endif
    if (!tryReadLock(milliseconds))
    {
      printf("TIMEOUT read-locking %s from thread %s after %ld ms.\n",
        lockName, CURRENT_THREAD_NAME, milliseconds);
      throw std::runtime_error("CRWLock::readLock: timeout");
    }
#if defined(DEBUG_LOCKS)
    printf(" READ LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
  }

  void writeLock(long milliseconds)
  {
#if defined(DEBUG_LOCKS)
    printf("  WRITE LOCK mutex %s from thread %s (%ld ms).\n", lockName, CURRENT_THREAD_NAME, milliseconds);
#endif
    if (!tryWriteLock(milliseconds))
    {
      printf("TIMEOUT write-locking %s from thread %s after %ld ms.\n",
        lockName, CURRENT_THREAD_NAME, milliseconds);
      throw std::runtime_error("CRWLock::writeLock: timeout");
    }
#if defined(DEBUG_LOCKS)
    printf("WRITE LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
  }

  bool tryReadLock()
  {
    bool res = _mutex.try_lock_shared();
    if (res)
      _exclusiveHeld.store(false, std::memory_order_release);
#if defined(DEBUG_LOCKS)
    printf("  %s mutex %s from thread %s.\n",
      res ? "  RD LOCKED" : "CAN'T LOCK", lockName, CURRENT_THREAD_NAME);
#endif
    return res;
  }

  bool tryWriteLock()
  {
    bool res = _mutex.try_lock();
    if (res)
      _exclusiveHeld.store(true, std::memory_order_release);
#if defined(DEBUG_LOCKS)
    printf("  %s mutex %s from thread %s.\n",
      res ? "  WR LOCKED" : "CAN'T LOCK", lockName, CURRENT_THREAD_NAME);
#endif
    return res;
  }

  bool tryReadLock(long milliseconds)
  {
    auto deadline = std::chrono::steady_clock::now()
      + std::chrono::milliseconds(milliseconds);
    do
    {
      if (_mutex.try_lock_shared())
      {
        _exclusiveHeld.store(false, std::memory_order_release);
#if defined(DEBUG_LOCKS)
        printf("  RD LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
        return true;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
    } while (std::chrono::steady_clock::now() < deadline);
#if defined(DEBUG_LOCKS)
    printf("CAN'T LOCK mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    return false;
  }

  bool tryWriteLock(long milliseconds)
  {
    auto deadline = std::chrono::steady_clock::now()
      + std::chrono::milliseconds(milliseconds);
    do
    {
      if (_mutex.try_lock())
      {
        _exclusiveHeld.store(true, std::memory_order_release);
#if defined(DEBUG_LOCKS)
        printf("  WR LOCKED mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
        return true;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
    } while (std::chrono::steady_clock::now() < deadline);
#if defined(DEBUG_LOCKS)
    printf("CAN'T LOCK mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    return false;
  }

  void unlockRead()
  {
#if defined(DEBUG_LOCKS)
    printf("    UNLOCKRD mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    _mutex.unlock_shared();
  }

  void unlockWrite()
  {
#if defined(DEBUG_LOCKS)
    printf("    UNLOCKWR mutex %s from thread %s.\n", lockName, CURRENT_THREAD_NAME);
#endif
    _mutex.unlock();
  }

  void unlock()
  {
    if (_exclusiveHeld.load(std::memory_order_acquire))
      unlockWrite();
    else
      unlockRead();
  }

  char* lockName;

private:
  std::shared_mutex   _mutex;
  std::atomic<bool>   _exclusiveHeld{ false };

  CRWLock(const CRWLock&) = delete;
  CRWLock& operator=(const CRWLock&) = delete;
};

class CScopedRWLock
{
public:
  CScopedRWLock(CRWLock* rwl, bool write = false)
    : _rwl(rwl), _write(write)
  {
    if (_write)
      _rwl->writeLock();
    else
      _rwl->readLock();
  }

  ~CScopedRWLock()
  {
    if (_write)
      _rwl->unlockWrite();
    else
      _rwl->unlockRead();
  }

  CScopedRWLock(const CScopedRWLock&) = delete;
  CScopedRWLock& operator=(const CScopedRWLock&) = delete;

private:
  CRWLock* _rwl;
  bool     _write;
};

#define SCOPED_READ_LOCK(mutex)   CScopedRWLock L_##__LINE__((mutex), false)
#define SCOPED_WRITE_LOCK(mutex)  CScopedRWLock L_##__LINE__((mutex), true)

#define MUTEX_READ_UNLOCK(mutex)  (mutex)->unlockRead()

#endif // ES40_RWLOCK_INCLUDED
