/*
 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
 * Copyright (c) 2010 ACCESS CO., LTD. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "CSSPrimitiveValue.h"

#include "CSSHelper.h"
#include "CSSPropertyNames.h"
#include "CSSStyleSheet.h"
#include "CSSValueKeywords.h"
#include "Color.h"
#include "Counter.h"
#include "ExceptionCode.h"
#include "Node.h"
#include "Pair.h"
#include "RGBColor.h"
#include "Rect.h"
#include "RenderStyle.h"
#include <wtf/ASCIICType.h>
#include <wtf/StdLibExtras.h>

#if ENABLE(DASHBOARD_SUPPORT)
#include "DashboardRegion.h"
#endif

using namespace WTF;

namespace WebCore {

// A more stylish solution than sharing would be to turn CSSPrimitiveValue (or CSSValues in general) into non-virtual,
// non-refcounted simple type with value semantics. In practice these sharing tricks get similar memory benefits 
// with less need for refactoring.

RefPtr<CSSPrimitiveValue>* CSSPrimitiveValue::m_IdentValueCache = 0;

CSSPrimitiveValue::ColorValueCache* CSSPrimitiveValue::m_ColorValueCache = 0;

CSSPrimitiveValue* CSSPrimitiveValue::m_ColorTransparent = 0;

CSSPrimitiveValue* CSSPrimitiveValue::m_ColorWhite = 0;

static const int cachedIntegerCount = 128;
CSSPrimitiveValue::IntegerValueCache CSSPrimitiveValue::m_IntegerValueCache = 0;

#if PLATFORM(WKC)
CSSPrimitiveValue::DoubleValueCacheShared* CSSPrimitiveValue::m_doubleValueCacheShared = 0;
#endif

const String* CSSPrimitiveValue::m_AttrParen = 0;

const String* CSSPrimitiveValue::m_RectParen = 0;

const String* CSSPrimitiveValue::m_CommaSpace = 0;

const String* CSSPrimitiveValue::m_RGBParen = 0;

const String* CSSPrimitiveValue::m_RGBAParen = 0;

struct CSSPrimitiveValueDoubleTranslator {
    static unsigned hash(const CSSPrimitiveValue* primitivevalue)
    {
        return CSSPrimitiveValueDoubleHash::hash(primitivevalue);
    }

    static bool equal(CSSPrimitiveValue* a, const CSSPrimitiveValue* b)
    {
        return CSSPrimitiveValueDoubleHash::equal(a, b);
    }

    static void translate(CSSPrimitiveValue*& location, const CSSPrimitiveValue* const& primitivevalue, unsigned hash)
    {
        location = const_cast<CSSPrimitiveValue*>(primitivevalue);
    }
};

/* static */ bool CSSPrimitiveValue::createSharedInstance(int in_type)
{
    bool ret = true;

    if (!m_IdentValueCache && CSS_PRIMITIVE_VALUE_IDENTVALUECACHE == in_type) {
        m_IdentValueCache = new RefPtr<CSSPrimitiveValue>[numCSSValueKeywords];
        if (!m_IdentValueCache) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_ColorValueCache && CSS_PRIMITIVE_VALUE_COLORVALUECACHE == in_type) {
        m_ColorValueCache = new ColorValueCache;
        if (!m_ColorValueCache) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_ColorTransparent && CSS_PRIMITIVE_VALUE_COLORTRANSPARENT == in_type) {
        m_ColorTransparent = new CSSPrimitiveValue(Color::transparent);
        if (!m_ColorTransparent) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_ColorWhite && CSS_PRIMITIVE_VALUE_COLORWHITE == in_type) {
        m_ColorWhite = new CSSPrimitiveValue(Color::white);
        if (!m_ColorWhite) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_IntegerValueCache && CSS_PRIMITIVE_VALUE_INTEGERVALUECACHE == in_type) {
        m_IntegerValueCache = new RefPtr<CSSPrimitiveValue>[cachedIntegerCount][maxCachedUnitType + 1];
        if (!m_IntegerValueCache) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_doubleValueCacheShared && CSS_PRIMITIVE_VALUE_INTEGERVALUECACHESHARED == in_type) {
        m_doubleValueCacheShared = new DoubleValueCacheShared;
        if (!m_doubleValueCacheShared) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_AttrParen && CSS_PRIMITIVE_VALUE_ATTRPAREN == in_type) {
        m_AttrParen = new String("attr(");
        if (!m_AttrParen) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_RectParen && CSS_PRIMITIVE_VALUE_RECTPAREN == in_type) {
        m_RectParen = new String("rect(");
        if (!m_RectParen) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_CommaSpace && CSS_PRIMITIVE_VALUE_COMMASPACE == in_type) {
        m_CommaSpace = new String(", ");
        if (!m_CommaSpace) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_RGBParen && CSS_PRIMITIVE_VALUE_RGBPAREN == in_type) {
        m_RGBParen = new String("rgb(");
        if (!m_RGBParen) {
            ret = false;
            goto exit_func;
        }
    }

    if (!m_RGBAParen && CSS_PRIMITIVE_VALUE_RGBAPAREN == in_type) {
        m_RGBAParen = new String("rgba(");
        if (!m_RGBAParen) {
            ret = false;
            goto exit_func;
        }
    }

exit_func:
    if (!ret)
        deleteSharedInstance();

    return ret;
}

