/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sot.hxx"

#if defined(_MSC_VER) && (_MSC_VER<1200)
#include <tools/presys.h>
#endif
#include <hash_map>
#if defined(_MSC_VER) && (_MSC_VER<1200)
#include <tools/postsys.h>
#endif
#include <vos/macros.hxx>

#include <string.h>
#include <osl/endian.h>
#include <tools/string.hxx>

#include "sot/stg.hxx"
#include "stgelem.hxx"
#include "stgcache.hxx"
#include "stgstrms.hxx"
#include "stgdir.hxx"
#include "stgio.hxx"

/*************************************************************************/
//-----------------------------------------------------------------------------
typedef std::hash_map
<
	sal_Int32,
	StgPage *,
	std::hash< sal_Int32 >,
	NAMESPACE_STD(equal_to)< sal_Int32 >
> UsrStgPagePtr_Impl;
#ifdef _MSC_VER
#pragma warning( disable: 4786 )
#endif

//#define	CHECK_DIRTY 1
//#define	READ_AFTER_WRITE 1

////////////////////////////// class StgPage /////////////////////////////
// This class implements buffer functionality. The cache will always return
// a page buffer, even if a read fails. It is up to the caller to determine
// the correctness of the I/O.

StgPage::StgPage( StgCache* p, short n )
{
    OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
    pCache = p;
    nData  = n;
    bDirty = sal_False;
    nPage  = 0;
    pData  = new sal_uInt8[ nData ];
    pNext1 =
    pNext2 =
    pLast1 =
    pLast2 = NULL;
	pOwner = NULL;
}

StgPage::~StgPage()
{
    delete [] pData;
}

void StgPage::SetPage( short nOff, sal_Int32 nVal )
{
    if( ( nOff < (short) ( nData / sizeof( sal_Int32 ) ) ) && nOff >= 0 )
    {
#ifdef OSL_BIGENDIAN
	  nVal = SWAPLONG(nVal);
#endif
        ((sal_Int32*) pData )[ nOff ] = nVal;
        bDirty = sal_True;
    }
}

//////////////////////////////// class StgCache ////////////////////////////

// The disk cache holds the cached sectors. The sector type differ according
// to their purpose.

sal_Int32 lcl_GetPageCount( sal_uLong nFileSize, short nPageSize )
{
//    return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
    // #i61980# reallife: last page may be incomplete, return number of *started* pages
    return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
}

StgCache::StgCache()
{
	nRef = 0;
	pStrm = NULL;
	pCur = pElem1 = NULL;
    nPageSize = 512;
    nError = SVSTREAM_OK;
	bMyStream = sal_False;
	bFile = sal_False;
	pLRUCache = NULL;
	pStorageStream = NULL;
}

StgCache::~StgCache()
{
    Clear();
	SetStrm( NULL, sal_False );
	delete (UsrStgPagePtr_Impl*)pLRUCache;
}

void StgCache::SetPhysPageSize( short n )
{
    OSL_ENSURE( n >= 512, "Unexpecte page size is provided!" );
    if ( n >= 512 )
    {
        nPageSize = n;
        sal_uLong nPos = pStrm->Tell();
        sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
        nPages = lcl_GetPageCount( nFileSize, nPageSize );
        pStrm->Seek( nPos );
    }
}

// Create a new cache element
// pCur points to this element

StgPage* StgCache::Create( sal_Int32 nPg )
{
    StgPage* pElem = new StgPage( this, nPageSize );
    pElem->nPage = nPg;
    // For data security, clear the buffer contents
    memset( pElem->pData, 0, pElem->nData );

	// insert to LRU
    if( pCur )
    {
        pElem->pNext1 = pCur;
        pElem->pLast1 = pCur->pLast1;
        pElem->pNext1->pLast1 =
        pElem->pLast1->pNext1 = pElem;
    }
    else
        pElem->pNext1 = pElem->pLast1 = pElem;
	if( !pLRUCache )
		pLRUCache = new UsrStgPagePtr_Impl();
	(*(UsrStgPagePtr_Impl*)pLRUCache)[pElem->nPage] = pElem;
    pCur = pElem;

	// insert to Sorted
    if( !pElem1 )
        pElem1 = pElem->pNext2 = pElem->pLast2 = pElem;
    else
    {
        StgPage* p = pElem1;
        do
        {
            if( pElem->nPage < p->nPage )
                break;
            p = p->pNext2;
        } while( p != pElem1 );
        pElem->pNext2 = p;
        pElem->pLast2 = p->pLast2;
        pElem->pNext2->pLast2 =
        pElem->pLast2->pNext2 = pElem;
        if( p->nPage < pElem1->nPage )
            pElem1 = pElem;
    }
    return pElem;
}

// Delete the given element

