/*!
  @file           RTESync_RWRegion.hpp
  @author         RobinW
  @ingroup        Runtime
  @brief          RWRegions

  A realization of critical regions that support exclusive ("Write") and non-exclusive ("Read") access an the usage
  of a pool of spinlocks instead of using one spinlock for each region.

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2001-2005 SAP AG

    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end



\endif
*/
#ifndef RTESYNC_RWREGION_HPP
#define RTESYNC_RWREGION_HPP

#include "SAPDB/SAPDBCommon/SAPDB_Types.hpp"
#include "SAPDB/RunTime/RTE_Types.hpp"
#include "SAPDB/RunTime/Synchronisation/RTESync_NamedSpinlock.hpp"

class RTESync_BaseSpinlockPool;
class RTESync_SpinlockPool;
/*!
   @class RTESync_IRWRegion
   @brief a critical region that can be accessed exclusively (if the data is only read) or non-exclusively (if the data is 
          modified). Non-exclusive access can be granted to several tasks in parallel, Exclusive acess is only granted
          to one task at a time.
          To reduce memory consumption, not every region has its own spinlock, but a reference to a spinlock pool
          is given to the region from which one spinlock is taken.

          This is only an interface. Instances of this class cannot be created directly. Use the factory function
          RTESync_CreateRWRegion() to create a new instance of a class with this interface.
 */
class RTESync_IRWRegion
{
public:
    /*!
      @brief                 enter the region either exclusively or non-exclusively

      @param exclusive  [in] true if exclusive access is desired, false if non-exclusive access is desired
      @param pid        [in] the current task id. As there are means to calculate the current task id, it can be left out.
                             But the calculation is quite expensive, so it is a good idea to fill it in.
      @return none
     */
    virtual void enter(bool exclusive,RTE_TaskId pid=RTE_UnknownTaskId)=0;

    /*!
      @brief                 try to enter the region, do not block if entering is not possible because of a collision

      @param exclusive  [in] true if exclusive access is desired, false if non-exclusive access is desired
      @param pid        [in] the current task id. As there are means to calculate the current task id, it can be left out.
                             But the calculation is quite expensive, so it is a good idea to fill it in.
      @return [bool]         true if the region could be entered, false if not.
     */
    virtual bool tryEnter(bool exclusive,RTE_TaskId pid=RTE_UnknownTaskId)=0;

    /*!
      @brief                 leave a region.

      @param exclusive  [in] the exclusive value enter()/tryEnter() had been called with
      @param pid        [in] the current task id. As there are means to calculate the current task id, it can be left out.
                             But the calculation is quite expensive, so it is a good idea to fill it in.
      @return none
     */
    virtual void leave(bool exclusive,RTE_TaskId pid=RTE_UnknownTaskId)=0;


    /*!
      @brief                 change the access level of an already entered region from exclusive access to non-exclusive access.
                             To leave the region, call leave() with exclusive=false!

      @param exclusive  [in] the exclusive value enter()/tryEnter() had been called
      @param pid        [in] the current task id. As there are means to calculate the current task id, it can be left out.
                             But the calculation is quite expensive, so it is a good idea to fill it in.
      @return none
     */
    virtual void SwitchToNonExclusiveAccess(RTE_TaskId pid=RTE_UnknownTaskId)=0;

    /*!
      @brief get the id of the region. Usable for sanity checks.

      @param none
      @return [SAPDB_Int8] the id of the region
     */
    virtual SAPDB_Int8 getId(void)=0;

    /*!
      @brief get the id of the task that was the last to enter the region. Usable for sanity checks.

      @param none
      @return [RTE_TaskId] the id of the task.
     */
    virtual RTE_TaskId getLastEntered(void)=0;


    virtual SAPDB_UInt4 GetCollisionCounter(void)=0;

    virtual SAPDB_UInt4 GetExclusiveAccessCounter(void)=0;

    virtual SAPDB_UInt4 GetNonExclusiveAccessCounter(void)=0;

