/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include "storpage.hxx"

#include <sal/types.h>
#include <sal/log.hxx>
#include <rtl/string.h>
#include <osl/mutex.hxx>

#include <store/types.h>

#include "storbase.hxx"
#include "stordata.hxx"
#include "stortree.hxx"

using namespace store;

const sal_uInt32 OStorePageManager::m_nTypeId = sal_uInt32(0x62190120);

OStorePageManager::OStorePageManager()
{
}

OStorePageManager::~OStorePageManager()
{
}

bool OStorePageManager::isKindOf (sal_uInt32 nTypeId)
{
    return (nTypeId == m_nTypeId);
}

/*
 * initialize (two-phase construction).
 * Precond: none.
 */
storeError OStorePageManager::initialize (
    ILockBytes *    pLockBytes,
    storeAccessMode eAccessMode,
    sal_uInt16 &    rnPageSize)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check arguments.
    if (!pLockBytes)
        return store_E_InvalidParameter;

    // Initialize base.
    storeError eErrCode = base::initialize (pLockBytes, eAccessMode, rnPageSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check for (not) writeable.
    if (!base::isWriteable())
    {
        // Readonly. Load RootNode.
        return base::loadObjectAt (m_aRoot, rnPageSize);
    }

    // Writeable. Load or Create RootNode.
    eErrCode = m_aRoot.loadOrCreate (rnPageSize, *this);
    if (eErrCode == store_E_Pending)
    {
        // Creation notification.
        PageHolderObject< page > xRoot (m_aRoot.get());

        // Pre-allocate left most entry (ugly, but we can't insert to left).
        OStorePageKey aKey (rtl_crc32 (0, "/", 1), 0);
        xRoot->insert (0, entry(aKey));

        // Save RootNode.
        eErrCode = base::saveObjectAt (m_aRoot, rnPageSize);
    }

    // Done.
    return eErrCode;
}

/*
 * find_lookup (w/o split()).
 * Internal: Precond: initialized, readable, exclusive access.
 */
