/*
 * Copyright (C) 2013 Google 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 "FontLoader.h"

#if ENABLE(FONT_LOAD_EVENTS)

#include "CSSFontFaceLoadEvent.h"
#include "CSSFontFaceSource.h"
#include "CSSFontSelector.h"
#include "CSSParser.h"
#include "CSSSegmentedFontFace.h"
#include "Dictionary.h"
#include "Document.h"
#include "ExceptionCodeDescription.h"
#include "FontCascade.h"
#include "FrameView.h"
#include "StyleProperties.h"
#include "StyleResolver.h"

namespace WebCore {

static const int defaultFontSize = 10;
static const char* const defaultFontFamily = "sans-serif";

class LoadFontCallback : public CSSSegmentedFontFace::LoadFontCallback {
public:
    static Ref<LoadFontCallback> create(int numLoading, FontLoader& fontLoader, PassRefPtr<VoidCallback> loadCallback, PassRefPtr<VoidCallback> errorCallback)
    {
        return adoptRef(*new LoadFontCallback(numLoading, fontLoader, loadCallback, errorCallback));
    }

    static PassRefPtr<LoadFontCallback> createFromParams(const Dictionary& params, FontLoader& fontLoader, const FontCascade& font)
    {
        RefPtr<VoidCallback> onsuccess;
        RefPtr<VoidCallback> onerror;
        params.get("onsuccess", onsuccess);
        params.get("onerror", onerror);
        if (!onsuccess && !onerror)
            return 0;
        int numFamilies = font.familyCount();
        return LoadFontCallback::create(numFamilies, fontLoader, onsuccess, onerror);
    }

    virtual void notifyLoaded() override;
    virtual void notifyError() override;
    virtual ~LoadFontCallback() { };

    int familyCount() const { return m_familyCount; }

private:
    LoadFontCallback(int numLoading, FontLoader& fontLoader, PassRefPtr<VoidCallback> loadCallback, PassRefPtr<VoidCallback> errorCallback)
        : m_familyCount(numLoading)
        , m_numLoading(numLoading)
        , m_errorOccured(false)
        , m_fontLoader(fontLoader)
        , m_loadCallback(loadCallback)
        , m_errorCallback(errorCallback)
    { }

    int m_familyCount;
    int m_numLoading;
    bool m_errorOccured;
    FontLoader& m_fontLoader;
    RefPtr<VoidCallback> m_loadCallback;
    RefPtr<VoidCallback> m_errorCallback;
};

void LoadFontCallback::notifyLoaded()
{
    m_numLoading--;
    if (m_numLoading)
        return;

    m_fontLoader.loadFontDone(*this);

    if (m_errorOccured) {
        if (m_errorCallback)
            m_errorCallback->handleEvent();
    } else {
        if (m_loadCallback)
            m_loadCallback->handleEvent();
    }
}

void LoadFontCallback::notifyError() 
{
    m_errorOccured = true;
    notifyLoaded();
}

void FontLoader::loadFontDone(const LoadFontCallback& callback)
{
    m_numLoadingFromJS -= callback.familyCount();
}

FontLoader::FontLoader(Document* document)
    : ActiveDOMObject(document)
    , m_document(document)
    , m_numLoadingFromCSS(0)
    , m_numLoadingFromJS(0)
    , m_pendingEventsTimer(*this, &FontLoader::firePendingEvents)
{
    suspendIfNeeded();
}

FontLoader::~FontLoader()
{
}

EventTargetData* FontLoader::eventTargetData()
{
    return &m_eventTargetData;
}

EventTargetData& FontLoader::ensureEventTargetData()
{
    return m_eventTargetData;
}

EventTargetInterface FontLoader::eventTargetInterface() const
{
    return FontLoaderEventTargetInterfaceType;
}

ScriptExecutionContext* FontLoader::scriptExecutionContext() const
{
    return ActiveDOMObject::scriptExecutionContext();
}

void FontLoader::didLayout()
{
    loadingDone();
}

const char* FontLoader::activeDOMObjectName() const
{
    return "FontLoader";
}

bool FontLoader::canSuspendForPageCache() const
{
    return !m_numLoadingFromCSS && !m_numLoadingFromJS;
}

void FontLoader::scheduleEvent(PassRefPtr<Event> event)
{
    m_pendingEvents.append(event);
    if (!m_pendingEventsTimer.isActive())
        m_pendingEventsTimer.startOneShot(0);
}

void FontLoader::firePendingEvents()
{
    if (m_pendingEvents.isEmpty() && !m_loadingDoneEvent && !m_callbacks.isEmpty())
        return;

    Vector<RefPtr<Event>> pendingEvents;
    m_pendingEvents.swap(pendingEvents);

    bool loadingDone = false;
    if (m_loadingDoneEvent) {
        pendingEvents.append(m_loadingDoneEvent.release());
        loadingDone = true;
    }

    for (size_t index = 0; index < pendingEvents.size(); ++index)
        dispatchEvent(pendingEvents[index].release());

    if (loadingDone && !m_callbacks.isEmpty()) {
        Vector<RefPtr<VoidCallback>> callbacks;
        m_callbacks.swap(callbacks);
        for (size_t index = 0; index < callbacks.size(); ++index)
            callbacks[index]->handleEvent();
    }
}

void FontLoader::beginFontLoading(CSSFontFaceRule* rule)
{
    ++m_numLoadingFromCSS;
    if (m_numLoadingFromCSS == 1 && !m_loadingDoneEvent)
        scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingEvent, rule));
    scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadstartEvent, rule));
}

void FontLoader::fontLoaded(CSSFontFaceRule* rule)
{
    ASSERT(m_numLoadingFromCSS > 0);
    scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadEvent, rule));

    --m_numLoadingFromCSS;
    if (!m_numLoadingFromCSS)
        m_loadingDoneEvent = CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingdoneEvent, rule);
}

void FontLoader::loadError(CSSFontFaceRule* rule, CSSFontFaceSource* source)
{
    ASSERT(m_numLoadingFromCSS > 0);

    // FIXME: We should report NetworkError in case of timeout, etc.
    String errorName = (source && source->isDecodeError()) ? "InvalidFontDataError" : ExceptionCodeDescription(NOT_FOUND_ERR).name;
    scheduleEvent(CSSFontFaceLoadEvent::createForError(rule, DOMError::create(errorName)));
    --m_numLoadingFromCSS;
    if (!m_numLoadingFromCSS)
        m_loadingDoneEvent = CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingdoneEvent, rule);
}

void FontLoader::notifyWhenFontsReady(PassRefPtr<VoidCallback> callback)
{
    m_callbacks.append(callback);
}

void FontLoader::loadingDone()
{
    if (loading() || !m_document->haveStylesheetsLoaded())
        return;
    if (!m_loadingDoneEvent && m_callbacks.isEmpty() && m_pendingEvents.isEmpty())
        return;

    if (FrameView* view = m_document->view()) {
        if (view->isInLayout() || view->needsLayout())
            return;
    }

    if (!m_pendingEventsTimer.isActive())
        m_pendingEventsTimer.startOneShot(0);
}

void FontLoader::loadFont(const Dictionary& params)
{
    // FIXME: The text member of params is ignored.
    String fontString;
    if (!params.get("font", fontString))
        return;
    FontCascade font;
    if (!resolveFontStyle(fontString, font))
        return;
    RefPtr<LoadFontCallback> callback = LoadFontCallback::createFromParams(params, *this, font);
    m_numLoadingFromJS += callback->familyCount();

    for (unsigned i = 0; i < font.familyCount(); i++) {
        CSSSegmentedFontFace* face = m_document->fontSelector().getFontFace(font.fontDescription(), font.familyAt(i));
        if (!face) {
            if (callback)
                callback->notifyError();
            continue;
        }
        face->loadFont(font.fontDescription(), callback);
    }
}

bool FontLoader::checkFont(const String& fontString, const String&)
{
    // FIXME: The second parameter (text) is ignored.
    FontCascade font;
    if (!resolveFontStyle(fontString, font))
        return false;
    for (unsigned i = 0; i < font.familyCount(); i++) {
        CSSSegmentedFontFace* face = m_document->fontSelector().getFontFace(font.fontDescription(), font.familyAt(i));
        if (!face || !face->checkFont())
            return false;
    }
    return true;
}

static void applyPropertyToCurrentStyle(StyleResolver& styleResolver, CSSPropertyID id, const RefPtr<StyleProperties>& parsedStyle)
{
    styleResolver.applyPropertyToCurrentStyle(id, parsedStyle->getPropertyCSSValue(id).get());
}

bool FontLoader::resolveFontStyle(const String& fontString, FontCascade& font)
{
    // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
    RefPtr<MutableStyleProperties> parsedStyle = MutableStyleProperties::create();
    CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, CSSStrictMode, nullptr);
    if (parsedStyle->isEmpty())
        return false;
    
    String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
    if (fontValue == "inherit" || fontValue == "initial")
        return false;

    RefPtr<RenderStyle> style = RenderStyle::create();

    FontDescription defaultFontDescription;
    defaultFontDescription.setOneFamily(defaultFontFamily);
    defaultFontDescription.setSpecifiedSize(defaultFontSize);
    defaultFontDescription.setComputedSize(defaultFontSize);

    style->setFontDescription(defaultFontDescription);

    style->fontCascade().update(style->fontCascade().fontSelector());

    // Now map the font property longhands into the style.
    StyleResolver& styleResolver = m_document->ensureStyleResolver();
    styleResolver.applyPropertyToStyle(CSSPropertyFontFamily, parsedStyle->getPropertyCSSValue(CSSPropertyFontFamily).get(), style.get());
    applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontStyle, parsedStyle);
    applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontVariant, parsedStyle);
    applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontWeight, parsedStyle);

    // As described in BUG66291, setting font-size and line-height on a font may entail a CSSPrimitiveValue::computeLengthDouble call,
    // which assumes the fontMetrics are available for the affected font, otherwise a crash occurs (see http://trac.webkit.org/changeset/96122).
    // The updateFont() calls below update the fontMetrics and ensure the proper setting of font-size and line-height.
    styleResolver.updateFont();
    applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontSize, parsedStyle);
    styleResolver.updateFont();
    applyPropertyToCurrentStyle(styleResolver, CSSPropertyLineHeight, parsedStyle);

    font = style->fontCascade();
    font.update(&m_document->fontSelector());
    return true;
}

} // namespace WebCore

#endif // ENABLE(FONT_LOAD_EVENTS)
