/* 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.
 */

 //
 // Semaphore.h
 //
 // $Id$
 //
 // Library: Foundation
 // Package: Threading
 // Module:  Semaphore
 //
 // Definition of the Semaphore 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 Foundation_Semaphore_INCLUDED
#define Foundation_Semaphore_INCLUDED

#include <mutex>
#include <condition_variable>
#include <chrono>
#include <stdexcept>
#include <cassert>

class CSemaphore
{
public:
  /// Create with initial count \a n and maximum count \a max.
  CSemaphore(int n, int max)
    : _n(n), _max(max)
  {
    assert(n >= 0 && max > 0 && n <= max);
  }

  /// Convenience: initial count = n, max = n  (must be > 0).
  explicit CSemaphore(int n)
    : _n(n), _max(n)
  {
    assert(n > 0);
  }

  ~CSemaphore() = default;

  /// V operation – increment by one, wake one waiter.
  void set()
  {
    std::lock_guard<std::mutex> lk(_mutex);
    if (_n < _max)
      ++_n;
    _cond.notify_one();
  }

  /// P operation – block until count > 0, then decrement.
  void wait()
  {
    std::unique_lock<std::mutex> lk(_mutex);
    _cond.wait(lk, [this] { return _n > 0; });
    --_n;
  }

  /// Timed P – throws std::runtime_error on timeout (matches old Poco behaviour).
  void wait(long milliseconds)
  {
    std::unique_lock<std::mutex> lk(_mutex);
    if (!_cond.wait_for(lk, std::chrono::milliseconds(milliseconds),
      [this] { return _n > 0; }))
    {
      throw std::runtime_error("CSemaphore::wait: timeout");
    }
    --_n;
  }

  /// Timed P – returns true on success, false on timeout.
  bool tryWait(long milliseconds)
  {
    std::unique_lock<std::mutex> lk(_mutex);
    if (!_cond.wait_for(lk, std::chrono::milliseconds(milliseconds),
      [this] { return _n > 0; }))
    {
      return false;
    }
    --_n;
    return true;
  }

private:
  int                     _n;
  int                     _max;
  std::mutex              _mutex;
  std::condition_variable _cond;

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

#endif // Foundation_Semaphore_INCLUDED