storeError OStorePageManager::find_lookup (
    OStoreBTreeNodeObject & rNode,
    sal_uInt16 &            rIndex,
    OStorePageKey const &   rKey)
{
    // Find Node and Index.
    storeError eErrCode = m_aRoot.find_lookup (rNode, rIndex, rKey, *this);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Greater or Equal.
    PageHolderObject< page > xPage (rNode.get());
    SAL_WARN_IF(rIndex >= xPage->usageCount(), "store", "store::PageManager::find_lookup(): logic error");
    entry e (xPage->m_pData[rIndex]);

    // Check for exact match.
    if (e.compare(entry(rKey)) != entry::COMPARE_EQUAL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check address.
    if (e.m_aLink.location() == STORE_PAGE_NULL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    return store_E_None;
}

/*
 * remove_Impl (possibly down from root).
 * Internal: Precond: initialized, writeable, exclusive access.
 */

storeError OStorePageManager::remove_Impl (entry & rEntry)
{
    OStoreBTreeNodeObject aNode (m_aRoot.get());

    // Check current page index.
    PageHolderObject< page > xPage (aNode.get());
    sal_uInt16 i = xPage->find (rEntry), n = xPage->usageCount();
    if (i >= n)
    {
        // Path to entry not exists (Must not happen(?)).
        return store_E_NotExists;
    }

    // Compare entry.
    entry::CompareResult result = rEntry.compare (xPage->m_pData[i]);

    // Iterate down until equal match.
    while ((result == entry::COMPARE_GREATER) && (xPage->depth() > 0))
    {
        // Check link address.
        sal_uInt32 const nAddr = xPage->m_pData[i].m_aLink.location();
        if (nAddr == STORE_PAGE_NULL)
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Load link page.
        storeError eErrCode = loadObjectAt (aNode, nAddr);
        if (eErrCode != store_E_None)
            return eErrCode;

        PageHolderObject< page > xNext (aNode.get());
        xNext.swap (xPage);

        // Check index.
        i = xPage->find (rEntry);
        n = xPage->usageCount();
        if (i >= n)
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Compare entry.
        result = rEntry.compare (xPage->m_pData[i]);
    }

    SAL_WARN_IF(
        result == entry::COMPARE_LESS,
        "store",
        "OStorePageManager::remove(): find failed");

    // Check entry comparison.
    if (result == entry::COMPARE_LESS)
    {
        // Must not happen.
        return store_E_Unknown;
    }

    // Remove down from current page (recursive).
    return aNode.remove (i, rEntry, *this);
}

/*
 * namei.
 * Precond: none (static).
 */
storeError OStorePageManager::namei (
    const rtl_String *pPath, const rtl_String *pName, OStorePageKey &rKey)
{
    // Check parameter.
    if (!(pPath && pName))
        return store_E_InvalidParameter;

    // Check name length.
    if (pName->length >= STORE_MAXIMUM_NAMESIZE)
        return store_E_NameTooLong;

    // Transform pathname into key.
    rKey.m_nLow  = store::htonl(rtl_crc32 (0, pName->buffer, pName->length));
    rKey.m_nHigh = store::htonl(rtl_crc32 (0, pPath->buffer, pPath->length));

    // Done.
    return store_E_None;
}

/*
 * iget.
 * Precond: initialized.
 */
storeError OStorePageManager::iget (
    OStoreDirectoryPageObject & rPage,
    sal_uInt32                  nAttrib,
    const rtl_String          * pPath,
    const rtl_String          * pName,
    storeAccessMode             eMode)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Setup inode page key.
    OStorePageKey aKey;
    storeError eErrCode = namei (pPath, pName, aKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check for directory.
    if (nAttrib & STORE_ATTRIB_ISDIR)
    {
        // Ugly, but necessary (backward compatibility).
        aKey.m_nLow = store::htonl(rtl_crc32 (store::ntohl(aKey.m_nLow), "/", 1));
    }

    // Load inode page.
    eErrCode = load_dirpage_Impl (aKey, rPage);
    if (eErrCode != store_E_None)
    {
        // Check mode and reason.
        if (eErrCode != store_E_NotExists)
            return eErrCode;

        if (eMode == storeAccessMode::ReadWrite)
            return store_E_NotExists;
        if (eMode == storeAccessMode::ReadOnly)
            return store_E_NotExists;

        if (!base::isWriteable())
            return store_E_AccessViolation;

        // Create inode page.
        eErrCode = rPage.construct< inode >(base::allocator());
        if (eErrCode != store_E_None)
            return eErrCode;

        // Setup inode nameblock.
        PageHolderObject< inode > xPage (rPage.get());

        rPage.key (aKey);
        rPage.attrib (nAttrib);

        memcpy (
            &(xPage->m_aNameBlock.m_pData[0]),
            pName->buffer, pName->length);

        // Save inode page.
        eErrCode = save_dirpage_Impl (aKey, rPage);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Check for symbolic link.
    if (rPage.attrib() & STORE_ATTRIB_ISLINK)
    {
        // Obtain 'Destination' page key.
        PageHolderObject< inode > xPage (rPage.get());
        OStorePageKey aDstKey;
        memcpy (&aDstKey, &(xPage->m_pData[0]), sizeof(aDstKey));

        // Load 'Destination' inode.
        eErrCode = load_dirpage_Impl (aDstKey, rPage);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Done.
    return store_E_None;
}

/*
 * iterate.
 * Precond: initialized.
 * ToDo: skip hardlink entries.
 */
storeError OStorePageManager::iterate (
    OStorePageKey &  rKey,
    OStorePageLink & rLink,
    sal_uInt32 &     rAttrib)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Find NodePage and Index.
    OStoreBTreeNodeObject aNode;
    sal_uInt16 i = 0;
    storeError eErrCode = m_aRoot.find_lookup (aNode, i, rKey, *this);
    if (eErrCode != store_E_None)
        return eErrCode;

    // GreaterEqual. Found next entry.
    PageHolderObject< page > xNode (aNode.get());
    entry e (xNode->m_pData[i]);

    // Setup result.
    rKey    = e.m_aKey;
    rLink   = e.m_aLink;
    rAttrib = store::ntohl(e.m_nAttrib);

    // Done.
    return store_E_None;
}

/*
 * load => private: iget() @@@
 * Internal: Precond: initialized, exclusive access.
 */
storeError OStorePageManager::load_dirpage_Impl (
    const OStorePageKey       &rKey,
    OStoreDirectoryPageObject &rPage)
{
    // Find Node and Index.
    OStoreBTreeNodeObject aNode;
    sal_uInt16 i = 0;
    storeError eErrCode = find_lookup (aNode, i, rKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Existing entry. Load page.
    PageHolderObject< page > xNode (aNode.get());
    entry e (xNode->m_pData[i]);
    return loadObjectAt (rPage, e.m_aLink.location());
}

/*
 * save => private: iget(), rebuild() @@@
 * Internal: Precond: initialized, writeable, exclusive access.
 */
storeError OStorePageManager::save_dirpage_Impl (
    const OStorePageKey       &rKey,
    OStoreDirectoryPageObject &rPage)
{
    // Find NodePage and Index.
    node aNode;
    sal_uInt16 i = 0;

    storeError eErrCode = m_aRoot.find_insert (aNode, i, rKey, *this);
    PageHolderObject< page > xNode (aNode.get());
    if (eErrCode != store_E_None)
    {
        if (eErrCode != store_E_AlreadyExists)
            return eErrCode;

        // Existing entry.
        entry e (xNode->m_pData[i]);
        if (e.m_aLink.location() != STORE_PAGE_NULL)
        {
            // Save page to existing location.
            return saveObjectAt (rPage, e.m_aLink.location());
        }

        // Allocate page.
        eErrCode = base::allocate (rPage);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Update page location.
        xNode->m_pData[i].m_aLink = rPage.location();

        // Save modified NodePage.
        return saveObjectAt (aNode, aNode.location());
    }

    // Allocate page.
    eErrCode = base::allocate (rPage);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Insert.
    OStorePageLink aLink (rPage.location());
    xNode->insert (i + 1, entry (rKey, aLink));

    // Save modified NodePage.
    return saveObjectAt (aNode, aNode.location());
}

/*
 * remove.
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::remove (const OStorePageKey &rKey)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Find NodePage and index.
    OStoreBTreeNodeObject aNodePage;
    sal_uInt16 i = 0;
    storeError eErrCode = find_lookup (aNodePage, i, rKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Existing entry.
    PageHolderObject< page > xNodePage (aNodePage.get());
    entry e (xNodePage->m_pData[i]);

    // Check for (not a) hardlink.
    if (!(store::ntohl(e.m_nAttrib) & STORE_ATTRIB_ISLINK))
    {
        // Load directory page.
        OStoreDirectoryPageObject aPage;
        eErrCode = base::loadObjectAt (aPage, e.m_aLink.location());
        if (eErrCode != store_E_None)
            return eErrCode;

        inode_holder_type xNode (aPage.get());

        // Acquire page write access.
        OStorePageDescriptor aDescr (xNode->m_aDescr);
        eErrCode = base::acquirePage (aDescr, storeAccessMode::ReadWrite);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Check for symbolic link.
        if (!(aPage.attrib() & STORE_ATTRIB_ISLINK))
        {
            // Ordinary inode. Determine 'Data' scope.
            inode::ChunkScope eScope = xNode->scope (aPage.dataLength());
            if (eScope == inode::SCOPE_EXTERNAL)
            {
                // External 'Data' scope. Truncate all external data pages.
                eErrCode = aPage.truncate (0, *this);
                if (eErrCode != store_E_None)
                    return eErrCode;
            }

            // Truncate internal data page.
            memset (&(xNode->m_pData[0]), 0, xNode->capacity());
            aPage.dataLength (0);
        }

        // Release page write access.
        base::releasePage (aDescr);

        // Release and free directory page.
        (void)base::free (aPage.location());
    }

    // Remove entry.
    return remove_Impl (e);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */