/**************************************************************
 * 
 * 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_store.hxx"

#include "storlckb.hxx"

#include "sal/types.h"
#include "sal/macros.h"
#include "rtl/string.h"
#include "rtl/ref.hxx"
#include "osl/mutex.hxx"

#include "store/types.h"
#include "object.hxx"

#include "storbase.hxx"
#include "stordata.hxx"
#include "storpage.hxx"

using namespace store;

/*========================================================================
 *
 * OStoreLockBytes implementation.
 *
 *======================================================================*/
const sal_uInt32 OStoreLockBytes::m_nTypeId = sal_uInt32(0x94190310);

/*
 * OStoreLockBytes.
 */
OStoreLockBytes::OStoreLockBytes (void)
	: m_xManager   (),
	  m_xNode      (),
	  m_bWriteable (false)
{
}

/*
 * ~OStoreLockBytes.
 */
OStoreLockBytes::~OStoreLockBytes (void)
{
	if (m_xManager.is())
	{
		if (m_xNode.is())
		{
			OStorePageDescriptor aDescr (m_xNode->m_aDescr);
			if (m_bWriteable)
                m_xManager->releasePage (aDescr, store_AccessReadWrite);
			else
                m_xManager->releasePage (aDescr, store_AccessReadOnly);
		}
	}
}

/*
 * isKindOf.
 */
sal_Bool SAL_CALL OStoreLockBytes::isKindOf (sal_uInt32 nTypeId)
{
	return (nTypeId == m_nTypeId);
}

/*
 * create.
 */
storeError OStoreLockBytes::create (
	OStorePageManager *pManager,
	rtl_String        *pPath,
	rtl_String        *pName,
	storeAccessMode    eMode)
{
	rtl::Reference<OStorePageManager> xManager (pManager);
	if (!xManager.is())
		return store_E_InvalidAccess;

	if (!(pPath && pName))
		return store_E_InvalidParameter;

	OStoreDirectoryPageObject aPage;
	storeError eErrCode = xManager->iget (
		aPage, STORE_ATTRIB_ISFILE,
		pPath, pName, eMode);
	if (eErrCode != store_E_None)
		return eErrCode;

	if (!(aPage.attrib() & STORE_ATTRIB_ISFILE))
	{
		// No ISFILE in older versions (backward compatibility).
		if (aPage.attrib() & STORE_ATTRIB_ISLINK)
			return store_E_NotFile;
	}

	// ...
    inode_holder_type xNode (aPage.get());
	if (eMode != store_AccessReadOnly)
		eErrCode = xManager->acquirePage (xNode->m_aDescr, store_AccessReadWrite);
	else
		eErrCode = xManager->acquirePage (xNode->m_aDescr, store_AccessReadOnly);
	if (eErrCode != store_E_None)
		return eErrCode;

	// ...
	m_xManager   = xManager;
	m_xNode      = xNode;
	m_bWriteable = (eMode != store_AccessReadOnly);

	// Check for truncation.
	if (eMode == store_AccessCreate)
	{
		// Truncate to zero length.
		eErrCode = setSize(0);
	}
	return eErrCode;
}

/*
 * readAt.
 */