    virtual SAPDB_UInt4 GetWaitCounter(void)=0;

    virtual SAPDB_UInt4 GetTasCounter(void)=0;

    /*!
      @brief get the spinlock pool the rwregion uses
     */
    virtual RTESync_SpinlockPool *GetSpinlockPool(void)=0;

    /*!
      There might be 
    */
    enum flavours {WithSemalist,WithoutSemalist};
protected:
    SAPDB_UTF8              m_Name[40+1];       ///< The copied spinlock name
};

/*!
  @brief            factory function that returns an instance of a class accessible by the interface RTESync_IRWRegion

  @param id         [in] the id of the region
  @param pool       [in] the spinlock pool from which the spinlocks are taken
  @param allocator  [in] the allocator from which the memory for the region is taken
  @param flavour    [in] the flavour. If left out, a default is chosen. Currently there is only one really sensible flavour
                         (the WithSemalist one), so the parameter can be left out in most cases-
  @return [RTESync_IRWRegion *] pointer to a newly created RWRegion. Call delete for it when it is no longer needed
*/
RTESync_IRWRegion *RTESync_CreateRWRegion(SAPDB_Int8 id,RTESync_BaseSpinlockPool &pool, SAPDBMem_IRawAllocator &allocator,RTESync_IRWRegion::flavours flavour = RTESync_IRWRegion::WithSemalist);

/*!
  @brief            function that that destroys an instance of a class created by RTESync_CreateRWRegion()

  @param region     [in] a pointer to the region to be destroyed
  @param allocator  [in] the allocator to which the memory for the region is returned
*/
void RTESync_DestroyRWRegion(RTESync_IRWRegion *region,SAPDBMem_IRawAllocator &allocator);

class SAPDBMem_IRawAllocator;

/*!
   @class RTESync_BaseSpinlockPool
   @brief Spinlock pool used to reduce collisions

   This special spinlock pool constructor uses the specified allocator only and is therefore 
   sharable across process borders, if the allocator returns memory which is sharable.

   The spinlock pooling allows to lock a large amount of objects without the need to have an 
   spinlock for each object.
 */
class RTESync_BaseSpinlockPool
{
public:
    /*!
      @brief Basic spinlock pool constructor

      Allocate memory using given allocator. The RTEMem_RteAllocator::Instance()
      is a good choice for kernel runtime, but a GlobalSharedMemory based allocator
      will allow to use a spinlock pool even in separate processes! To do that, the
      pool itself should have been allocated using that allocator, so that the pool
      reference is also sharable between processes.

      @param numberOfElements [in] fixed number of elements in pool
      @param allocator [in] allocator to use
      @return none
     */
    RTESync_BaseSpinlockPool(const SAPDB_UTF8 *poolIdentifier,
                             SAPDB_Int4 numberOfElements,
                             SAPDBMem_IRawAllocator &allocator );

    /*!
      @brief destruct spinlock pool

      Deallocates the pool memory. Be carefull to do that, because you are not protected
      against any dangling references to spinlocks in the pool...
     */
    ~RTESync_BaseSpinlockPool();

    /*!
      @brief retrieve internal lock index from given index
      The returned lock index allows a faster access to the spinlock in the lock pool

      @param id [in] any index value
      @return [SAPDB_Int4] the internal lock index
     */
    SAPDB_Int4 GetLockIndex(SAPDB_Int8 id);

    /*!
      @brief retrieve internal lock index from given pointer
      The returned lock index allows a faster access to the spinlock in the lock pool

      @param ptr [in] any pointer value
      @return [SAPDB_Int4] the internal lock index
     */
    SAPDB_Int4 GetLockIndex(void *ptr);

    /*!
      @brief lock via internal index

      @param lockIndex [in] internal lock pool index
      @return [SAPDB_Bool] true if lock failed, false if locked
     */
    SAPDB_Bool TryLock(SAPDB_Int4 lockIndex);

