/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "StorageMap.h"

#if ENABLE(DOM_STORAGE)

namespace WebCore {

PassRefPtr<StorageMap> StorageMap::create(unsigned quota)
{
    return adoptRef(new StorageMap(quota));
}

StorageMap::StorageMap(unsigned quota)
    : m_iterator(m_map.end())
    , m_iteratorIndex(UINT_MAX)
    , m_quotaSize(quota)  // quota measured in bytes
    , m_currentLength(0)
{
}

PassRefPtr<StorageMap> StorageMap::copy()
{
    RefPtr<StorageMap> newMap = create(m_quotaSize);
    newMap->m_map = m_map;
    return newMap.release();
}

void StorageMap::invalidateIterator()
{
    m_iterator = m_map.end();
    m_iteratorIndex = UINT_MAX;
}

void StorageMap::setIteratorToIndex(unsigned index)
{
    // FIXME: Once we have bidirectional iterators for HashMap we can be more intelligent about this.
    // The requested index will be closest to begin(), our current iterator, or end(), and we
    // can take the shortest route.
    // Until that mechanism is available, we'll always increment our iterator from begin() or current.

    if (m_iteratorIndex == index)
        return;

    if (index < m_iteratorIndex) {
        m_iteratorIndex = 0;
        m_iterator = m_map.begin();
        ASSERT(m_iterator != m_map.end());
    }

    while (m_iteratorIndex < index) {
        ++m_iteratorIndex;
        ++m_iterator;
        ASSERT(m_iterator != m_map.end());
    }
}

unsigned StorageMap::length() const
{
    return m_map.size();
}

String StorageMap::key(unsigned index)
{
    if (index >= length())
        return String();

    setIteratorToIndex(index);
    return m_iterator->first;
}

String StorageMap::getItem(const String& key) const
{
    return m_map.get(key);
}

PassRefPtr<StorageMap> StorageMap::setItem(const String& key, const String& value, String& oldValue, bool& quotaException)
{
    ASSERT(!value.isNull());
    quotaException = false;

    // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
    // so if more than one Storage object refs this map, copy it before mutating it.
    if (refCount() > 1) {
        RefPtr<StorageMap> newStorageMap = copy();
        newStorageMap->setItem(key, value, oldValue, quotaException);
        return newStorageMap.release();
    }

    // Quota tracking.  If the quota is enabled and this would go over it, bail.
    oldValue = m_map.get(key);
    unsigned newLength = m_currentLength + value.length() - oldValue.length();
    bool overQuota = newLength > m_quotaSize / sizeof(UChar);
    bool overflow = (newLength > m_currentLength) != (value.length() > oldValue.length());
    ASSERT(!overflow);  // If we're debugging, make a fuss.  But it's still worth checking this in the following if statement.
    if (m_quotaSize != noQuota && (overflow || overQuota)) {
        quotaException = true;
        return 0;
    }
    m_currentLength = newLength;

    pair<HashMap<String, String>::iterator, bool> addResult = m_map.add(key, value);
    if (!addResult.second)
        addResult.first->second = value;

    invalidateIterator();

    return 0;
}

PassRefPtr<StorageMap> StorageMap::removeItem(const String& key, String& oldValue)
{
    // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
    // so if more than one Storage object refs this map, copy it before mutating it.
    if (refCount() > 1) {
        RefPtr<StorageMap> newStorage = copy();
        newStorage->removeItem(key, oldValue);
        return newStorage.release();
    }

    oldValue = m_map.take(key);
    if (!oldValue.isNull())
        invalidateIterator();

    // Update quota.
    ASSERT(m_currentLength - oldValue.length() <= m_currentLength);
    m_currentLength -= oldValue.length();

    return 0;
}

bool StorageMap::contains(const String& key) const
{
    return m_map.contains(key);
}

void StorageMap::importItem(const String& key, const String& value)
{
    // Be sure to copy the keys/values as items imported on a background thread are destined
    // to cross a thread boundary
    pair<HashMap<String, String>::iterator, bool> result = m_map.add(key.threadsafeCopy(), String());

    if (result.second)
        result.first->second = value.threadsafeCopy();

    // Update quota.
    ASSERT(m_currentLength + value.length() >= m_currentLength);
    m_currentLength += value.length();
}

}

#endif // ENABLE(DOM_STORAGE)