storeError OStoreLockBytes::readAt (
	sal_uInt32  nOffset,
	void       *pBuffer,
	sal_uInt32  nBytes,
	sal_uInt32 &rnDone)
{
	rnDone = 0;

	if (!m_xManager.is())
		return store_E_InvalidAccess;

	if (!pBuffer)
		return store_E_InvalidParameter;
	if (!nBytes)
		return store_E_None;

	// Acquire exclusive access.
	osl::MutexGuard aGuard (*m_xManager);

	// Determine data length.
	OStoreDirectoryPageObject aPage (m_xNode.get());

	sal_uInt32 nDataLen = aPage.dataLength();
	if ((nOffset + nBytes) > nDataLen)
		nBytes = nDataLen - nOffset;

	// Read data.
    OStoreDataPageObject aData;
	sal_uInt8 *pData = (sal_uInt8*)pBuffer;
	while ((0 < nBytes) && (nOffset < nDataLen))
	{
		// Determine 'Offset' scope.
		inode::ChunkScope eScope = m_xNode->scope (nOffset);
		if (eScope == inode::SCOPE_INTERNAL)
		{
			// Read from inode page (internal scope).
			inode::ChunkDescriptor aDescr (
				nOffset, m_xNode->capacity());

			sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
			nLength = SAL_MIN(nLength, nBytes);

			memcpy (
				&pData[rnDone],
				&m_xNode->m_pData[aDescr.m_nOffset],
				nLength);

			// Adjust counters.
			rnDone  += nLength;
			nOffset += nLength;
			nBytes  -= nLength;
		}
		else
		{
			// Read from data page (external scope).
			inode::ChunkDescriptor aDescr (
				nOffset - m_xNode->capacity(), OStoreDataPageData::capacity(m_xNode->m_aDescr)); // @@@

			sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
			nLength = SAL_MIN(nLength, nBytes);

			storeError eErrCode = aPage.read (aDescr.m_nPage, aData, *m_xManager);
			if (eErrCode != store_E_None)
			{
				if (eErrCode != store_E_NotExists)
					return eErrCode;

				memset (
					&pData[rnDone],
                    0,
					nLength);
			}
			else
			{
                PageHolderObject< data > xData (aData.makeHolder<data>());
				memcpy (
					&pData[rnDone],
					&xData->m_pData[aDescr.m_nOffset],
					nLength);
			}

			// Adjust counters.
			rnDone  += nLength;
			nOffset += nLength;
			nBytes  -= nLength;
		}
	}

	// Done.
	return store_E_None;
}

/*
 * writeAt.
 */
storeError OStoreLockBytes::writeAt (
	sal_uInt32  nOffset,
	const void *pBuffer,
	sal_uInt32  nBytes,
	sal_uInt32 &rnDone)
{
	rnDone = 0;

	if (!m_xManager.is())
		return store_E_InvalidAccess;
	if (!m_bWriteable)
		return store_E_AccessViolation;

	if (!pBuffer)
		return store_E_InvalidParameter;
	if (!nBytes)
		return store_E_None;

	// Acquire exclusive access.
	osl::MutexGuard aGuard (*m_xManager);

	// Write data.
	OStoreDirectoryPageObject aPage (m_xNode.get());
	const sal_uInt8 *pData = (const sal_uInt8*)pBuffer;

	storeError eErrCode = store_E_None;
	while (nBytes > 0)
	{
		// Determine 'Offset' scope.
		inode::ChunkScope eScope = m_xNode->scope (nOffset);
		if (eScope == inode::SCOPE_INTERNAL)
		{
			// Write to inode page (internal scope).
			inode::ChunkDescriptor aDescr (
				nOffset, m_xNode->capacity());

			sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
			nLength = SAL_MIN(nLength, nBytes);

			memcpy (
				&m_xNode->m_pData[aDescr.m_nOffset],
				&pData[rnDone], nLength);

			// Mark inode dirty.
			aPage.touch();

			// Adjust counters.
			rnDone  += nLength;
			nOffset += nLength;
			nBytes  -= nLength;

			// Adjust data length.
			if (aPage.dataLength() < nOffset)
				aPage.dataLength (nOffset);
		}
		else
		{
			// Write to data page (external scope).
			OStoreDataPageObject aData;

			inode::ChunkDescriptor aDescr (
				nOffset - m_xNode->capacity(), OStoreDataPageData::capacity(m_xNode->m_aDescr)); // @@@

			sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
			if ((aDescr.m_nOffset > 0) || (nBytes < nLength))
			{
				// Unaligned. Need to load/create data page.
// @@@ loadOrCreate()
				eErrCode = aPage.read (aDescr.m_nPage, aData, *m_xManager);
				if (eErrCode != store_E_None)
				{
					if (eErrCode != store_E_NotExists)
						return eErrCode;

                    eErrCode = aData.construct<data>(m_xManager->allocator());
                    if (eErrCode != store_E_None)
						return eErrCode;
				}
			}

            PageHolderObject< data > xData (aData.makeHolder<data>());
            if (!xData.is())
            {
                eErrCode = aData.construct<data>(m_xManager->allocator());
                if (eErrCode != store_E_None)
                    return eErrCode;
                xData = aData.makeHolder<data>();
            }

			// Modify data page.
			nLength = SAL_MIN(nLength, nBytes);
			memcpy (
				&xData->m_pData[aDescr.m_nOffset],
				&pData[rnDone], nLength);

			// Save data page.
			eErrCode = aPage.write (aDescr.m_nPage, aData, *m_xManager);
			if (eErrCode != store_E_None)
				return eErrCode;

			// Adjust counters.
			rnDone  += nLength;
			nOffset += nLength;
			nBytes  -= nLength;

			// Adjust data length.
			if (aPage.dataLength() < nOffset)
				aPage.dataLength (nOffset);
		}
	}

	// Check for modified inode.
	if (aPage.dirty())
		return m_xManager->saveObjectAt (aPage, aPage.location());
	else
		return store_E_None;
}

