/*
 * Copyright (C) 2012 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. 
 */

#import "config.h"

#if ENABLE(VIDEO_TRACK)

#import "CaptionUserPreferencesMac.h"

#import "ColorMac.h"
#import "DOMWrapperWorld.h"
#import "FloatConversion.h"
#import "KURL.h"
#import "PageGroup.h"
#import "TextTrackCue.h"
#import "UserStyleSheetTypes.h"
#import "WebCoreSystemInterface.h"
#import <wtf/RetainPtr.h>
#import <wtf/text/StringBuilder.h>

using namespace std;

namespace WebCore {

static void userCaptionPreferencesChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef, const void *, CFDictionaryRef)
{
    static_cast<CaptionUserPreferencesMac*>(observer)->captionPreferencesChanged();
}


CaptionUserPreferencesMac::CaptionUserPreferencesMac(PageGroup* group)
    : CaptionUserPreferences(group)
    , m_listeningForPreferenceChanges(false)
{
}

CaptionUserPreferencesMac::~CaptionUserPreferencesMac()
{
    if (wkCaptionAppearanceGetSettingsChangedNotification())
        CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, wkCaptionAppearanceGetSettingsChangedNotification(), NULL);
}

bool CaptionUserPreferencesMac::userHasCaptionPreferences() const
{
    return wkCaptionAppearanceHasUserPreferences();
}

bool CaptionUserPreferencesMac::userPrefersCaptions() const
{
    return wkCaptionAppearanceShowCaptionsWhenAvailable();
}

void CaptionUserPreferencesMac::registerForCaptionPreferencesChangedCallbacks(CaptionPreferencesChangedListener* listener)
{
    ASSERT(!m_captionPreferenceChangeListeners.contains(listener));

    if (!wkCaptionAppearanceGetSettingsChangedNotification())
        return;
    
    if (!m_listeningForPreferenceChanges) {
        m_listeningForPreferenceChanges = true;
        CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter(), this, userCaptionPreferencesChangedNotificationCallback, wkCaptionAppearanceGetSettingsChangedNotification(), NULL, CFNotificationSuspensionBehaviorCoalesce);
        updateCaptionStyleSheetOveride();
    }
    
    m_captionPreferenceChangeListeners.add(listener);
}

void CaptionUserPreferencesMac::unregisterForCaptionPreferencesChangedCallbacks(CaptionPreferencesChangedListener* listener)
{
    if (wkCaptionAppearanceGetSettingsChangedNotification())
        m_captionPreferenceChangeListeners.remove(listener);
}

Color CaptionUserPreferencesMac::captionsWindowColor() const
{
    RetainPtr<CGColorRef> color(AdoptCF, wkCaptionAppearanceCopyWindowColor());
    Color windowColor(color.get());
    if (!windowColor.isValid())
        windowColor = Color::transparent;
    
    CGFloat opacity;
    if (wkCaptionAppearanceGetWindowOpacity(&opacity))
        return Color(windowColor.red(), windowColor.green(), windowColor.blue(), static_cast<int>(opacity * 255));
    
    if (!color)
        return Color();
    
    return windowColor;
}

Color CaptionUserPreferencesMac::captionsBackgroundColor() const
{
    // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-past-nodes
    // and webkit-media-text-track-future-nodes.
    DEFINE_STATIC_LOCAL(Color, defaultBackgroundColor, (Color(0, 0, 0, 0.8 * 255)));
    
    RetainPtr<CGColorRef> color(AdoptCF, wkCaptionAppearanceCopyBackgroundColor());
    Color backgroundColor(color.get());
    if (!backgroundColor.isValid()) {
        backgroundColor = defaultBackgroundColor;
    }
    
    CGFloat opacity;
    if (wkCaptionAppearanceGetBackgroundOpacity(&opacity))
        return Color(backgroundColor.red(), backgroundColor.green(), backgroundColor.blue(), static_cast<int>(opacity * 255));
    
    if (!color)
        return Color();
    
    return backgroundColor;
}

