/*!
 * \file    OMS_VersionDictionary.cpp
 * \author  IvanS, MarkusSi, PeterG
 * \brief   Implementation of the dictionary for versions
 */

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

*/

#include "Oms/OMS_Globals.hpp"
#include "Oms/OMS_VersionDictionary.hpp"
#include "Oms/OMS_SinkCriticalSection.hpp"
#include "Oms/OMS_Session.hpp"
#include "Oms/OMS_VersionDirRegions.hpp"
#include "liveCache/LVC_LockRequest.hpp"
#include "liveCache/LVC_IliveCacheSink.hpp"
#include "SAPDBCommon/SAPDB_MemCopyMove.hpp"

/*--------------------------------------------------------------------------*/
/*       Implementation of  OMS_VersionDictionary                           */
/*--------------------------------------------------------------------------*/

OMS_VersionDictionary::OMS_VersionDictionary() 
  : m_UnloadableVersions(),
    m_useRWLocks(true)
{
  for (int i=0; i<OMS_VDIR_SIZE; ++i){
    // Initialize buckets of hash-array
    m_pVersionDictionary[i] = NULL;
  }
}

/*---------------------------------------------------------------------------*/

tgg00_BasisError OMS_VersionDictionary::DropVersion  (const OmsVersionId&  versionId)
{
  int slot = HashValue(versionId);
  OMS_Context *pCurr = m_pVersionDictionary[slot];
  OMS_Context *pPred = NULL;
  while (pCurr){
    if (!memcmp(pCurr->GetVersionId(), versionId, sizeof(OmsVersionId))){
      if (pPred != NULL)
        pPred->SetNext(pCurr->GetNext());
      else
        m_pVersionDictionary[slot] = pCurr->GetNext();

      // PTS 1125433
      pCurr->SetNext(NULL);

      return e_ok;
    }
    pPred = pCurr;
    pCurr = pCurr->GetNext();
  }
  return e_unknown_version;
}

/*---------------------------------------------------------------------------*/

OMS_Context* OMS_VersionDictionary::FindVersion (const OmsVersionId& versionId) 
{
  OMS_Context *pCurr = m_pVersionDictionary[HashValue(versionId)];
  while (pCurr){
    if (!memcmp(pCurr->GetVersionId(), versionId, sizeof(OmsVersionId))){
      return pCurr;
    }
    pCurr = pCurr->GetNext();
  }
  return NULL;
}

/*---------------------------------------------------------------------------*/

