/*
 * Copyright (C) 2011 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. AND ITS CONTRIBUTORS ``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 ITS 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 "WebPluginSiteDataManager.h"

#include "ImmutableArray.h"
#include "PluginProcessManager.h"
#include "WebContext.h"
#include "WebProcessMessages.h"

using namespace WebCore;

namespace WebKit {

#if ENABLE(PLUGIN_PROCESS)
class WebPluginSiteDataManager::GetSitesWithDataState {
public:
    explicit GetSitesWithDataState(WebPluginSiteDataManager* webPluginSiteDataManager, uint64_t callbackID)
        : m_webPluginSiteDataManager(webPluginSiteDataManager)
        , m_callbackID(callbackID)
        , m_plugins(webPluginSiteDataManager->m_webContext->pluginInfoStore().plugins())
    {
    }

    void getSitesWithDataForNextPlugin()
    {
        if (m_plugins.isEmpty()) {
            Vector<String> sites;
            copyToVector(m_sites, sites);

            m_webPluginSiteDataManager->didGetSitesWithDataForAllPlugins(sites, m_callbackID);
            return;
        }

        PluginProcessManager::shared().getSitesWithData(m_plugins.last(), m_webPluginSiteDataManager, m_callbackID);
        m_plugins.removeLast();
    }

    void didGetSitesWithDataForSinglePlugin(const Vector<String>& sites)
    {
        for (size_t i = 0; i < sites.size(); ++i)
            m_sites.add(sites[i]);

        getSitesWithDataForNextPlugin();
    }
    
private:
    WebPluginSiteDataManager* m_webPluginSiteDataManager;
    uint64_t m_callbackID;
    Vector<PluginModuleInfo> m_plugins;
    HashSet<String> m_sites;
};

class WebPluginSiteDataManager::ClearSiteDataState {
public:
    explicit ClearSiteDataState(WebPluginSiteDataManager* webPluginSiteDataManager, const Vector<String>& sites, uint64_t flags, uint64_t maxAgeInSeconds, uint64_t callbackID)
        : m_webPluginSiteDataManager(webPluginSiteDataManager)
        , m_sites(sites)
        , m_flags(flags)
        , m_maxAgeInSeconds(maxAgeInSeconds)
        , m_callbackID(callbackID)
        , m_plugins(webPluginSiteDataManager->m_webContext->pluginInfoStore().plugins())
    {
    }

    void clearSiteDataForNextPlugin()
    {
        if (m_plugins.isEmpty()) {
            m_webPluginSiteDataManager->didClearSiteDataForAllPlugins(m_callbackID);
            return;
        }

        PluginProcessManager::shared().clearSiteData(m_plugins.last(), m_webPluginSiteDataManager, m_sites, m_flags, m_maxAgeInSeconds, m_callbackID);
        m_plugins.removeLast();
    }

    void didClearSiteDataForSinglePlugin()
    {
        clearSiteDataForNextPlugin();
    }
    
private:
    WebPluginSiteDataManager* m_webPluginSiteDataManager;
    Vector<String> m_sites;
    uint64_t m_flags;
    uint64_t m_maxAgeInSeconds;
    uint64_t m_callbackID;
    Vector<PluginModuleInfo> m_plugins;
};
#endif // ENABLE(PLUGIN_PROCESS)

PassRefPtr<WebPluginSiteDataManager> WebPluginSiteDataManager::create(WebContext* webContext)
{
    return adoptRef(new WebPluginSiteDataManager(webContext));
}

WebPluginSiteDataManager::WebPluginSiteDataManager(WebContext* webContext)
    : m_webContext(webContext)
{
}

WebPluginSiteDataManager::~WebPluginSiteDataManager()
{
    ASSERT(m_arrayCallbacks.isEmpty());
    ASSERT(m_voidCallbacks.isEmpty());
#if ENABLE(PLUGIN_PROCESS)
    ASSERT(m_pendingGetSitesWithData.isEmpty());
    ASSERT(m_pendingClearSiteData.isEmpty());
#endif
}

void WebPluginSiteDataManager::invalidate()
{
    invalidateCallbackMap(m_arrayCallbacks);

#if ENABLE(PLUGIN_PROCESS)
    deleteAllValues(m_pendingGetSitesWithData);
    m_pendingGetSitesWithData.clear();
    deleteAllValues(m_pendingClearSiteData);
    m_pendingClearSiteData.clear();
#endif
}

void WebPluginSiteDataManager::getSitesWithData(PassRefPtr<ArrayCallback> prpCallback)
{
    RefPtr<ArrayCallback> callback = prpCallback;

    if (!m_webContext) {
        callback->invalidate();
        return;
    }

    uint64_t callbackID = callback->callbackID();
    m_arrayCallbacks.set(callbackID, callback.release());

#if ENABLE(PLUGIN_PROCESS)
    ASSERT(!m_pendingGetSitesWithData.contains(callbackID));

    GetSitesWithDataState* state = new GetSitesWithDataState(this, callbackID);
    m_pendingGetSitesWithData.set(callbackID, state);
    state->getSitesWithDataForNextPlugin();
#else
    m_webContext->relaunchProcessIfNecessary();

    Vector<PluginModuleInfo> plugins = m_webContext->pluginInfoStore().plugins();
    Vector<String> pluginPaths;
    for (size_t i = 0; i < plugins.size(); ++i)
        pluginPaths.append(plugins[i].path);

    // FIXME (Multi-WebProcess): When multi-process is enabled, we must always use a plug-in process for this,
    // so this code should just be removed.
    m_webContext->sendToAllProcessesRelaunchingThemIfNecessary(Messages::WebProcess::GetSitesWithPluginData(pluginPaths, callbackID));
#endif
}

void WebPluginSiteDataManager::didGetSitesWithData(const Vector<String>& sites, uint64_t callbackID)
{
    RefPtr<ArrayCallback> callback = m_arrayCallbacks.take(callbackID);
    if (!callback) {
        // FIXME: Log error or assert.
        return;
    }

    Vector<RefPtr<APIObject> > sitesWK(sites.size());

    for (size_t i = 0; i < sites.size(); ++i)
        sitesWK[i] = WebString::create(sites[i]);

    RefPtr<ImmutableArray> resultArray = ImmutableArray::adopt(sitesWK);
    callback->performCallbackWithReturnValue(resultArray.get());
}

void WebPluginSiteDataManager::clearSiteData(ImmutableArray* sites, uint64_t flags, uint64_t maxAgeInSeconds, PassRefPtr<VoidCallback> prpCallback)
{
    RefPtr<VoidCallback> callback = prpCallback;
    if (!m_webContext) {
        callback->invalidate();
        return;
    }

    Vector<String> sitesVector;

    // If the array is empty, don't do anything.
    if (sites) {
        if (!sites->size()) {
            callback->performCallback();
            return;
        }

        for (size_t i = 0; i < sites->size(); ++i) {
            if (WebString* site = sites->at<WebString>(i))
                sitesVector.append(site->string());
        }
    }

    uint64_t callbackID = callback->callbackID();
    m_voidCallbacks.set(callbackID, callback.release());

#if ENABLE(PLUGIN_PROCESS)
    ASSERT(!m_pendingClearSiteData.contains(callbackID));

    ClearSiteDataState* state = new ClearSiteDataState(this, sitesVector, flags, maxAgeInSeconds, callbackID);
    m_pendingClearSiteData.set(callbackID, state);
    state->clearSiteDataForNextPlugin();
#else
    m_webContext->relaunchProcessIfNecessary();

    Vector<PluginModuleInfo> plugins = m_webContext->pluginInfoStore().plugins();
    Vector<String> pluginPaths;
    for (size_t i = 0; i < plugins.size(); ++i)
        pluginPaths.append(plugins[i].path);

    // FIXME (Multi-WebProcess): When multi-process is enabled, we must always use a plug-in process for this,
    // so this code should just be removed.
    m_webContext->sendToAllProcessesRelaunchingThemIfNecessary(Messages::WebProcess::ClearPluginSiteData(pluginPaths, sitesVector, flags, maxAgeInSeconds, callbackID));
#endif
}

void WebPluginSiteDataManager::didClearSiteData(uint64_t callbackID)
{
    RefPtr<VoidCallback> callback = m_voidCallbacks.take(callbackID);
    if (!callback) {
        // FIXME: Log error or assert.
        return;
    }

    callback->performCallback();
}

bool WebPluginSiteDataManager::shouldTerminate(WebProcessProxy*) const
{
#if ENABLE(PLUGIN_PROCESS)
    // When out of process plug-ins are enabled, the web process is not involved in fetching site data.
    return true;
#else
    return m_arrayCallbacks.isEmpty() && m_voidCallbacks.isEmpty();
#endif
}

#if ENABLE(PLUGIN_PROCESS)
void WebPluginSiteDataManager::didGetSitesWithDataForSinglePlugin(const Vector<String>& sites, uint64_t callbackID)
{
    GetSitesWithDataState* state = m_pendingGetSitesWithData.get(callbackID);
    ASSERT(state);

    state->didGetSitesWithDataForSinglePlugin(sites);
}

void WebPluginSiteDataManager::didGetSitesWithDataForAllPlugins(const Vector<String>& sites, uint64_t callbackID)
{
    OwnPtr<GetSitesWithDataState> state = adoptPtr(m_pendingGetSitesWithData.take(callbackID));
    ASSERT(state);

    didGetSitesWithData(sites, callbackID);
}

void WebPluginSiteDataManager::didClearSiteDataForSinglePlugin(uint64_t callbackID)
{
    ClearSiteDataState* state = m_pendingClearSiteData.get(callbackID);
    ASSERT(state);
    
    state->didClearSiteDataForSinglePlugin();
}

void WebPluginSiteDataManager::didClearSiteDataForAllPlugins(uint64_t callbackID)
{
    OwnPtr<ClearSiteDataState> state = adoptPtr(m_pendingClearSiteData.take(callbackID));
    ASSERT(state);

    didClearSiteData(callbackID);
}

#endif

} // namespace WebKit