/* static */ void CSSPrimitiveValue::resetVariables()
{
    m_IdentValueCache = 0;

    m_ColorValueCache = 0;

    m_ColorTransparent = 0;

    m_ColorWhite = 0;

    m_IntegerValueCache = 0;

    m_doubleValueCacheShared = 0;

    m_AttrParen = 0;

    m_RectParen = 0;

    m_CommaSpace = 0;

    m_RGBParen = 0;

    m_RGBAParen = 0;
}

/* static */ void CSSPrimitiveValue::deleteSharedInstance()
{
    if (m_IdentValueCache) {
        for (int i = 0; i < numCSSValueKeywords; i++) {
            CSSPrimitiveValue* value = m_IdentValueCache[i].get();
            if (value) {
                while (value->refCount() > 1)
                    value->deref();
            }
        }
    }

    delete[] m_IdentValueCache;
    m_IdentValueCache = 0;

    delete m_ColorValueCache;
    m_ColorValueCache = 0;

    delete m_ColorTransparent;
    m_ColorTransparent = 0;

    delete m_ColorWhite;
    m_ColorWhite = 0;

    if (m_IntegerValueCache) {
        for (int i = 0; i < cachedIntegerCount; i++) {
            for (int j = 0; j <= maxCachedUnitType; j++) {
                CSSPrimitiveValue *value = m_IntegerValueCache[i][j].get();
                if (value) {
                    while (value->refCount() > 1)
                        value->deref();
                }
            }
        }
    }

    delete[] m_IntegerValueCache;
    m_IntegerValueCache = 0;

    if (m_doubleValueCacheShared) {
        DoubleValueCacheShared::iterator end = m_doubleValueCacheShared->end();
        for (DoubleValueCacheShared::iterator it = m_doubleValueCacheShared->begin(); it != end; ++it) {
            CSSPrimitiveValue* value = *it;
            if (value) {
                value->m_doRemove = false;
                delete value;
            }
        }
    }

    delete m_doubleValueCacheShared;
    m_doubleValueCacheShared = 0;

    delete m_AttrParen;
    m_AttrParen = 0;

    delete m_RectParen;
    m_RectParen = 0;

    delete m_CommaSpace;
    m_CommaSpace = 0;

    delete m_RGBParen;
    m_RGBParen = 0;

    delete m_RGBAParen;
    m_RGBAParen = 0;
}

PassRefPtr<CSSPrimitiveValue> CSSPrimitiveValue::createIdentifier(int ident)
{
    (void)createSharedInstance(CSS_PRIMITIVE_VALUE_IDENTVALUECACHE);

    if (ident >= 0 && ident < numCSSValueKeywords) {
        RefPtr<CSSPrimitiveValue> primitiveValue = m_IdentValueCache[ident];
        if (!primitiveValue) {
            primitiveValue = adoptRef(new CSSPrimitiveValue(ident));
            m_IdentValueCache[ident] = primitiveValue;
        }
        return primitiveValue.release();
    } 
    return adoptRef(new CSSPrimitiveValue(ident));
}

PassRefPtr<CSSPrimitiveValue> CSSPrimitiveValue::createColor(unsigned rgbValue)
{
    if (!createSharedInstance(CSS_PRIMITIVE_VALUE_COLORVALUECACHE))
        return 0;

    // These are the empty and deleted values of the hash table.
    if (rgbValue == Color::transparent) {
        if (!createSharedInstance(CSS_PRIMITIVE_VALUE_COLORTRANSPARENT))
            return 0;

        return m_ColorTransparent;
    }
    if (rgbValue == Color::white) {
        if (!createSharedInstance(CSS_PRIMITIVE_VALUE_COLORWHITE))
            return 0;

        return m_ColorWhite;
    }
    RefPtr<CSSPrimitiveValue> primitiveValue = m_ColorValueCache->get(rgbValue);
    if (primitiveValue)
        return primitiveValue.release();
    primitiveValue = adoptRef(new CSSPrimitiveValue(rgbValue));
    // Just wipe out the cache and start rebuilding when it gets too big.
    const int maxColorCacheSize = 512;
    if (m_ColorValueCache->size() >= maxColorCacheSize)
        m_ColorValueCache->clear();
    m_ColorValueCache->add(rgbValue, primitiveValue);
    
    return primitiveValue.release();
}