void StgCache::Erase( StgPage* pElem )
{
    OSL_ENSURE( pElem, "The pointer should not be NULL!" );
    if ( pElem )
    {
        OSL_ENSURE( pElem->pNext1 && pElem->pLast1, "The pointers may not be NULL!" );
        //remove from LRU
        pElem->pNext1->pLast1 = pElem->pLast1;
        pElem->pLast1->pNext1 = pElem->pNext1;
        if( pCur == pElem )
            pCur = ( pElem->pNext1 == pElem ) ? NULL : pElem->pNext1;
        if( pLRUCache )
            ((UsrStgPagePtr_Impl*)pLRUCache)->erase( pElem->nPage );
        // remove from Sorted
        pElem->pNext2->pLast2 = pElem->pLast2;
        pElem->pLast2->pNext2 = pElem->pNext2;
        if( pElem1 == pElem )
            pElem1 = ( pElem->pNext2 == pElem ) ? NULL : pElem->pNext2;
        delete pElem;
    }
}

// remove all cache elements without flushing them

void StgCache::Clear()
{
	StgPage* pElem = pCur;
	if( pCur ) do
	{
		StgPage* pDelete = pElem;
		pElem = pElem->pNext1;
		delete pDelete;
	}
    while( pCur != pElem );
    pCur = NULL;
	pElem1 = NULL;
	delete (UsrStgPagePtr_Impl*)pLRUCache;
	pLRUCache = NULL;
}

// Look for a cached page

StgPage* StgCache::Find( sal_Int32 nPage )
{
	if( !pLRUCache )
		return NULL;
	UsrStgPagePtr_Impl::iterator aIt = ((UsrStgPagePtr_Impl*)pLRUCache)->find( nPage );
	if( aIt != ((UsrStgPagePtr_Impl*)pLRUCache)->end() )
	{
		// page found
	    StgPage* pFound = (*aIt).second;
        OSL_ENSURE( pFound, "The pointer may not be NULL!" );

		if( pFound != pCur )
		{
            OSL_ENSURE( pFound->pNext1 && pFound->pLast1, "The pointers may not be NULL!" );
			// remove from LRU
			pFound->pNext1->pLast1 = pFound->pLast1;
			pFound->pLast1->pNext1 = pFound->pNext1;
			// insert to LRU
			pFound->pNext1 = pCur;
			pFound->pLast1 = pCur->pLast1;
			pFound->pNext1->pLast1 =
			pFound->pLast1->pNext1 = pFound;
		}
		return pFound;
	}
    return NULL;
}

// Load a page into the cache

StgPage* StgCache::Get( sal_Int32 nPage, sal_Bool bForce )
{
    StgPage* p = Find( nPage );
    if( !p )
    {
        p = Create( nPage );
        if( !Read( nPage, p->pData, 1 ) && bForce )
		{
			Erase( p );
			p = NULL;
            SetError( SVSTREAM_READ_ERROR );
		}
	}
    return p;
}

// Copy an existing page into a new page. Use this routine
// to duplicate an existing stream or to create new entries.
// The new page is initially marked dirty. No owner is copied.

StgPage* StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
{
    StgPage* p = Find( nNew );
    if( !p )
        p = Create( nNew );
    if( nOld >= 0 )
    {
        // old page: we must have this data!
        StgPage* q = Get( nOld, sal_True );
        if( q )
        {
            OSL_ENSURE( p->nData == q->nData, "Unexpected page size!" );
            memcpy( p->pData, q->pData, p->nData );
        }
    }
    p->SetDirty();
    return p;
}

// Flush the cache whose owner is given. NULL flushes all.

sal_Bool StgCache::Commit( StgDirEntry* )
{
    StgPage* p = pElem1;
    if( p ) do
    {
        if( p->bDirty )
        {
            sal_Bool b = Write( p->nPage, p->pData, 1 );
            if( !b )
				return sal_False;
            p->bDirty = sal_False;
        }
        p = p->pNext2;
    } while( p != pElem1 );
	pStrm->Flush();
	SetError( pStrm->GetError() );
#ifdef CHECK_DIRTY
	p = pElem1;
	if( p ) do
	{
	    if( p->bDirty )
	    {
			ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in Ordered List") ).Execute();
	        sal_Bool b = Write( p->nPage, p->pData, 1 );
	        if( !b )
				return sal_False;
	        p->bDirty = sal_False;
	    }
	    p = p->pNext2;
	} while( p != pElem1 );
	p = pElem1;
	if( p ) do
	{
	    if( p->bDirty )
	    {
			ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in LRU List") ).Execute();
	        sal_Bool b = Write( p->nPage, p->pData, 1 );
	        if( !b )
				return sal_False;
	        p->bDirty = sal_False;
	    }
	    p = p->pNext1;
	} while( p != pElem1 );
#endif
	return sal_True;
}

void StgCache::Revert( StgDirEntry* )
{}

// Set a stream

void StgCache::SetStrm( SvStream* p, sal_Bool bMy )
{
	if( pStorageStream )
	{
		pStorageStream->ReleaseRef();
		pStorageStream = NULL;
	}

	if( bMyStream )
		delete pStrm;
	pStrm = p;
	bMyStream = bMy;
}

void StgCache::SetStrm( UCBStorageStream* pStgStream )
{
	if( pStorageStream )
		pStorageStream->ReleaseRef();
	pStorageStream = pStgStream;

	if( bMyStream )
		delete pStrm;

	pStrm = NULL;

	if ( pStorageStream )
	{
		pStorageStream->AddRef();
		pStrm = pStorageStream->GetModifySvStream();
	}

	bMyStream = sal_False;
}

