/*

    ========== licence begin  GPL
    Copyright (c) 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_BeforeImageList.hpp"
#include "Oms/OMS_Session.hpp"
#include "Oms/OMS_DumpInterface.hpp"
#include "Oms/OMS_ObjectContainer.hpp"
#include "SAPDBCommon/SAPDB_MemCopyMove.hpp"

class OMS_BeforeImageListDumpInfo 
{
public:
  OmsObjectContainerPtr m_this;
  OmsObjectContainerPtr m_hashNext;
  OMS_Context*          m_context;
  OMS_ClassIdEntry*     m_containerInfo;
  OmsObjectId           m_oid;
  tsp00_Int2            m_subtransLevel;
  unsigned char         m_state;
  bool                  m_filler1;
  tsp00_Int4            m_filler2;
};


OMS_BeforeImageList::OMS_BeforeImageList () : m_session(NULL) 
{
  int ix;
  for (ix = 0; ix < OMS_BEFOREIMAGE_MAX_SUBTRANS_LEVEL; ix++) {
    m_beforeImages[ix] = NULL;
  }
}

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

OMS_BeforeImageList::~OMS_BeforeImageList ()
{

}

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

void OMS_BeforeImageList::Dump(OMS_DumpInterface& dumpObj) const
{
  OMS_BeforeImageListDumpInfo beforeImageDumpInfo;
  OmsObjectContainerPtr pBeforeImage;
  bool isFirst = true;
  int  subtransLvl = m_session->CurrentSubtransLevel();
  while (subtransLvl > 0) {
    beforeImageDumpInfo.m_subtransLevel = subtransLvl;
    pBeforeImage = m_beforeImages[subtransLvl-1];
    while (pBeforeImage) {
      if (isFirst)
      {
        isFirst = false;
        dumpObj.SetDumpLabel(LABEL_OMS_BEFORE_IMAGE);
      }
      beforeImageDumpInfo.m_this     = pBeforeImage;
      beforeImageDumpInfo.m_hashNext = pBeforeImage->GetNext();
      beforeImageDumpInfo.m_context = *REINTERPRET_CAST(OMS_Context**, &pBeforeImage->m_pobj);
      OMS_ClassIdEntry* containerInfo = 
        pBeforeImage->GetContainerInfoNoCheck(beforeImageDumpInfo.m_context);   // PTS 1127338
      if (containerInfo != NULL && !containerInfo->GetContainerInfoPtr()->IsDropped())
        beforeImageDumpInfo.m_containerInfo = containerInfo;
      else
        beforeImageDumpInfo.m_containerInfo = NULL;
      beforeImageDumpInfo.m_state = pBeforeImage->GetState();
      beforeImageDumpInfo.m_oid   = pBeforeImage->m_oid;
      dumpObj.Dump(&beforeImageDumpInfo, sizeof(beforeImageDumpInfo));
      pBeforeImage   = pBeforeImage->GetNext();
    }
    subtransLvl--; 
  }
}

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

void OMS_BeforeImageList::init (OMS_Session* s)
{
  m_session = s;
}

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

void OMS_BeforeImageList::freeBeforeImages ()
{
  OmsObjectContainerPtr pBeforeImage;
  OmsObjectContainerPtr pToFree;
  OMS_ClassIdEntry*     pContainerInfo;
  OMS_Context*      context;
  int                   subtransLvl = m_session->CurrentSubtransLevel();
  while (subtransLvl > 0) {
    pBeforeImage                  = m_beforeImages[subtransLvl-1];
    m_beforeImages[subtransLvl-1] = NULL;
    while (pBeforeImage) {
      context        = *REINTERPRET_CAST(OMS_Context**, &pBeforeImage->m_pobj);
      pContainerInfo = pBeforeImage->GetContainerInfoNoCheck(context);   // PTS 1127338
      pToFree        = pBeforeImage;
      pBeforeImage   = pBeforeImage->GetNext();
      pContainerInfo->chainFree(*context, pToFree, 1);
    }
    subtransLvl--; 
  }
}

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

void OMS_BeforeImageList::removeContext (OMS_Context* pContext) 
{
  OmsObjectContainerPtr  pBeforeImage;
  OmsObjectContainerPtr* pPrev;
  OmsObjectContainerPtr  pToFree;
  OMS_ClassIdEntry*      pContainerInfo;
  OMS_Context*           currContext;
  int                    subtransLvl = m_session->CurrentSubtransLevel();
  while (subtransLvl > 0) {
    pBeforeImage = m_beforeImages[subtransLvl-1];
    pPrev        = &m_beforeImages[subtransLvl-1];
    while (NULL != pBeforeImage) {
      currContext = *REINTERPRET_CAST(OMS_Context**, &pBeforeImage->m_pobj);
      if (currContext == pContext) {
        pContainerInfo = pBeforeImage->GetContainerInfoNoCheck(currContext);  // PTS 1127338
        pToFree        = pBeforeImage;
        pBeforeImage   = pBeforeImage->GetNext();
        *pPrev         = pBeforeImage;
        pContainerInfo->chainFree(*currContext, pToFree, 2);
      }
      else {
        pPrev        = pBeforeImage->GetNextAddr();
        pBeforeImage = pBeforeImage->GetNext();
      }
    }
    subtransLvl--; 
  } 
}

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

void OMS_BeforeImageList::subtransCommit (int wantedSubtransLvl) 
{
  OmsObjectContainerPtr pBeforeImage, p;
  OmsObjectContainerPtr found;
  OMS_Context*      context;
  int         subtransLvl = m_session->CurrentSubtransLevel();
  tsp00_Uint4 mask = (1 == wantedSubtransLvl) ? 0 : (0xFFFFFFFF >> (OMS_BEFOREIMAGE_MAX_SUBTRANS_LEVEL - wantedSubtransLvl + 1));
  const bool version = m_session->InVersion() || OMS_Globals::m_globalsInstance->InSimulator();
  while (subtransLvl >= wantedSubtransLvl) {
    subtransLvl--;
    pBeforeImage                = m_beforeImages[subtransLvl];
    m_beforeImages[subtransLvl] = NULL;
    OmsObjectContainerPtr* pInsert = &m_beforeImages[wantedSubtransLvl-2];
    while (pBeforeImage) {
      p                  = pBeforeImage;
      pBeforeImage       = pBeforeImage->GetNext();
      context            = *REINTERPRET_CAST(OMS_Context**, &p->m_pobj);
      found = context->FindObjInContext(&p->m_oid);
      if (found) {
        found->m_beforeImages &= mask;
        if (!found->existBeforeImage(wantedSubtransLvl-1, version)) 
        {
          p->SetNext(*pInsert);
          *pInsert      = p;
          pInsert       = p->GetNextAddr();
          found->setBeforeImage (wantedSubtransLvl-1);
        }
        else 
        {
          OMS_ClassIdEntry* containerInfo = p->GetContainerInfo(context);
          containerInfo->chainFree(*context, p, 18);
        }
      }
      else {
        // object must have been release by application, i.e. omsRelease of an
        // object, that has been read for update without omsStore, omsDelete
      }
    }
  }
}

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

void OMS_BeforeImageList::subtransRollback (int wantedSubtransLvl)
{
  OmsObjectContainerPtr pBeforeImage, p;

  int         subtransLvl = m_session->CurrentSubtransLevel();
  tsp00_Uint4 mask        = (1 == wantedSubtransLvl) 
                            ? 0 
                            : (0xFFFFFFFF >> (OMS_BEFOREIMAGE_MAX_SUBTRANS_LEVEL - wantedSubtransLvl + 1));
  
  while (subtransLvl >= wantedSubtransLvl) {
    subtransLvl--;
    pBeforeImage                = m_beforeImages[subtransLvl];
    m_beforeImages[subtransLvl] = NULL;
    while (pBeforeImage) {
      p                  = pBeforeImage;
      p->m_beforeImages &= mask; 
      pBeforeImage       = pBeforeImage->GetNext();

      // Pointer to the context was stored in the vtable-field of the object
      OMS_Context*       context        = *REINTERPRET_CAST(OMS_Context**, &p->m_pobj);
      OMS_ClassIdEntry*  pContainerInfo = p->GetContainerInfoNoCheck(context);   // PTS 1127338

      OmsObjectContainerPtr found = context->FindObjInContext (&p->m_oid);
      if (found){
        if (p->NewFlag()) {
          // Before-image indicates, that the object was newly created in the subtransaction. 
          // So the rollback must delete the object completely,
          
          if (pContainerInfo->IsKeyedObject()) {
            if (context->IsVersionOid(found->m_oid)){
              context->DecNewObjectsToFlush();

              // Remove the entry in the key-tree (version)
              pContainerInfo->VersionDelKey(found, context);
            }
            else {
              // Reactivate 'replaced' version if one exists   PTS 1125361
              OmsObjectContainerPtr pReactivated = context->ReactivateReplacedVersion(p);

              // If reactivated object is also marked as new, then the number of objects to 
              // flush remain unchanged
              if (!pReactivated || !pReactivated->IsNewObject()){
                context->DecNewObjectsToFlush();
              }

              // Adapt the key-tree
              if ((pContainerInfo->UseCachedKeys())
                && (found == pContainerInfo->VersionFindKey(pContainerInfo->GetKeyPtr(found)))){
                if (pReactivated != NULL){
                  // Redirect the pointer in the key tree to the formerly replaced object
                  pContainerInfo->VersionReplaceOrAddKeyContainerPtr(pReactivated, context);
                }
                else {
                  // Remove the entry in the key-tree (cached-keys)
                  pContainerInfo->VersionDelKey(found, context);
                }
              }
            }
          }
          else {
            // For non-keyed objects the number of objects to flush must always be decreased
            context->DecNewObjectsToFlush();
          }

          OMS_DETAIL_TRACE(omsTrNewObj, m_session->m_lcSink,
            "OMS_BeforeImageList::subtransRollback : dec flush: " << found->m_oid 
            << ", class: " << pContainerInfo->GetClassInfoPtr()->GetClassName());

          context->DeleteObjInContext(found, pContainerInfo);
        }
        else {
          // Before-image indicates, that the object was modified in the subtransaction.
          if (found->IsNewObject() && ! p->IsNewObject()) {
            OMS_DETAIL_TRACE(omsTrNewObj, m_session->m_lcSink,
              "OMS_BeforeImageList::subtransRollback : dec flush: " << found->m_oid 
              << ", class: " << pContainerInfo->GetClassInfoPtr()->GetClassName());

            context->DecNewObjectsToFlush();
          }

          // Copy object body into the old frame.
          found->Copy (context, p, pContainerInfo->GetObjectSize());

          if (pContainerInfo->IsKeyedObject()
             && (pContainerInfo->UseCachedKeys() || context->IsVersionOid(found->m_oid))
             && !found->DeletedFlag()){
            // Adjust the key-tree, so that the entry is pointing to the right object
            pContainerInfo->VersionReplaceOrAddKeyContainerPtr(found, context);
          }

          // restore vtable ptr 
          *REINTERPRET_CAST(void**, &found->m_pobj) = pContainerInfo->GetVirtualTablePtr();
        }
      }
      else {
        if (!pContainerInfo->GetContainerInfoPtr()->IsDropped()){ // PTS 1127338 condition added 
          // object must have been release by application, i.e. omsRelease of an
          // object, that has been read for update without omsStore, omsDelete
          // *** TODO: Should never occur! If tested for a while then remove completely (MS 28.11.03)
          OMS_TRACE(omsTrAlways, m_session->m_lcSink,
            "OMS_BeforeImageList::subtransRollback :" << __MY_FILE__ << ":" << __LINE__ );
        }
      }
      
      // Release the memory of the before-image
      pContainerInfo->chainFree(*context, p, 3);
    }
  }
}

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

void OMS_BeforeImageList::checkStores()
{
  OmsObjectContainerPtr pBeforeImage = m_beforeImages[0];
  while (pBeforeImage) {
    OmsObjectContainerPtr p = pBeforeImage;
    pBeforeImage            = pBeforeImage->GetNext();

    if (!p->NewFlag()) {
      OMS_Context* context        = *REINTERPRET_CAST(OMS_Context**, &p->m_pobj);
      OmsObjectContainerPtr found = context->FindObjInContext(&p->m_oid);
      if (NULL != found) 
      {
        OMS_ClassIdEntry* pContainerInfo = p->GetContainerInfoNoCheck(context);   // PTS 1127338
        if (!found->StoredFlag() && !found->DeletedFlag() &&
          found->Compare(context, p, pContainerInfo->GetPersistentSize()) != 0) {
          // ERROR: not stored, but modified
          const char *className = pContainerInfo->GetClassInfoPtr()->GetClassName();
          OMS_TRACE(omsTrAlways, m_session->m_lcSink,
            "OMS_BeforeImageList::checkStores: " << found->m_oid 
            << ", class: " << className << " not stored but modified");

          // quick hack, TODO: remove MultiPurpose later?
          struct _DATA {
            OmsObjectId oid;
            const char  *name;

            _DATA(OmsObjectId &o, const char *n)
              : oid(o), name(n)
            {
            }
          };
          _DATA data(found->m_oid, className);
          short err;
          tsp00_Int4 datasize = sizeof(data);
          m_session->m_lcSink->MultiPurpose(m_verify, mm_object, &datasize,
            reinterpret_cast<unsigned char*>(&data), &err);
        }
      } else {
        // object must have been release by application, i.e. omsRelease of an
        // object, that has been read for update without omsStore, omsDelete
      }
    }
  }
}

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

void OMS_BeforeImageList::insertBeforeImage (OmsObjectContainerPtr pObj, int subtransLvl)
{
  if (subtransLvl > 0) {
#ifdef _ASSERT_OMS
    OmsObjectContainerPtr p = m_beforeImages[subtransLvl-1];
    while (p) {
      _OMSASSERT (m_session, pObj->m_oid != p->m_oid);
      p = p->GetNext();
    }
#endif
    OMS_ClassIdEntry* pContainerInfo = pObj->GetContainerInfo(m_session->m_context);  
    OmsObjectContainerPtr pBeforeImage;
    if (pObj->IsVerNewObject()) {
      pBeforeImage = m_session->GetMemoryInVersion (*pContainerInfo);
    } else {
      pBeforeImage = m_session->GetMemory (*pContainerInfo);
    }
    SAPDB_MemCopyNoCheck (pBeforeImage, pObj, pContainerInfo->GetObjectSize());
    pBeforeImage->SetNext(m_beforeImages[subtransLvl-1]);
    m_beforeImages[subtransLvl-1] = pBeforeImage;
    pObj->setBeforeImage (subtransLvl);
    /* use the vtable ptr of the before image to store the context the object is from */
    *REINTERPRET_CAST(OMS_Context**, (&pBeforeImage->m_pobj)) = m_session->m_context;
    if (pObj->VarObjFlag())
    {
      OMS_VarObjInfo* objInfo = REINTERPRET_CAST (OMS_VarObjInfo*, &pObj->m_pobj);
      objInfo->incRefCnt();
    }
  }
}

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