PassRefPtr<CSSPrimitiveValue> CSSPrimitiveValue::create(double value, UnitTypes type)
{
    // Other common primitive types have UnitTypes smaller than this.
    if (!createSharedInstance(CSS_PRIMITIVE_VALUE_INTEGERVALUECACHE))
        return 0;

    if (type <= maxCachedUnitType && value >= 0 && value < cachedIntegerCount) {
        int intValue = static_cast<int>(value);
        if (value == intValue) {
            RefPtr<CSSPrimitiveValue> primitiveValue = m_IntegerValueCache[intValue][type];
            if (!primitiveValue) {
                primitiveValue = adoptRef(new CSSPrimitiveValue(value, type));
                m_IntegerValueCache[intValue][type] = primitiveValue;
            }
            return primitiveValue.release();
        }
    }

#if PLATFORM(WKC)
    if (!createSharedInstance(CSS_PRIMITIVE_VALUE_INTEGERVALUECACHESHARED))
        return 0;

    CSSPrimitiveValue* primitivevalue = new CSSPrimitiveValue(value, type);

    DoubleValueCacheShared::iterator it = m_doubleValueCacheShared->find<CSSPrimitiveValue*, CSSPrimitiveValueDoubleTranslator>(primitivevalue);
    if (it != m_doubleValueCacheShared->end()) {
        CSSPrimitiveValue* primitive_old = (CSSPrimitiveValue*)*it;
        ASSERT(primitive_old->primitiveType() == primitivevalue->primitiveType());
        ASSERT(primitive_old->getDoubleValue() == primitivevalue->getDoubleValue());
        delete primitivevalue;
        primitivevalue = (CSSPrimitiveValue*)*it;
        primitivevalue->ref();
    } else {
        primitivevalue->m_doRemove = true;
        pair<DoubleValueCacheShared::iterator, bool> ret = m_doubleValueCacheShared->add<CSSPrimitiveValue*, CSSPrimitiveValueDoubleTranslator>(primitivevalue);
        ASSERT(ret.second);
    }

    return adoptRef(primitivevalue);
#else

    return adoptRef(NEW_NORMAL CSSPrimitiveValue(value, type));
#endif
}

PassRefPtr<CSSPrimitiveValue> CSSPrimitiveValue::create(const String& value, UnitTypes type)
{
    return adoptRef(new CSSPrimitiveValue(value, type));
}

static const char* valueOrPropertyName(int valueOrPropertyID)
{
    if (const char* valueName = getValueName(valueOrPropertyID))
        return valueName;
    return getPropertyName(static_cast<CSSPropertyID>(valueOrPropertyID));
}

// "ident" from the CSS tokenizer, minus backslash-escape sequences
static bool isCSSTokenizerIdentifier(const String& string)
{
    const UChar* p = string.characters();
    const UChar* end = p + string.length();

    // -?
    if (p != end && p[0] == '-')
        ++p;

    // {nmstart}
    if (p == end || !(p[0] == '_' || p[0] >= 128 || isASCIIAlpha(p[0])))
        return false;
    ++p;

    // {nmchar}*
    for (; p != end; ++p) {
        if (!(p[0] == '_' || p[0] == '-' || p[0] >= 128 || isASCIIAlphanumeric(p[0])))
            return false;
    }

    return true;
}

// "url" from the CSS tokenizer, minus backslash-escape sequences
static bool isCSSTokenizerURL(const String& string)
{
    const UChar* p = string.characters();
    const UChar* end = p + string.length();

    for (; p != end; ++p) {
        UChar c = p[0];
        switch (c) {
            case '!':
            case '#':
            case '$':
            case '%':
            case '&':
                break;
            default:
                if (c < '*')
                    return false;
                if (c <= '~')
                    break;
                if (c < 128)
                    return false;
        }
    }

    return true;
}

// We use single quotes for now because markup.cpp uses double quotes.
static String quoteString(const String& string)
{
    // FIXME: Also need to escape characters like '\n'.
    String s = string;
    s.replace('\\', "\\\\");
    s.replace('\'', "\\'");
    return "'" + s + "'";
}

static String quoteStringIfNeeded(const String& string)
{
    return isCSSTokenizerIdentifier(string) ? string : quoteString(string);
}

static String quoteURLIfNeeded(const String& string)
{
    return isCSSTokenizerURL(string) ? string : quoteString(string);
}

CSSPrimitiveValue::CSSPrimitiveValue()
    : m_type(0)
    , m_doRemove(false)
{
}