    /*!
      @brief unlock via internal index

      @param lockIndex [in] internal lock pool index 
      @return none
     */
    void Unlock(SAPDB_Int4 lockIndex);

    SAPDB_UTF8 *Name(void)
    {
        return m_Name;
    };

private:
    SAPDB_Int4              m_numberOfElements; ///< fixed number of elements in pool
    RTESync_NamedSpinlock * m_pool;             ///< pool spinlock array
    SAPDBMem_IRawAllocator &m_allocator;        ///< allocator used to allocate pool
protected:
    SAPDB_UTF8              m_Name[40+1];       ///< The copied spinlock name
friend class RTESync_SpinlockPoolRegister;
protected:
    RTE_ItemRegister<RTESync_BaseSpinlockPool>::Info    m_info;
};


/*!
   @class RTESync_SpinlockPool
   @brief Spinlock pool used to reduce collisions

   This special spinlock pool constructor uses memory from RunTime only and is not sharable
   across process borders. The spinlock pooling allows to lock a large amount of objects without
   the need to have an spinlock for each object.
 */
class RTESync_SpinlockPool : public RTESync_BaseSpinlockPool
{
public:
    /*!
      @brief Spinlock pool constructor for RTEMem_RteAllocator...

      @param numberOfElements [in] fixed number of elements in pool
      @return none
     */
    RTESync_SpinlockPool(const SAPDB_UTF8 *poolIdentifier,SAPDB_Int4 numberOfElements);

    /*!
      @brief destruct spinlock pool

      Deallocates the pool memory. Be carefull to do that, because you are not protected
      against any dangling references to spinlocks in the pool...
     */
    ~RTESync_SpinlockPool() {}
};

class RTESync_IRWRegionReader
{

public:
    enum Mode
    {
        normal,
        reset
    };

    enum Selection
    {
        all,
        usedOnesOnly
    };

    /* -----------------------------------------------------------------------*/
    /*! chapter: Iteration                                                    */
    /* -----------------------------------------------------------------------*/

    virtual SAPDB_Bool First (Mode mode = normal,Selection  selection = all)=0;

    virtual SAPDB_Bool Next (void)=0;

    /* -----------------------------------------------------------------------*/
    /*! endchapter: Iteration                                                 */
    /* -----------------------------------------------------------------------*/

    /* -----------------------------------------------------------------------*/
    /*! chapter: Data Access                                                  */
    /* -----------------------------------------------------------------------*/

    virtual SAPDB_Bool SpinlockPoolName         (SAPDB_UTF8 name[])=0;

    virtual SAPDB_Bool Address                  (SAPDB_UInt8 &address)=0;

    virtual SAPDB_Bool CollisionCount           (SAPDB_UInt8 &collisioncount)=0;

    virtual SAPDB_Bool WaitCount                (SAPDB_UInt8 &waitcount)=0;

    virtual SAPDB_Bool SpinLockCollisionCount   (SAPDB_UInt8 &tascount)=0;

    virtual SAPDB_Bool ExclusiveAccessCount     (SAPDB_UInt8 &exclusiveaccesscount)=0;

    virtual SAPDB_Bool NonExclusiveAccessCount  (SAPDB_UInt8 &nonexclusiveaccesscount)=0;

    virtual SAPDB_Bool LastEnteredTask          (SAPDB_Int4 &taskId)=0;
    
    virtual SAPDB_Bool CurrentAccesses          (SAPDB_Int4 &currentAccesses)=0;

#ifdef ADD_WAITERS_
    virtual SAPDB_Bool Waiters                  (SAPDB_Char waiters[])=0;
#endif
    virtual SAPDB_UInt4 GetNumberOfItems        (void)=0;
    /* -----------------------------------------------------------------------*/
    /*! endchapter: Data Access                                               */
    /* -----------------------------------------------------------------------*/
};

RTESync_IRWRegionReader *RTESync_GetRWRegionReader(SAPDBMem_IRawAllocator &allocator);

#endif // RTESYNC_RWREGION_HPP