tgg00_BasisError OMS_VersionDictionary::InsertVersion(const OmsVersionId& versionId,OMS_Context* pContext) 
{
  int slot = HashValue(versionId);
  if (m_pVersionDictionary[slot] == NULL){
    m_pVersionDictionary[slot] = pContext;
  }
  else {
    OMS_Context* pCurr = m_pVersionDictionary[slot];
    while (true) {
      // Check if there is already a version with the same name
      if (!memcmp(pCurr->GetVersionId(), versionId, sizeof(OmsVersionId))){
        return e_duplicate_key;
      }

      if (pCurr->GetNext() == NULL){
        // Append new entry at end of list
        pCurr->SetNext(pContext);
        return e_ok;
      }
      else {
        pCurr = pCurr->GetNext();
      }
    }
  }

  return e_ok;
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::Shutdown() 
{
  for (int i=0; i<OMS_VDIR_SIZE; ++i){
    m_pVersionDictionary[i] = NULL;
  }
}

/*---------------------------------------------------------------------------*/

bool OMS_VersionDictionary::UnloadOldestVersion(IliveCacheSink* lcSink) 
{
  OMS_Context* pContext = NULL;
  { // Begin of exclusive critical section.   // PTS 1124170
    // Aquire all locks on the version dictionary.
    int         pLockId[OMS_VDIR_SIZE];
    int         size;
    GetAllLockIds(&pLockId[0], size);
    ExclusiveVersionDirRgn rgn(&pLockId[0], size, UseRWLocks());

    OMS_SinkCriticalSection unloadRegion(lcSink, RGN_UNLOAD_VERSION);
    unloadRegion.Enter();
    pContext = m_UnloadableVersions.RemoveVersion(NULL);
    if (NULL != pContext)
    {
        DbpBase b(lcSink);
        char versionId[sizeof(OmsVersionId)+1];
        SAPDB_MemCopyNoCheck(&versionId[0], &pContext->GetVersionId()[0], sizeof(OmsVersionId));
        versionId[sizeof(OmsVersionId)] = 0;
        b.dbpOpMsg("unloading oms version %s", &versionId[0]);
        pContext->AssignLcSink(lcSink);
        pContext->UnLoad();
        pContext->ResetLcSink();
    }
  }  // End of exclusive critical section.

  return pContext == NULL ? false : true;
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::MarkNotUnloadable(IliveCacheSink* lcSink, OMS_Context* pContext)
{
  OMS_SinkCriticalSection region(lcSink, RGN_UNLOAD_VERSION);
  region.Enter();
  m_UnloadableVersions.RemoveVersion(pContext);
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::MarkUnloadable(OMS_Context* pContext, bool callFromDestructor)
{
  OMS_SinkCriticalSection region(pContext->m_session->m_lcSink, RGN_UNLOAD_VERSION);
  region.Enter();
  m_UnloadableVersions.InsertVersion(pContext, callFromDestructor);
}

/*---------------------------------------------------------------------------*/

int OMS_VersionDictionary::GetSingleLockId(const OmsVersionId &versionId) const{
  return HashValue(versionId) + GetVDirStartCnt();
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::GetMultiLockIds(const OmsVersionId** ppVersionId, int &size, int *pLockId) const
{
  int i, j;
  int tmp[OMS_VDIR_SIZE];

  // Initialize temporary array
  for (i=0; i<OMS_VDIR_SIZE; ++i){
    tmp[i] = -1;
  }

  // Determine hash slots which corresponds to the given version-ids. 
  for (i=0; i<size; ++i){
    int slot = HashValue(*(ppVersionId[i]));
    if (tmp[slot] == -1)
      tmp[slot] = slot + GetVDirStartCnt();
  }

  // "Compress" the array
  for (i=0,j=0; i<OMS_VDIR_SIZE; ++i){
    if (tmp[i] != -1)
      pLockId[j++] = tmp[i];
  }

  // Return the number of lock-ids
  size = j;
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::GetAllLockIds(int *pLockId, int &size) const{
  for (int i=0; i<OMS_VDIR_SIZE; ++i)
    pLockId[i] = i + GetVDirStartCnt();
  size   = OMS_VDIR_SIZE;
}

/*---------------------------------------------------------------------------*/

bool OMS_VersionDictionary::isLockedByOwn(int slot) const {  
  int lockId = slot + GetVDirStartCnt();
  if (m_useRWLocks){
    LVC_LockRequest lock(LVC_LockRequest::RWLOCK_IS_LOCKED, lockId);
    short err = co10_GetKernelInterface()->LockRequest(lock);
    return (err == 0 ? true : false);
  }
  else {
    bool inRgn = false;
    OMS_Globals::m_globalsInstance->GetCurrentLcSink()->IsInRegion(lockId, inRgn);
    return inRgn;
  }
}

/*---------------------------------------------------------------------------*/






/*--------------------------------------------------------------------------*/
/*       Implementation of  CVersionDirctionary_co17::Iter                  */
/*--------------------------------------------------------------------------*/


OMS_VersionDictionary::Iter::Iter()
{
  m_slot     = -1;
  m_lockMode = OMS_NO_LOCK;
  m_pCurr    = NULL;
  m_locked   = false;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "OMS_VersionDictionary::Iter::CTOR");
}
  
OMS_VersionDictionary::Iter::Iter(OMS_LockMode lockMode)
{
  m_slot     = -1;
  m_lockMode = lockMode;
  m_pCurr    = NULL;
  m_locked   = false;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "OMS_VersionDictionary::Iter::CTOR");
}
  
/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::Iter::Reset(OMS_LockMode lockMode)
{
  m_lockMode   = lockMode;
  m_slot       = -1;
  m_pCurr      = NULL;
  m_locked     = false;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "OMS_VersionDictionary::Iter::Reset");
}

/*---------------------------------------------------------------------------*/

OMS_VersionDictionary::Iter::Iter(const Iter &iter){
  // Copying is not allowed
  throw DbpError(DbpError::DB_ERROR, e_invalid_command, "OMS_VersionDictionary::Iter::Iter", __MY_FILE__, __LINE__);
}

/*---------------------------------------------------------------------------*/

OMS_VersionDictionary::Iter& OMS_VersionDictionary::Iter::operator=(const OMS_VersionDictionary::Iter &iter){
  // Assignment is not allowed
  throw DbpError(DbpError::DB_ERROR, e_invalid_command, "OMS_VersionDictionary::Iter::operator==", __MY_FILE__, __LINE__);
  return *this;
}

/*---------------------------------------------------------------------------*/
OMS_VersionDictionary::Iter::~Iter()
{
  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "BEGIN OMS_VersionDictionary::Iter::DTOR-1-");

  if (m_locked){
    // Release lock on the current slot
    LeaveRgn();
  }

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "END OMS_VersionDictionary::Iter::DTOR-2-");
}

/*---------------------------------------------------------------------------*/

OMS_Context* OMS_VersionDictionary::Iter::GetFirstInSlot()
{
  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "BEGIN OMS_VersionDictionary::Iter::GetFirstInSlot");

  if (m_pCurr != NULL){
    throw DbpError(DbpError::DB_ERROR, e_invalid_iterator, "OMS_VersionDictionary::Iter::GetFirstInSlot", __MY_FILE__, __LINE__);
  }

  // There might be still a lock on the previous slot; release it now
  if (m_locked){
    LeaveRgn();
  }

  OMS_VersionDictionary& VD = OMS_Globals::m_globalsInstance->m_versionDictionary;

  ++m_slot;
  while (m_slot < OMS_VDIR_SIZE){
    // Get appropriate lock on the current slot
    EnterRgn();

    if (VD.m_pVersionDictionary[m_slot] != NULL){
      m_pCurr = VD.m_pVersionDictionary[m_slot];
      break;
    }

    // Release lock on the current slot
    LeaveRgn();

    ++m_slot;
  }

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "END OMS_VersionDictionary::Iter::GetFirstInSlot" << (m_pCurr==0 ? " = EMPTY" : ""));

  return m_pCurr;
}