CSSPrimitiveValue::CSSPrimitiveValue(int ident)
    : m_type(CSS_IDENT)
    , m_doRemove(false)
{
    m_value.ident = ident;
}

CSSPrimitiveValue::CSSPrimitiveValue(double num, UnitTypes type)
    : m_type(type)
    , m_doRemove(false)
{
    m_value.num = num;
}

CSSPrimitiveValue::CSSPrimitiveValue(const String& str, UnitTypes type)
    : m_type(type)
    , m_doRemove(false)
{
    if ((m_value.string = str.impl()))
        m_value.string->ref();
}

CSSPrimitiveValue::CSSPrimitiveValue(RGBA32 color)
    : m_type(CSS_RGBCOLOR)
    , m_doRemove(false)
{
    m_value.rgbcolor = color;
}

CSSPrimitiveValue::CSSPrimitiveValue(const Length& length)
    : m_doRemove(false)
{
    switch (length.type()) {
        case Auto:
            m_type = CSS_IDENT;
            m_value.ident = CSSValueAuto;
            break;
        case WebCore::Fixed:
            m_type = CSS_PX;
            m_value.num = length.value();
            break;
        case Intrinsic:
            m_type = CSS_IDENT;
            m_value.ident = CSSValueIntrinsic;
            break;
        case MinIntrinsic:
            m_type = CSS_IDENT;
            m_value.ident = CSSValueMinIntrinsic;
            break;
        case Percent:
            m_type = CSS_PERCENTAGE;
            m_value.num = length.percent();
            break;
        case Relative:
        case Static:
            ASSERT_NOT_REACHED();
            break;
    }
}

void CSSPrimitiveValue::init(PassRefPtr<Counter> c)
{
    m_type = CSS_COUNTER;
    m_value.counter = c.releaseRef();
}

void CSSPrimitiveValue::init(PassRefPtr<Rect> r)
{
    m_type = CSS_RECT;
    m_value.rect = r.releaseRef();
}

#if ENABLE(DASHBOARD_SUPPORT)
void CSSPrimitiveValue::init(PassRefPtr<DashboardRegion> r)
{
    m_type = CSS_DASHBOARD_REGION;
    m_value.region = r.releaseRef();
}
#endif

void CSSPrimitiveValue::init(PassRefPtr<Pair> p)
{
    m_type = CSS_PAIR;
    m_value.pair = p.releaseRef();
}

CSSPrimitiveValue::~CSSPrimitiveValue()
{
    if (m_doubleValueCacheShared && m_doRemove)
        m_doubleValueCacheShared->remove(this);

    cleanup();
}

void CSSPrimitiveValue::cleanup()
{
    switch (m_type) {
        case CSS_STRING:
        case CSS_URI:
        case CSS_ATTR:
        case CSS_PARSER_VARIABLE_FUNCTION_SYNTAX:
        case CSS_PARSER_HEXCOLOR:
            if (m_value.string)
                m_value.string->deref();
            break;
        case CSS_COUNTER:
            m_value.counter->deref();
            break;
        case CSS_RECT:
            m_value.rect->deref();
            break;
        case CSS_PAIR:
            m_value.pair->deref();
            break;
#if ENABLE(DASHBOARD_SUPPORT)
        case CSS_DASHBOARD_REGION:
            if (m_value.region)
                m_value.region->deref();
            break;
#endif
        default:
            break;
    }

    m_type = 0;
}

int CSSPrimitiveValue::computeLengthInt(RenderStyle* style, RenderStyle* rootStyle)
{
    double result = computeLengthDouble(style, rootStyle);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > INT_MAX || result < INT_MIN)
        return 0;
    return static_cast<int>(result);
}

int CSSPrimitiveValue::computeLengthInt(RenderStyle* style, RenderStyle* rootStyle, double multiplier)
{
    double result = computeLengthDouble(style, rootStyle, multiplier);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > INT_MAX || result < INT_MIN)
        return 0;
    return static_cast<int>(result);
}

const int intMaxForLength = 0x7ffffff; // max value for a 28-bit int
const int intMinForLength = (-0x7ffffff - 1); // min value for a 28-bit int

// Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle* style, RenderStyle* rootStyle)
{
    double result = computeLengthDouble(style, rootStyle);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > intMaxForLength || result < intMinForLength)
        return 0;
    return static_cast<int>(result);
}

// Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle* style, RenderStyle* rootStyle, double multiplier)
{
    double result = computeLengthDouble(style, rootStyle, multiplier);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > intMaxForLength || result < intMinForLength)
        return 0;
    return static_cast<int>(result);
}