void OMS_BeforeImageList::insertNewBeforeImage (OmsObjectContainerPtr pObj, OMS_ClassIdEntry* pContainerInfo, int subtransLvl) 
{
  if (subtransLvl > 0) {
#ifdef _ASSERT_OMS
    OmsObjectContainerPtr p = m_beforeImages[subtransLvl-1];
    while (p) {
      _OMSASSERT (m_session, pObj->m_oid != p->m_oid);
      p = p->GetNext();
    }
#endif
    OmsObjectContainerPtr pBeforeImage;
    if (pObj->IsVerNewObject()) {
      pBeforeImage = m_session->GetMemoryInVersion (*pContainerInfo);
    } else {
      pBeforeImage = m_session->GetMemory (*pContainerInfo);
    }
    SAPDB_MemCopyNoCheck (pBeforeImage, pObj, pContainerInfo->GetObjectSize());
    pBeforeImage->SetNext(m_beforeImages[subtransLvl-1]);
    pBeforeImage->MarkNew();
    m_beforeImages[subtransLvl-1] = pBeforeImage;
    pObj->setBeforeImage (subtransLvl);
    /* use the vtable ptr of the before image to store the context the object is from */
    *REINTERPRET_CAST(OMS_Context**, (&pBeforeImage->m_pobj)) = m_session->m_context;
  }
}