/*---------------------------------------------------------------------------*/

OMS_Context* OMS_VersionDictionary::Iter::GetNextInSlot()
{
  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "BEGIN OMS_VersionDictionary::Iter::GetNextInSlot" << (dropCurr ? " DropCurr" : ""));

  OMS_VersionDictionary& VD = OMS_Globals::m_globalsInstance->m_versionDictionary;

  if (m_pCurr == NULL){
    throw DbpError(DbpError::DB_ERROR, e_invalid_iterator, "OMS_VersionDictionary::Iter::GetNextInSlot", __MY_FILE__, __LINE__);
  }

  m_pCurr = m_pCurr->GetNext();

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "END OMS_VersionDictionary::Iter::getNext" << (m_pCurr==0 ? " = EMPTY" : ""));

  return m_pCurr;
}

/*---------------------------------------------------------------------------*/
    
void OMS_VersionDictionary::Iter::ReleaseSlot()
{
  m_pCurr = NULL;

  if (m_locked){
    LeaveRgn();
  }
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::Iter::EnterRgn()
{
  OMS_VersionDictionary& VD = OMS_Globals::m_globalsInstance->m_versionDictionary;

  if (m_pCurr != NULL || m_slot >= OMS_VDIR_SIZE){
    throw DbpError(DbpError::DB_ERROR, e_invalid_iterator, "OMS_VersionDictionary::Iter::enterRgn", __MY_FILE__, __LINE__);
    return;
  }

  // ONLY for debugging
  //if (VD.isLockedByOwn(m_slot)){
  //  DebugBreak();  
  //}

  if (m_lockMode == OMS_NO_LOCK)
    return;

  int lockId = m_slot + VD.GetVDirStartCnt();

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), 
  //  (m_lockMode == OMS_LOCK_EXCLUSIVE ? "Exclusive " : "Shared ") << "Region requested: " << lockId);

  if (VD.m_useRWLocks){
    LVC_LockRequest lock(m_lockMode == OMS_LOCK_SHARED ? LVC_LockRequest::RWLOCK_LOCK_SHARED : LVC_LockRequest::RWLOCK_LOCK_EXCLUSIVE, 
                         lockId);
    short err = co10_GetKernelInterface()->LockRequest(lock);
    if (err != e_ok)
      throw DbpError(DbpError::DB_ERROR, err, "OMS_VersionDictionary::Iter::enterRgn", __MY_FILE__, __LINE__);
  }
  else {
    OMS_Globals::m_globalsInstance->GetCurrentLcSink()->EnterCriticalSection(lockId);
  }

  m_locked = true;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), 
  //  (m_lockMode == OMS_LOCK_EXCLUSIVE ? "Exclusive " : "Shared ") << "Region aquired: " << lockId);
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::Iter::LeaveRgn()
{
  OMS_VersionDictionary& VD = OMS_Globals::m_globalsInstance->m_versionDictionary;

  if (m_slot >= OMS_VDIR_SIZE){
    throw DbpError(DbpError::DB_ERROR, e_invalid_iterator, "OMS_VersionDictionary::Iter::leaveRgn", __MY_FILE__, __LINE__);
    return;
  }

  // ONLY for debugging
  //if (!VD.isLockedByOwn(m_slot)){
  //  DebugBreak();  
  //}

  if (m_lockMode == OMS_NO_LOCK)
    return;

  int lockId = m_slot + VD.GetVDirStartCnt();

  if (VD.m_useRWLocks){
    LVC_LockRequest lock(m_lockMode == OMS_LOCK_SHARED ? LVC_LockRequest::RWLOCK_UNLOCK_SHARED : LVC_LockRequest::RWLOCK_UNLOCK_EXCLUSIVE, 
                         lockId);
    short err = co10_GetKernelInterface()->LockRequest(lock);
    if (err != e_ok)
      throw DbpError(DbpError::DB_ERROR, err, "OMS_VersionDictionary::Iter::leaveRgn", __MY_FILE__, __LINE__);
  }
  else {
    OMS_Globals::m_globalsInstance->GetCurrentLcSink()->LeaveCriticalSection(lockId);
  }

  m_locked = false;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), 
  //  (m_lockMode == OMS_LOCK_EXCLUSIVE ? "Exclusive " : "Shared ") << "Region freed: " << lockId);
}

/*---------------------------------------------------------------------------*/

void OMS_VersionDictionary::Iter::Stop(){
  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "BEGIN OMS_VersionDictionary::Iter::Stop");

  if (m_locked){
    LeaveRgn();
  }

  m_pCurr    = NULL;
  m_slot     = -1;

  //OMS_TRACE(omsTrInterface, OMS_Globals::m_globalsInstance->GetCurrentLcSink(), "END OMS_VersionDictionary::Iter::Stop");
}

/*---------------------------------------------------------------------------*/