short CSSPrimitiveValue::computeLengthShort(RenderStyle* style, RenderStyle* rootStyle)
{
    double result = computeLengthDouble(style, rootStyle);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > SHRT_MAX || result < SHRT_MIN)
        return 0;
    return static_cast<short>(result);
}

short CSSPrimitiveValue::computeLengthShort(RenderStyle* style, RenderStyle* rootStyle, double multiplier)
{
    double result = computeLengthDouble(style, rootStyle, multiplier);

    // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
    // need to go ahead and round if we're really close to the next integer value.
    result += result < 0 ? -0.01 : +0.01;

    if (result > SHRT_MAX || result < SHRT_MIN)
        return 0;
    return static_cast<short>(result);
}

float CSSPrimitiveValue::computeLengthFloat(RenderStyle* style, RenderStyle* rootStyle, bool computingFontSize)
{
    return static_cast<float>(computeLengthDouble(style, rootStyle, 1.0, computingFontSize));
}

float CSSPrimitiveValue::computeLengthFloat(RenderStyle* style, RenderStyle* rootStyle, double multiplier, bool computingFontSize)
{
    return static_cast<float>(computeLengthDouble(style, rootStyle, multiplier, computingFontSize));
}

double CSSPrimitiveValue::computeLengthDouble(RenderStyle* style, RenderStyle* rootStyle, double multiplier, bool computingFontSize)
{
    unsigned short type = primitiveType();

    // We do not apply the zoom factor when we are computing the value of the font-size property.  The zooming
    // for font sizes is much more complicated, since we have to worry about enforcing the minimum font size preference
    // as well as enforcing the implicit "smart minimum."  In addition the CSS property text-size-adjust is used to
    // prevent text from zooming at all.  Therefore we will not apply the zoom here if we are computing font-size.
    bool applyZoomMultiplier = !computingFontSize;

    double factor = 1.0;
    switch (type) {
        case CSS_EMS:
            applyZoomMultiplier = false;
            factor = computingFontSize ? style->fontDescription().specifiedSize() : style->fontDescription().computedSize();
            break;
        case CSS_EXS:
            // FIXME: We have a bug right now where the zoom will be applied twice to EX units.
            // We really need to compute EX using fontMetrics for the original specifiedSize and not use
            // our actual constructed rendering font.
            applyZoomMultiplier = false;
            factor = style->font().xHeight();
            break;
        case CSS_REMS:
            applyZoomMultiplier = false;
            factor = computingFontSize ? rootStyle->fontDescription().specifiedSize() : rootStyle->fontDescription().computedSize();
            break;
        case CSS_PX:
            break;
        case CSS_CM:
            factor = cssPixelsPerInch / 2.54; // (2.54 cm/in)
            break;
        case CSS_MM:
            factor = cssPixelsPerInch / 25.4;
            break;
        case CSS_IN:
            factor = cssPixelsPerInch;
            break;
        case CSS_PT:
            factor = cssPixelsPerInch / 72.0;
            break;
        case CSS_PC:
            // 1 pc == 12 pt
            factor = cssPixelsPerInch * 12.0 / 72.0;
            break;
        default:
            return -1.0;
    }

    double result = getDoubleValue() * factor;
    if (!applyZoomMultiplier || multiplier == 1.0)
        return result;
     
    // Any original result that was >= 1 should not be allowed to fall below 1.  This keeps border lines from
    // vanishing.
    double zoomedResult = result * multiplier;
    if (result >= 1.0)
        zoomedResult = max(1.0, zoomedResult);
    return zoomedResult;
}

void CSSPrimitiveValue::setFloatValue(unsigned short unitType, double floatValue, ExceptionCode& ec)
{
    ec = 0;

    // FIXME: check if property supports this type
    if (m_type > CSS_DIMENSION) {
        ec = SYNTAX_ERR;
        return;
    }

    cleanup();

    //if(m_type > CSS_DIMENSION) throw DOMException(INVALID_ACCESS_ERR);
    m_value.num = floatValue;
    m_type = unitType;
}

static double scaleFactorForConversion(unsigned short unitType)
{
    double factor = 1.0;
    switch (unitType) {
        case CSSPrimitiveValue::CSS_PX:
            break;
        case CSSPrimitiveValue::CSS_CM:
            factor = cssPixelsPerInch / 2.54; // (2.54 cm/in)
            break;
        case CSSPrimitiveValue::CSS_MM:
            factor = cssPixelsPerInch / 25.4;
            break;
        case CSSPrimitiveValue::CSS_IN:
            factor = cssPixelsPerInch;
            break;
        case CSSPrimitiveValue::CSS_PT:
            factor = cssPixelsPerInch / 72.0;
            break;
        case CSSPrimitiveValue::CSS_PC:
            factor = cssPixelsPerInch * 12.0 / 72.0; // 1 pc == 12 pt
            break;
        default:
            break;
    }

    return factor;
}