// Open/close the disk file

sal_Bool StgCache::Open( const String& rName, StreamMode nMode )
{
	// do not open in exclusive mode!
	if( nMode & STREAM_SHARE_DENYALL )
		nMode = ( ( nMode & ~STREAM_SHARE_DENYALL ) | STREAM_SHARE_DENYWRITE );
	SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
	// SvStream "Feature" Write Open auch erfolgreich, wenns nicht klappt
	sal_Bool bAccessDenied = sal_False;
	if( ( nMode & STREAM_WRITE ) && !pFileStrm->IsWritable() )
	{
		pFileStrm->Close();
		bAccessDenied = sal_True;
	}
	SetStrm( pFileStrm, sal_True );
	if( pFileStrm->IsOpen() )
	{
	    sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
        nPages = lcl_GetPageCount( nFileSize, nPageSize );
	    pStrm->Seek( 0L );
	}
	else
		nPages = 0;
	bFile = sal_True;
    SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : pStrm->GetError() );
    return Good();
}

void StgCache::Close()
{
	if( bFile )
	{
		((SvFileStream*) pStrm)->Close();
	    SetError( pStrm->GetError() );
	}
}

// low level I/O

sal_Bool StgCache::Read( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
{
    if( Good() )
    {
        /*  #i73846# real life: a storage may refer to a page one-behind the
            last valid page (see document attached to the issue). In that case
            (if nPage==nPages), just do nothing here and let the caller work on
            the empty zero-filled buffer. */
        if ( nPage > nPages )
			SetError( SVSTREAM_READ_ERROR );
        else if ( nPage < nPages )
		{
			sal_uLong nPos = Page2Pos( nPage );
			sal_Int32 nPg2 = ( ( nPage + nPg ) > nPages ) ? nPages - nPage : nPg;
			sal_uLong nBytes = nPg2 * nPageSize;
			// fixed address and size for the header
			if( nPage == -1 )
			{
				nPos = 0L, nBytes = 512;
				nPg2 = nPg;
			}
			if( pStrm->Tell() != nPos )
			{
				if( pStrm->Seek( nPos ) != nPos ) {
	#ifdef CHECK_DIRTY
					ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
	#endif
                }
			}
			pStrm->Read( pBuf, nBytes );
			if ( nPg != nPg2 )
				SetError( SVSTREAM_READ_ERROR );
			else
				SetError( pStrm->GetError() );
		}
    }
    return Good();
}

sal_Bool StgCache::Write( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
{
    if( Good() ) 
    {
        sal_uLong nPos = Page2Pos( nPage );
        sal_uLong nBytes = 0;
        if ( SAL_MAX_INT32 / nPg > nPageSize )
            nBytes = nPg * nPageSize;

        // fixed address and size for the header
        // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
        if( nPage == -1 )
            nPos = 0L, nBytes = 512;
        if( pStrm->Tell() != nPos )
		{
			if( pStrm->Seek( nPos ) != nPos ) {
#ifdef CHECK_DIRTY
				ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
#endif
            }
		}
		sal_uLong nRes = pStrm->Write( pBuf, nBytes );
        if( nRes != nBytes )
            SetError( SVSTREAM_WRITE_ERROR );
        else
            SetError( pStrm->GetError() );
#ifdef READ_AFTER_WRITE
		sal_uInt8 cBuf[ 512 ];
		pStrm->Flush();
		pStrm->Seek( nPos );
		sal_Bool bRes = ( pStrm->Read( cBuf, 512 ) == 512 );
		if( bRes )
			bRes = !memcmp( cBuf, pBuf, 512 );
		if( !bRes )
		{
			ErrorBox( NULL, WB_OK, String("SO2: Read after Write failed") ).Execute();
			pStrm->SetError( SVSTREAM_WRITE_ERROR );
		}
#endif
    }
    return Good();
}

// set the file size in pages

sal_Bool StgCache::SetSize( sal_Int32 n )
{
    // Add the file header
    sal_Int32 nSize = n * nPageSize + 512;
    pStrm->SetStreamSize( nSize );
    SetError( pStrm->GetError() );
	if( !nError )
	    nPages = n;
    return Good();
}

void StgCache::SetError( sal_uLong n )
{
    if( n && !nError )
        nError = n;
}

void StgCache::ResetError()
{
    nError = SVSTREAM_OK;
    pStrm->ResetError();
}

void StgCache::MoveError( StorageBase& r )
{
	if( nError != SVSTREAM_OK )
	{
		r.SetError( nError );
		ResetError();
	}
}

// Utility functions

sal_Int32 StgCache::Page2Pos( sal_Int32 nPage )
{
    if( nPage < 0 ) nPage = 0;
    return( nPage * nPageSize ) + nPageSize;
}

sal_Int32 StgCache::Pos2Page( sal_Int32 nPos )
{
    return ( ( nPos + nPageSize - 1 ) / nPageSize ) * nPageSize - 1;
}