/*
 * flush.
 */
storeError OStoreLockBytes::flush (void)
{
	if (!m_xManager.is())
		return store_E_InvalidAccess;

	return m_xManager->flush();
}

/*
 * setSize.
 */
storeError OStoreLockBytes::setSize (sal_uInt32 nSize)
{
	if (!m_xManager.is())
		return store_E_InvalidAccess;
	if (!m_bWriteable)
		return store_E_AccessViolation;

	// Acquire exclusive access.
	osl::MutexGuard aGuard (*m_xManager);

	// Determine current length.
	OStoreDirectoryPageObject aPage (m_xNode.get());
	sal_uInt32 nDataLen = aPage.dataLength();

	if (nSize == nDataLen)
		return store_E_None;

	if (nSize < nDataLen)
	{
		// Truncate.
		storeError eErrCode = store_E_None;

		// Determine 'Size' scope.
		inode::ChunkScope eSizeScope = m_xNode->scope (nSize);
		if (eSizeScope == inode::SCOPE_INTERNAL)
		{
			// Internal 'Size' scope. Determine 'Data' scope.
			inode::ChunkScope eDataScope = m_xNode->scope (nDataLen);
			if (eDataScope == inode::SCOPE_EXTERNAL)
			{
				// External 'Data' scope. Truncate all external data pages.
				eErrCode = aPage.truncate (0, *m_xManager);
				if (eErrCode != store_E_None)
					return eErrCode;
			}

			// Truncate internal data page.
			inode::ChunkDescriptor aDescr (nSize, m_xNode->capacity());
			memset (
				&(m_xNode->m_pData[aDescr.m_nOffset]),
				0, aDescr.m_nLength);
		}
		else
		{
			// External 'Size' scope. Truncate external data pages.
			inode::ChunkDescriptor aDescr (
				nSize - m_xNode->capacity(), OStoreDataPageData::capacity(m_xNode->m_aDescr)); // @@@

			sal_uInt32 nPage = aDescr.m_nPage;
			if (aDescr.m_nOffset) nPage += 1;

			eErrCode = aPage.truncate (nPage, *m_xManager);
			if (eErrCode != store_E_None)
				return eErrCode;
		}
	}

	// Set (extended or truncated) size.
	aPage.dataLength (nSize);

	// Save modified inode.
	return m_xManager->saveObjectAt (aPage, aPage.location());
}

/*
 * stat.
 */
storeError OStoreLockBytes::stat (sal_uInt32 &rnSize)
{
	rnSize = 0;

	if (!m_xManager.is())
		return store_E_InvalidAccess;

	OStoreDirectoryPageObject aPage (m_xNode.get());
	rnSize = aPage.dataLength();
	return store_E_None;
}