double CSSPrimitiveValue::getDoubleValue(unsigned short unitType, ExceptionCode& ec)
{
    ec = 0;
    if (m_type < CSS_NUMBER || m_type > CSS_DIMENSION || unitType < CSS_NUMBER || unitType > CSS_DIMENSION) {
        ec = INVALID_ACCESS_ERR;
        return 0.0;
    }

    if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
        return m_value.num;

    double convertedValue = m_value.num;

    // First convert the value from m_type into CSSPixels
    double factor = scaleFactorForConversion(m_type);
    convertedValue *= factor;

    // Now convert from CSSPixels to the specified unitType
    factor = scaleFactorForConversion(unitType);
    convertedValue /= factor;

    return convertedValue;
}

double CSSPrimitiveValue::getDoubleValue(unsigned short unitType)
{
    if (m_type < CSS_NUMBER || m_type > CSS_DIMENSION || unitType < CSS_NUMBER || unitType > CSS_DIMENSION)
        return 0;

    if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
        return m_value.num;

    double convertedValue = m_value.num;

    // First convert the value from m_type into CSSPixels
    double factor = scaleFactorForConversion(m_type);
    convertedValue *= factor;

    // Now convert from CSSPixels to the specified unitType
    factor = scaleFactorForConversion(unitType);
    convertedValue /= factor;

    return convertedValue;
}


void CSSPrimitiveValue::setStringValue(unsigned short stringType, const String& stringValue, ExceptionCode& ec)
{
    ec = 0;

    //if(m_type < CSS_STRING) throw DOMException(INVALID_ACCESS_ERR);
    //if(m_type > CSS_ATTR) throw DOMException(INVALID_ACCESS_ERR);
    if (m_type < CSS_STRING || m_type > CSS_ATTR) {
        ec = SYNTAX_ERR;
        return;
    }

    cleanup();

    if (stringType != CSS_IDENT) {
        m_value.string = stringValue.impl();
        m_value.string->ref();
        m_type = stringType;
    }
    // FIXME: parse ident
}

String CSSPrimitiveValue::getStringValue(ExceptionCode& ec) const
{
    ec = 0;
    switch (m_type) {
        case CSS_STRING:
        case CSS_ATTR:
        case CSS_URI:
        case CSS_PARSER_VARIABLE_FUNCTION_SYNTAX:
            return m_value.string;
        case CSS_IDENT:
            return valueOrPropertyName(m_value.ident);
        default:
            ec = INVALID_ACCESS_ERR;
            break;
    }

    return String();
}

String CSSPrimitiveValue::getStringValue() const
{
    switch (m_type) {
        case CSS_STRING:
        case CSS_ATTR:
        case CSS_URI:
        case CSS_PARSER_VARIABLE_FUNCTION_SYNTAX:
             return m_value.string;
        case CSS_IDENT:
            return valueOrPropertyName(m_value.ident);
        default:
            break;
    }

    return String();
}

Counter* CSSPrimitiveValue::getCounterValue(ExceptionCode& ec) const
{
    ec = 0;
    if (m_type != CSS_COUNTER) {
        ec = INVALID_ACCESS_ERR;
        return 0;
    }

    return m_value.counter;
}

Rect* CSSPrimitiveValue::getRectValue(ExceptionCode& ec) const
{
    ec = 0;
    if (m_type != CSS_RECT) {
        ec = INVALID_ACCESS_ERR;
        return 0;
    }

    return m_value.rect;
}

PassRefPtr<RGBColor> CSSPrimitiveValue::getRGBColorValue(ExceptionCode& ec) const
{
    ec = 0;
    if (m_type != CSS_RGBCOLOR) {
        ec = INVALID_ACCESS_ERR;
        return 0;
    }

    // FIMXE: This should not return a new object for each invocation.
    return RGBColor::create(m_value.rgbcolor);
}

Pair* CSSPrimitiveValue::getPairValue(ExceptionCode& ec) const
{
    ec = 0;
    if (m_type != CSS_PAIR) {
        ec = INVALID_ACCESS_ERR;
        return 0;
    }

    return m_value.pair;
}

unsigned short CSSPrimitiveValue::cssValueType() const
{
    return CSS_PRIMITIVE_VALUE;
}

bool CSSPrimitiveValue::parseString(const String& /*string*/, bool /*strict*/)
{
    // FIXME
    return false;
}

int CSSPrimitiveValue::getIdent()
{
    if (m_type != CSS_IDENT)
        return 0;
    return m_value.ident;
}