Color CaptionUserPreferencesMac::captionsTextColor() const
{
    RetainPtr<CGColorRef> color(AdoptCF, wkCaptionAppearanceCopyForegroundColor());
    Color textColor(color.get());
    if (!textColor.isValid()) {
        // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-container.
        textColor = Color::white;
    }
    
    CGFloat opacity;
    if (wkCaptionAppearanceGetForegroundOpacity(&opacity))
        return Color(textColor.red(), textColor.green(), textColor.blue(), static_cast<int>(opacity * 255));
    
    if (!color)
        return Color();
    
    return textColor;
}

Color CaptionUserPreferencesMac::captionsEdgeColorForTextColor(const Color& textColor) const
{
    int distanceFromWhite = differenceSquared(textColor, Color::white);
    int distanceFromBlack = differenceSquared(textColor, Color::black);
    
    if (distanceFromWhite < distanceFromBlack)
        return textColor.dark();
    
    return textColor.light();
}

String CaptionUserPreferencesMac::cssPropertyWithTextEdgeColor(CSSPropertyID id, const String& value, const Color& textColor) const
{
    StringBuilder builder;
    
    builder.append(getPropertyNameString(id));
    builder.append(':');
    builder.append(value);
    builder.append(' ');
    builder.append(captionsEdgeColorForTextColor(textColor).serialized());
    builder.append(';');
    
    return builder.toString();
}

String CaptionUserPreferencesMac::cssColorProperty(CSSPropertyID id, const Color& color) const
{
    StringBuilder builder;
    
    builder.append(getPropertyNameString(id));
    builder.append(':');
    builder.append(color.serialized());
    builder.append(';');
    
    return builder.toString();
}

String CaptionUserPreferencesMac::captionsTextEdgeStyle() const
{
    DEFINE_STATIC_LOCAL(const String, edgeStyleRaised, (" -.05em -.05em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleDepressed, (" .05em .05em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleDropShadow, (" .075em .075em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleUniform, (" .03em ", String::ConstructFromLiteral));
    
    Color color = captionsTextColor();
    if (!color.isValid())
        color.setNamedColor("black");
    color = captionsEdgeColorForTextColor(color);
    
    wkCaptionTextEdgeStyle textEdgeStyle = static_cast<wkCaptionTextEdgeStyle>(wkCaptionAppearanceGetTextEdgeStyle());
    switch (textEdgeStyle) {
        case wkCaptionTextEdgeStyleUndefined:
        case wkCaptionTextEdgeStyleNone:
            return emptyString();
            
        case wkCaptionTextEdgeStyleRaised:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleRaised, color);
        case wkCaptionTextEdgeStyleDepressed:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDepressed, color);
        case wkCaptionTextEdgeStyleDropShadow:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDropShadow, color);
        case wkCaptionTextEdgeStyleUniform:
            return cssPropertyWithTextEdgeColor(CSSPropertyWebkitTextStroke, edgeStyleUniform, color);
            
        case wkCaptionTextEdgeStyleMax:
            ASSERT_NOT_REACHED();
        default:
            ASSERT_NOT_REACHED();
            break;
    }
    
    return emptyString();
}

String CaptionUserPreferencesMac::captionsDefaultFont() const
{
    RetainPtr<CGFontRef> font(AdoptCF, wkCaptionAppearanceCopyFontForStyle(wkCaptionFontStyleDefault));
    if (!font)
        return emptyString();
    
    RetainPtr<CFStringRef> name(AdoptCF, CGFontCopyPostScriptName(font.get()));
    if (!name)
        return emptyString();
    
    StringBuilder builder;
    
    builder.append(getPropertyNameString(CSSPropertyFontFamily));
    builder.append(": \"");
    builder.append(name.get());
    builder.append("\";");
    
    return builder.toString();
}