String CSSPrimitiveValue::cssText() const
{
    // FIXME: return the original value instead of a generated one (e.g. color
    // name if it was specified) - check what spec says about this
    String text;
    switch (m_type) {
        case CSS_UNKNOWN:
            // FIXME
            break;
        case CSS_NUMBER:
        case CSS_PARSER_INTEGER:
            text = String::number(m_value.num);
            break;
        case CSS_PERCENTAGE:
            text = String::format("%.6lg%%", m_value.num);
            break;
        case CSS_EMS:
            text = String::format("%.6lgem", m_value.num);
            break;
        case CSS_EXS:
            text = String::format("%.6lgex", m_value.num);
            break;
        case CSS_REMS:
            text = String::format("%.6lgrem", m_value.num);
            break;
        case CSS_PX:
            text = String::format("%.6lgpx", m_value.num);
            break;
        case CSS_CM:
            text = String::format("%.6lgcm", m_value.num);
            break;
        case CSS_MM:
            text = String::format("%.6lgmm", m_value.num);
            break;
        case CSS_IN:
            text = String::format("%.6lgin", m_value.num);
            break;
        case CSS_PT:
            text = String::format("%.6lgpt", m_value.num);
            break;
        case CSS_PC:
            text = String::format("%.6lgpc", m_value.num);
            break;
        case CSS_DEG:
            text = String::format("%.6lgdeg", m_value.num);
            break;
        case CSS_RAD:
            text = String::format("%.6lgrad", m_value.num);
            break;
        case CSS_GRAD:
            text = String::format("%.6lggrad", m_value.num);
            break;
        case CSS_MS:
            text = String::format("%.6lgms", m_value.num);
            break;
        case CSS_S:
            text = String::format("%.6lgs", m_value.num);
            break;
        case CSS_HZ:
            text = String::format("%.6lghz", m_value.num);
            break;
        case CSS_KHZ:
            text = String::format("%.6lgkhz", m_value.num);
            break;
        case CSS_TURN:
            text = String::format("%.6lgturn", m_value.num);
            break;
        case CSS_DIMENSION:
            // FIXME
            break;
        case CSS_STRING:
            text = quoteStringIfNeeded(m_value.string);
            break;
        case CSS_URI:
            text = "url(" + quoteURLIfNeeded(m_value.string) + ")";
            break;
        case CSS_IDENT:
            text = valueOrPropertyName(m_value.ident);
            break;
        case CSS_ATTR: {
            if (!createSharedInstance(CSS_PRIMITIVE_VALUE_ATTRPAREN))
                return String();

            Vector<UChar> result;
            result.reserveInitialCapacity(6 + m_value.string->length());

            append(result, *m_AttrParen);
            append(result, m_value.string);
            result.uncheckedAppend(')');

            return String::adopt(result);
        }
        case CSS_COUNTER:
            text = "counter(";
            text += String::number(m_value.num);
            text += ")";
            // FIXME: Add list-style and separator
            break;
        case CSS_RECT: {
            if (!createSharedInstance(CSS_PRIMITIVE_VALUE_RECTPAREN))
                return String();

            Rect* rectVal = getRectValue();
            Vector<UChar> result;
            result.reserveInitialCapacity(32);
            append(result, *m_RectParen);

            append(result, rectVal->top()->cssText());
            result.append(' ');

            append(result, rectVal->right()->cssText());
            result.append(' ');

            append(result, rectVal->bottom()->cssText());
            result.append(' ');

            append(result, rectVal->left()->cssText());
            result.append(')');

            return String::adopt(result);
        }
        case CSS_RGBCOLOR:
        case CSS_PARSER_HEXCOLOR: {
            if (!createSharedInstance(CSS_PRIMITIVE_VALUE_COMMASPACE))
                return String();

            if (!createSharedInstance(CSS_PRIMITIVE_VALUE_RGBPAREN))
                return String();

            if (!createSharedInstance(CSS_PRIMITIVE_VALUE_RGBAPAREN))
                return String();

            RGBA32 rgbColor = m_value.rgbcolor;
            if (m_type == CSS_PARSER_HEXCOLOR)
                Color::parseHexColor(m_value.string, rgbColor);
            Color color(rgbColor);

            Vector<UChar> result;
            result.reserveInitialCapacity(32);
            if (color.hasAlpha())
                append(result, *m_RGBAParen);
            else
                append(result, *m_RGBParen);

            appendNumber(result, static_cast<unsigned char>(color.red()));
            append(result, *m_CommaSpace);

            appendNumber(result, static_cast<unsigned char>(color.green()));
            append(result, *m_CommaSpace);

            appendNumber(result, static_cast<unsigned char>(color.blue()));
            if (color.hasAlpha()) {
                append(result, *m_CommaSpace);
                append(result, String::number(static_cast<float>(color.alpha()) / 256.0f));
            }

            result.append(')');
            return String::adopt(result);
        }
        case CSS_PAIR:
            text = m_value.pair->first()->cssText();
            text += " ";
            text += m_value.pair->second()->cssText();
            break;
#if ENABLE(DASHBOARD_SUPPORT)
        case CSS_DASHBOARD_REGION:
            for (DashboardRegion* region = getDashboardRegionValue(); region; region = region->m_next.get()) {
                if (!text.isEmpty())
                    text.append(' ');
                text += "dashboard-region(";
                text += region->m_label;
                if (region->m_isCircle)
                    text += " circle";
                else if (region->m_isRectangle)
                    text += " rectangle";
                else
                    break;
                if (region->top()->m_type == CSS_IDENT && region->top()->getIdent() == CSSValueInvalid) {
                    ASSERT(region->right()->m_type == CSS_IDENT);
                    ASSERT(region->bottom()->m_type == CSS_IDENT);
                    ASSERT(region->left()->m_type == CSS_IDENT);
                    ASSERT(region->right()->getIdent() == CSSValueInvalid);
                    ASSERT(region->bottom()->getIdent() == CSSValueInvalid);
                    ASSERT(region->left()->getIdent() == CSSValueInvalid);
                } else {
                    text.append(' ');
                    text += region->top()->cssText() + " ";
                    text += region->right()->cssText() + " ";
                    text += region->bottom()->cssText() + " ";
                    text += region->left()->cssText();
                }
                text += ")";
            }
            break;
#endif
        case CSS_PARSER_VARIABLE_FUNCTION_SYNTAX:
            text = "-webkit-var(";
            text += m_value.string;
            text += ")";
            break;
        case CSS_PARSER_OPERATOR: {
            char c = static_cast<char>(m_value.ident);
            text = String(&c, 1U);
            break;
        }
        case CSS_PARSER_IDENTIFIER:
            text = quoteStringIfNeeded(m_value.string);
            break;
    }
    return text;
}

CSSParserValue CSSPrimitiveValue::parserValue() const
{
    // We only have to handle a subset of types.
    CSSParserValue value;
    value.id = 0;
    value.isInt = false;
    value.unit = CSSPrimitiveValue::CSS_IDENT;
    switch (m_type) {
        case CSS_NUMBER:
        case CSS_PERCENTAGE:
        case CSS_EMS:
        case CSS_EXS:
        case CSS_REMS:
        case CSS_PX:
        case CSS_CM:
        case CSS_MM:
        case CSS_IN:
        case CSS_PT:
        case CSS_PC:
        case CSS_DEG:
        case CSS_RAD:
        case CSS_GRAD:
        case CSS_MS:
        case CSS_S:
        case CSS_HZ:
        case CSS_KHZ:
        case CSS_DIMENSION:
        case CSS_TURN:
            value.fValue = m_value.num;
            value.unit = m_type;
            break;
        case CSS_STRING:
        case CSS_URI:
        case CSS_PARSER_VARIABLE_FUNCTION_SYNTAX:
        case CSS_PARSER_HEXCOLOR:
            value.string.characters = const_cast<UChar*>(m_value.string->characters());
            value.string.length = m_value.string->length();
            value.unit = m_type;
            break;
        case CSS_IDENT: {
            value.id = m_value.ident;
            String name = valueOrPropertyName(m_value.ident);
            value.string.characters = const_cast<UChar*>(name.characters());
            value.string.length = name.length();
            break;
        }
        case CSS_PARSER_OPERATOR:
            value.iValue = m_value.ident;
            value.unit = CSSParserValue::Operator;
            break;
        case CSS_PARSER_INTEGER:
            value.fValue = m_value.num;
            value.unit = CSSPrimitiveValue::CSS_NUMBER;
            value.isInt = true;
            break;
        case CSS_PARSER_IDENTIFIER:
            value.string.characters = const_cast<UChar*>(m_value.string->characters());
            value.string.length = m_value.string->length();
            value.unit = CSSPrimitiveValue::CSS_IDENT;
            break;
        case CSS_UNKNOWN:
        case CSS_ATTR:
        case CSS_COUNTER:
        case CSS_RECT:
        case CSS_RGBCOLOR:
        case CSS_PAIR:
#if ENABLE(DASHBOARD_SUPPORT)
        case CSS_DASHBOARD_REGION:
#endif
            ASSERT_NOT_REACHED();
            break;
    }
    
    return value;
}

void CSSPrimitiveValue::addSubresourceStyleURLs(ListHashSet<KURL>& urls, const CSSStyleSheet* styleSheet)
{
    if (m_type == CSS_URI)
        addSubresourceURL(urls, styleSheet->completeURL(m_value.string));
}

} // namespace WebCore