String CaptionUserPreferencesMac::captionsStyleSheetOverride() const
{
    StringBuilder captionsOverrideStyleSheet;
    
    Color color = captionsBackgroundColor();
    if (color.isValid()) {
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(TextTrackCue::pastNodesShadowPseudoId());
        captionsOverrideStyleSheet.append('{');
        captionsOverrideStyleSheet.append(cssColorProperty(CSSPropertyBackgroundColor, color));
        captionsOverrideStyleSheet.append('}');
        
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(TextTrackCue::futureNodesShadowPseudoId());
        captionsOverrideStyleSheet.append('{');
        captionsOverrideStyleSheet.append(cssColorProperty(CSSPropertyBackgroundColor, color));
        captionsOverrideStyleSheet.append('}');
    }
    
    color = captionsWindowColor();
    if (color.isValid()) {
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(TextTrackCueBox::textTrackCueBoxShadowPseudoId());
        captionsOverrideStyleSheet.append('{');
        captionsOverrideStyleSheet.append(cssColorProperty(CSSPropertyBackgroundColor, color));
        captionsOverrideStyleSheet.append('}');
    }
    
    color = captionsTextColor();
    String edgeStyle = captionsTextEdgeStyle();
    String fontName = captionsDefaultFont();
    if (color.isValid() || !edgeStyle.isEmpty() || !fontName.isEmpty()) {
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(TextTrackCueBox::textTrackCueBoxShadowPseudoId());
        captionsOverrideStyleSheet.append('{');
        
        if (color.isValid())
            captionsOverrideStyleSheet.append(cssColorProperty(CSSPropertyColor, color));
        if (!edgeStyle.isEmpty())
            captionsOverrideStyleSheet.append(edgeStyle);
        if (!fontName.isEmpty())
            captionsOverrideStyleSheet.append(fontName);
        
        captionsOverrideStyleSheet.append('}');
    }
    
    return captionsOverrideStyleSheet.toString();
}

float CaptionUserPreferencesMac::captionFontSizeScale() const
{
    CGFloat characterScale = CaptionUserPreferences::captionFontSizeScale();
    CGFloat scaleAdjustment;
    
    if (!wkCaptionAppearanceGetRelativeCharacterSize(&scaleAdjustment))
        return characterScale;
    
#if defined(__LP64__) && __LP64__
    return narrowPrecisionToFloat(scaleAdjustment * characterScale);
#else
    return scaleAdjustment * characterScale;
#endif
}

void CaptionUserPreferencesMac::captionPreferencesChanged()
{
    if (m_captionPreferenceChangeListeners.isEmpty())
        return;

    updateCaptionStyleSheetOveride();

    for (HashSet<CaptionPreferencesChangedListener*>::iterator i = m_captionPreferenceChangeListeners.begin(); i != m_captionPreferenceChangeListeners.end(); ++i)
        (*i)->captionPreferencesChanged();
}

void CaptionUserPreferencesMac::updateCaptionStyleSheetOveride()
{
    // Identify our override style sheet with a unique URL - a new scheme and a UUID.
    DEFINE_STATIC_LOCAL(KURL, captionsStyleSheetURL, (ParsedURLString, "user-captions-override:01F6AF12-C3B0-4F70-AF5E-A3E00234DC23"));
    
    pageGroup()->removeUserStyleSheetFromWorld(mainThreadNormalWorld(), captionsStyleSheetURL);
    
    if (!userHasCaptionPreferences())
        return;
    
    String captionsOverrideStyleSheet = captionsStyleSheetOverride();
    if (captionsOverrideStyleSheet.isEmpty())
        return;
    
    pageGroup()->addUserStyleSheetToWorld(mainThreadNormalWorld(), captionsOverrideStyleSheet, captionsStyleSheetURL, Vector<String>(),
             Vector<String>(), InjectInAllFrames, UserStyleAuthorLevel, InjectInExistingDocuments);
}

}

#endif // ENABLE(VIDEO_TRACK)
