/*
 * CSS Media Query
 *
 * Copyright (C) 2005, 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
 *           (C) 2008 Germain Garand <germain@ebooksfrance.org>
 * 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 THE AUTHOR ``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 COMPUTER, 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 "css_mediaquery.h"
#include "css_valueimpl.h"
#include "css/css_stylesheetimpl.h"
#include "css/cssparser.h"
#include "cssstyleselector.h"
#include "css/cssvalues.h"
#include "khtml_part.h"
#include "khtmlview.h"
#include "rendering/render_style.h"
#include "xml/dom_stringimpl.h"
#include <QHash>
#include <limits.h>
#include <QDesktopWidget>
#include <QColormap>

using namespace DOM;
using namespace khtml;

MediaQuery::MediaQuery(Restrictor r, const DOMString &mediaType, QList<MediaQueryExp *> *exprs)
    : m_restrictor(r)
    , m_mediaType(mediaType)
    , m_expressions(exprs)
{
    if (!m_expressions) {
        m_expressions = new QList<MediaQueryExp *>;
    }
}

MediaQuery::~MediaQuery()
{
    if (m_expressions) {
        qDeleteAll(*m_expressions);
        delete m_expressions;
    }
}

bool MediaQuery::operator==(const MediaQuery &other) const
{
    if (m_restrictor != other.m_restrictor
            || m_mediaType != other.m_mediaType
            || m_expressions->size() != other.m_expressions->size()) {
        return false;
    }

    for (int i = 0; i < m_expressions->size(); ++i) {
        MediaQueryExp *exp = m_expressions->at(i);
        MediaQueryExp *oexp = other.m_expressions->at(i);
        if (!(*exp == *oexp)) {
            return false;
        }
    }

    return true;
}

DOMString MediaQuery::cssText() const
{
    DOMString text;
    switch (m_restrictor) {
    case MediaQuery::Only:
        text += "only ";
        break;
    case MediaQuery::Not:
        text += "not ";
        break;
    case MediaQuery::None:
    default:
        break;
    }
    text += m_mediaType;
    for (int i = 0; i < m_expressions->size(); ++i) {
        MediaQueryExp *exp = m_expressions->at(i);
        text += " and (";
        text += exp->mediaFeature();
        if (exp->value()) {
            text += ": ";
            text += exp->value()->cssText();
        }
        text += ")";
    }
    return text;
}

//---------------------------------------------------------------------------

MediaQueryExp::MediaQueryExp(const DOMString &mediaFeature, ValueList *valueList)
    : m_mediaFeature(mediaFeature)
    , m_value(nullptr)
{
    m_viewportDependent = (m_mediaFeature == "width" ||
                           m_mediaFeature == "height" ||
                           m_mediaFeature == "min-width" ||
                           m_mediaFeature == "min-height" ||
                           m_mediaFeature == "max-width" ||
                           m_mediaFeature == "max-height" ||
                           m_mediaFeature == "orientation" ||
                           m_mediaFeature == "aspect-ratio" ||
                           m_mediaFeature == "min-aspect-ratio" ||
                           m_mediaFeature == "max-aspect-ratio");
    if (valueList) {
        if (valueList->size() == 1) {
            Value *value = valueList->current();

            if (value->id != 0) {
                m_value = new CSSPrimitiveValueImpl(value->id);
            } else if (value->unit == CSSPrimitiveValue::CSS_STRING) {
                m_value = new CSSPrimitiveValueImpl(domString(value->string), (CSSPrimitiveValue::UnitTypes) value->unit);
            } else if ((value->unit >= CSSPrimitiveValue::CSS_NUMBER &&
                        value->unit <= CSSPrimitiveValue::CSS_KHZ) ||
                       value->unit == CSSPrimitiveValue::CSS_DPI  || value->unit == CSSPrimitiveValue::CSS_DPCM) {
                m_value = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit);
            }
            if (m_value) {
                m_value->ref();
            }
            valueList->next();
        } else if (valueList->size() > 1) {
            // create list of values
            // currently accepts only <integer>/<integer>

            CSSValueListImpl *list = new CSSValueListImpl();
            Value *value = nullptr;
            bool isValid = true;

            while ((value = valueList->current()) && isValid) {
                if (value->unit == Value::Operator && value->iValue == '/') {
                    list->append(new CSSPrimitiveValueImpl(DOMString("/"), CSSPrimitiveValue::CSS_STRING));
                } else if (value->unit == CSSPrimitiveValue::CSS_NUMBER) {
                    list->append(new CSSPrimitiveValueImpl(value->fValue, CSSPrimitiveValue::CSS_NUMBER));
                } else {
                    isValid = false;
                }

                value = valueList->next();
            }

            if (isValid) {
                m_value = list;
                m_value->ref();
            } else {
                delete list;
            }
        }
    }
}

MediaQueryExp::~MediaQueryExp()
{
    if (m_value) {
        m_value->deref();
    }
}

//---------------------------------------------------------------------------

// when adding features, update also m_viewportDependent test if applicable
#define CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(macro) \
    macro(color, "color") \
    macro(color_index, "color-index") \
    macro(grid, "grid") \
    macro(monochrome, "monochrome") \
    macro(height, "height") \
    macro(width, "width") \
    macro(device_aspect_ratio, "device-aspect-ratio") \
    macro(device_pixel_ratio, "-khtml-device-pixel-ratio") \
    macro(device_height, "device-height") \
    macro(device_width, "device-width") \
    macro(orientation, "orientation") \
    macro(aspect_ratio, "aspect-ratio") \
    macro(resolution, "resolution") \
    macro(scan, "scan") \
    macro(max_color, "max-color") \
    macro(max_color_index, "max-color-index") \
    macro(max_device_aspect_ratio, "max-device-aspect-ratio") \
    macro(max_device_pixel_ratio, "-khtml-max-device-pixel-ratio") \
    macro(max_device_height, "max-device-height") \
    macro(max_device_width, "max-device-width") \
    macro(max_aspect_ratio, "max-aspect-ratio") \
    macro(max_resolution, "max-resolution") \
    macro(max_height, "max-height") \
    macro(max_monochrome, "max-monochrome") \
    macro(max_width, "max-width") \
    macro(min_color, "min-color") \
    macro(min_color_index, "min-color-index") \
    macro(min_device_aspect_ratio, "min-device-aspect-ratio") \
    macro(min_device_pixel_ratio, "-khtml-min-device-pixel-ratio") \
    macro(min_device_height, "min-device-height") \
    macro(min_device_width, "min-device-width") \
    macro(min_resolution, "min-resolution") \
    macro(min_aspect_ratio, "min-aspect-ratio") \
    macro(min_height, "min-height") \
    macro(min_monochrome, "min-monochrome") \
    macro(min_width, "min-width") \
    // end of macro

enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix };

typedef bool (*EvalFunc)(CSSValueImpl *, RenderStyle *, KHTMLPart *,  MediaFeaturePrefix);
typedef QHash<DOMString, EvalFunc> FunctionMap;
static FunctionMap *gFunctionMap = nullptr;

MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult)
    : m_part(nullptr)
    , m_style(nullptr)
    , m_expResult(mediaFeatureResult)
{
}

MediaQueryEvaluator:: MediaQueryEvaluator(const DOMString &acceptedMediaType, bool mediaFeatureResult)
    : m_mediaType(acceptedMediaType)
    , m_part(nullptr)
    , m_style(nullptr)
    , m_expResult(mediaFeatureResult)
{
}

MediaQueryEvaluator:: MediaQueryEvaluator(const char *acceptedMediaType, bool mediaFeatureResult)
    : m_mediaType(acceptedMediaType)
    , m_part(nullptr)
    , m_style(nullptr)
    , m_expResult(mediaFeatureResult)
{
}

MediaQueryEvaluator:: MediaQueryEvaluator(const DOMString &acceptedMediaType, KHTMLPart *part, RenderStyle *style)
    : m_mediaType(acceptedMediaType.lower())
    , m_part(part)
    , m_style(style)
    , m_expResult(false) // doesn't matter when we have m_part and m_style
{
}

MediaQueryEvaluator::~MediaQueryEvaluator()
{
}

bool MediaQueryEvaluator::mediaTypeMatch(const DOMString &mediaTypeToMatch) const
{
    return mediaTypeToMatch.isEmpty()
           || !strcasecmp("all", mediaTypeToMatch)
           || !strcasecmp(m_mediaType, mediaTypeToMatch);
}

bool MediaQueryEvaluator::mediaTypeMatchSpecific(const char *mediaTypeToMatch) const
{
    // Like mediaTypeMatch, but without the special cases for "" and "all".
    assert(mediaTypeToMatch);
    assert(mediaTypeToMatch[0] != '\0');
    assert(strcasecmp(DOMString("all"), mediaTypeToMatch));
    return !strcasecmp(m_mediaType, mediaTypeToMatch);
}

static bool applyRestrictor(MediaQuery::Restrictor r, bool value)
{
    return r == MediaQuery::Not ? !value : value;
}

bool MediaQueryEvaluator::eval(const MediaListImpl *mediaList, CSSStyleSelector *styleSelector) const
{
    if (!mediaList) {
        return true;
    }

    const QList<MediaQuery *> *queries = mediaList->mediaQueries();
    if (!queries->size()) {
        return true;    // empty query list evaluates to true
    }

    // iterate over queries, stop if any of them eval to true (OR semantics)
    bool result = false;
    for (int i = 0; i < queries->size() && !result; ++i) {
        MediaQuery *query = queries->at(i);
        if (mediaTypeMatch(query->mediaType())) {
            const QList<MediaQueryExp *> *exps = query->expressions();
            // iterate through expressions, stop if any of them eval to false
            // (AND semantics)
            int j = 0;
            for (; j < exps->size(); ++j) {
                bool exprResult = eval(exps->at(j));
                if (styleSelector && exps->at(j)->isViewportDependent()) {
                    styleSelector->addViewportDependentMediaQueryResult(exps->at(j), exprResult);
                }
                if (!exprResult) {
                    break;
                }
            }

            // assume true if we are at the end of the list,
            // otherwise assume false
            result = applyRestrictor(query->restrictor(), exps->size() == j);
        } else {
            result = applyRestrictor(query->restrictor(), false);
        }
    }

    return result;
}

static bool parseAspectRatio(CSSValueImpl *value, int &h, int &v)
{
    if (value->isValueList()) {
        CSSValueListImpl *valueList = static_cast<CSSValueListImpl *>(value);
        if (valueList->length() == 3) {
            CSSValueImpl *i0 = valueList->item(0);
            CSSValueImpl *i1 = valueList->item(1);
            CSSValueImpl *i2 = valueList->item(2);
            if (i0->isPrimitiveValue() && static_cast<CSSPrimitiveValueImpl *>(i0)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER
                    && i1->isPrimitiveValue() && static_cast<CSSPrimitiveValueImpl *>(i1)->primitiveType() == CSSPrimitiveValue::CSS_STRING
                    && i2->isPrimitiveValue() && static_cast<CSSPrimitiveValueImpl *>(i2)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) {
                DOMString str = static_cast<CSSPrimitiveValueImpl *>(i1)->getStringValue();
                if (!str.isNull() && str.length() == 1 && str[0] == '/') {
                    h = (int)static_cast<CSSPrimitiveValueImpl *>(i0)->floatValue(CSSPrimitiveValue::CSS_NUMBER);
                    v = (int)static_cast<CSSPrimitiveValueImpl *>(i2)->floatValue(CSSPrimitiveValue::CSS_NUMBER);
                    return true;
                }
            }
        }
    }
    return false;
}

template<typename T>
bool compareValue(T a, T b, MediaFeaturePrefix op)
{
    switch (op) {
    case MinPrefix:
        return a >= b;
    case MaxPrefix:
        return a <= b;
    case NoPrefix:
        return a == b;
    }
    return false;
}

static bool numberValue(CSSValueImpl *value, float &result)
{
    if (value->isPrimitiveValue()
            && static_cast<CSSPrimitiveValueImpl *>(value)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) {
        result = static_cast<CSSPrimitiveValueImpl *>(value)->floatValue(CSSPrimitiveValue::CSS_NUMBER);
        return true;
    }
    return false;
}

static bool color_indexMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    KHTMLPart *rootPart = part;
    while (rootPart->parentPart()) {
        rootPart = rootPart->parentPart();
    }
    DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
    QPaintDevice *pd = doc->paintDevice();
    bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
    unsigned int numColors = 0;
    if (printing) {
        numColors = pd->colorCount();
    } else {
        int sn = QApplication::desktop()->screenNumber(rootPart->view());
        numColors = QApplication::desktop()->screen(sn)->colorCount();
    }
    if (numColors == INT_MAX) {
        numColors = UINT_MAX;
    }
    if (value) {
        float number = 0;
        return numberValue(value, number) && compareValue(numColors, static_cast<unsigned int>(number), op);
    }

    return numColors;
}

static bool colorMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    KHTMLPart *rootPart = part;
    while (rootPart->parentPart()) {
        rootPart = rootPart->parentPart();
    }
    DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
    QPaintDevice *pd = doc->paintDevice();
    bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
    int bitsPerComponent = 0;
    if (printing) {
        if (pd->colorCount() > 2) {
            bitsPerComponent = pd->depth() / 3;
        }
        // assume printer is either b&w or color.
    } else {
        int sn = QApplication::desktop()->screenNumber(rootPart->view());
        if (QColormap::instance(sn).mode() != QColormap::Gray) {
            bitsPerComponent = QApplication::desktop()->screen(sn)->depth() / 3;
        }
    }
    if (value && bitsPerComponent) {
        float number = 0;
        return numberValue(value, number) && compareValue(bitsPerComponent, static_cast<int>(number), op);
    }
    return bitsPerComponent;
}

static bool monochromeMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    KHTMLPart *rootPart = part;
    while (rootPart->parentPart()) {
        rootPart = rootPart->parentPart();
    }
    DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
    QPaintDevice *pd = doc->paintDevice();
    bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
    int depth = 0;
    if (printing) {
        if (pd->colorCount() < 2) {
            depth = 1;
        }
        // assume printer is either b&w or color.
    } else {
        int sn = QApplication::desktop()->screenNumber(rootPart->view());
        if (QApplication::desktop()->screen(sn)->depth() == 1) {
            depth = 1;
        } else if (QColormap::instance(sn).mode() == QColormap::Gray) {
            depth = QApplication::desktop()->screen(sn)->depth();
        }
    }
    if (value) {
        float number = 0;
        return numberValue(value, number) && compareValue(depth, static_cast<int>(number), op);
    }
    return depth;
}

static bool device_aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    if (value) {
        KHTMLPart *rootPart = part;
        while (rootPart->parentPart()) {
            rootPart = rootPart->parentPart();
        }
        DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
        QPaintDevice *pd = doc->paintDevice();
        bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
        QRect sg;
        int h = 0, v = 0;
        if (printing) {
            sg = QRect(0, 0, pd->width(), pd->height());
        } else {
            sg = QApplication::desktop()->screen(QApplication::desktop()->screenNumber(rootPart->view()))->rect();
        }
        if (parseAspectRatio(value, h, v)) {
            return v != 0  && compareValue(sg.width() * v, sg.height() * h, op);
        }
        return false;
    }

    // ({,min-,max-}device-aspect-ratio)
    // assume if we have a device, its aspect ratio is non-zero
    return true;
}

static bool aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    if (value) {
        KHTMLPart *rootPart = part;
        while (rootPart->parentPart()) {
            rootPart = rootPart->parentPart();
        }
        DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
        QPaintDevice *pd = doc->paintDevice();
        bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
        QSize vs;
        int h = 0, v = 0;
        if (printing) {
            vs = QSize(pd->width(), pd->height());
        } else {
            vs = QSize(part->view()->visibleWidth(), part->view()->visibleHeight());
        }
        if (parseAspectRatio(value, h, v)) {
            return v != 0  && compareValue(vs.width() * v, vs.height() * h, op);
        }
        return false;
    }
    // ({,min-,max-}aspect-ratio)
    // assume if we have a viewport, its aspect ratio is non-zero
    return true;
}

static bool orientationMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    if (value) {
        CSSPrimitiveValueImpl *pv = static_cast<CSSPrimitiveValueImpl *>(value);
        if (!value->isPrimitiveValue() || pv->primitiveType() != CSSPrimitiveValue::CSS_IDENT ||
                (pv->getIdent() != CSS_VAL_PORTRAIT && pv->getIdent() != CSS_VAL_LANDSCAPE)) {
            return false;
        }

        KHTMLPart *rootPart = part;
        while (rootPart->parentPart()) {
            rootPart = rootPart->parentPart();
        }
        DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
        QPaintDevice *pd = doc->paintDevice();
        bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
        if (printing) {
            if (pd->width() > pd->height()) {
                return (pv->getIdent() == CSS_VAL_LANDSCAPE);
            }
        } else {
            if (part->view()->visibleWidth() > part->view()->visibleHeight()) {
                return (pv->getIdent() == CSS_VAL_LANDSCAPE);
            }
        }
        return (pv->getIdent() == CSS_VAL_PORTRAIT);
    }
    return false;
}

static bool device_pixel_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part, MediaFeaturePrefix op)
{
    if (value) {
        return value->isPrimitiveValue() && compareValue(part->zoomFactor() / 100.0, static_cast<CSSPrimitiveValueImpl *>(value)->floatValue(), op);
    }

    return part->zoomFactor() != 0;
}

static bool gridMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart * /*part*/,  MediaFeaturePrefix op)
{
    // if output device is bitmap, grid: 0 == true
    // assume we have bitmap device
    float number;
    if (value && numberValue(value, number)) {
        return compareValue(static_cast<int>(number), 0, op);
    }
    return false;
}

// for printing media, we'll make the approximation that the device height == the paged box's height
static bool device_heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix op)
{
    if (value) {
        KHTMLPart *rootPart = part;
        while (rootPart->parentPart()) {
            rootPart = rootPart->parentPart();
        }
        DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
        QPaintDevice *pd = doc->paintDevice();
        bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
        int height;
        if (printing) {
            height = pd->height();
        } else {
            height = QApplication::desktop()->screen(QApplication::desktop()->screenNumber(rootPart->view()))->rect().height();
            doc = static_cast<DOM::DocumentImpl *>(part->document().handle());
        }
        int logicalDpiY = doc->logicalDpiY();
        return value->isPrimitiveValue() && compareValue(height, static_cast<CSSPrimitiveValueImpl *>(value)->computeLength(style, style, logicalDpiY), op);
    }
    // ({,min-,max-}device-height)
    // assume if we have a device, assume non-zero
    return true;
}

static bool device_widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix op)
{
    if (value) {
        KHTMLPart *rootPart = part;
        while (rootPart->parentPart()) {
            rootPart = rootPart->parentPart();
        }
        DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
        QPaintDevice *pd = doc->paintDevice();
        bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
        int width;
        if (printing) {
            width = pd->width();
        } else {
            width = QApplication::desktop()->screen(QApplication::desktop()->screenNumber(rootPart->view()))->rect().width();
            doc = static_cast<DOM::DocumentImpl *>(part->document().handle());
        }
        int logicalDpiY = doc->logicalDpiY();
        return value->isPrimitiveValue() && compareValue(width, static_cast<CSSPrimitiveValueImpl *>(value)->computeLength(style, style, logicalDpiY), op);
    }
    // ({,min-,max-}device-width)
    // assume if we have a device, assume non-zero
    return true;
}

// cf. https://www.w3.org/TR/css3-mediaqueries/#device-width
// "For continuous media, this is the width of the viewport (as described by CSS2, section 9.1.1 [CSS2]).
//  For paged media, this is the width of the page box (as described by CSS2, section 13.2 [CSS2])"

static bool widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix op)
{
    KHTMLPart *rootPart = part;
    while (rootPart->parentPart()) {
        rootPart = rootPart->parentPart();
    }
    DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
    QPaintDevice *pd = doc->paintDevice();
    bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
    int width;
    if (printing) {
        width = pd->width();
    } else {
        width = part->view()->visibleWidth();
        doc = static_cast<DOM::DocumentImpl *>(part->document().handle());
    }
    int logicalDpiY = doc->logicalDpiY();
    if (value) {
        return value->isPrimitiveValue() && compareValue(width, static_cast<CSSPrimitiveValueImpl *>(value)->computeLength(style, style, logicalDpiY), op);
    }

    return width > 0;
}

static bool heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix op)
{
    KHTMLPart *rootPart = part;
    while (rootPart->parentPart()) {
        rootPart = rootPart->parentPart();
    }
    DOM::DocumentImpl *doc =  static_cast<DOM::DocumentImpl *>(rootPart->document().handle());
    QPaintDevice *pd = doc->paintDevice();
    bool printing = pd ? (pd->devType() == QInternal::Printer) : false;
    int height;
    if (printing) {
        height = pd->height();
    } else {
        height = part->view()->visibleHeight();
        doc = static_cast<DOM::DocumentImpl *>(part->document().handle());
    }
    int logicalDpiY = doc->logicalDpiY();
    if (value) {
        return value->isPrimitiveValue() && compareValue(height, static_cast<CSSPrimitiveValueImpl *>(value)->computeLength(style, style, logicalDpiY), op);
    }

    return height > 0;
}

static bool resolutionMediaFeatureEval(CSSValueImpl *value, RenderStyle *, KHTMLPart *part,  MediaFeaturePrefix op)
{
    DOM::DocumentImpl *d = static_cast<DOM::DocumentImpl *>(part->document().handle());
    int logicalDpiY = d ? d->logicalDpiY() : 0;

    if (value && logicalDpiY) {
        return value->isPrimitiveValue() && compareValue(logicalDpiY, static_cast<CSSPrimitiveValueImpl *>(value)->getDPIResolution(), op);
    }

    return logicalDpiY != 0;
}

static bool scanMediaFeatureEval(CSSValueImpl * /*value*/, RenderStyle *, KHTMLPart * /*part*/,  MediaFeaturePrefix)
{
    // no support for tv media type.
    return false;
}

// rest of the functions are trampolines which set the prefix according to the media feature expression used

static bool min_color_indexMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return color_indexMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_color_indexMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return color_indexMediaFeatureEval(value, style, part, MinPrefix);
}

static bool min_colorMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return colorMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_colorMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return colorMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_monochromeMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return monochromeMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_monochromeMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return monochromeMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_device_aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_aspect_ratioMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_device_aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_aspect_ratioMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return aspect_ratioMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_aspect_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return aspect_ratioMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_device_pixel_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_pixel_ratioMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_device_pixel_ratioMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_pixel_ratioMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return heightMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return heightMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return widthMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return widthMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_device_heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_heightMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_device_heightMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_heightMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_device_widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_widthMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_device_widthMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return device_widthMediaFeatureEval(value, style, part, MaxPrefix);
}

static bool min_resolutionMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return resolutionMediaFeatureEval(value, style, part, MinPrefix);
}

static bool max_resolutionMediaFeatureEval(CSSValueImpl *value, RenderStyle *style, KHTMLPart *part,  MediaFeaturePrefix /*op*/)
{
    return resolutionMediaFeatureEval(value, style, part, MaxPrefix);
}

static void createFunctionMap()
{
    // Create the table.
    gFunctionMap = new FunctionMap;
#define ADD_TO_FUNCTIONMAP(name, str)  \
    gFunctionMap->insert(DOMString(str), name##MediaFeatureEval);
    CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP);
#undef ADD_TO_FUNCTIONMAP
}

void MediaQueryEvaluator::cleanup() // static
{
    delete gFunctionMap;
    gFunctionMap = nullptr;
}

bool MediaQueryEvaluator::eval(const MediaQueryExp *expr) const
{
    if (!m_part || !m_style) {
        return m_expResult;
    }

    if (!gFunctionMap) {
        createFunctionMap();
    }

    // call the media feature evaluation function. Assume no prefix
    // and let trampoline functions override the prefix if prefix is
    // used
    FunctionMap::ConstIterator func = gFunctionMap->constFind(expr->mediaFeature());
    if (func != gFunctionMap->constEnd()) {
        return (*func.value())(expr->value(), m_style, m_part, NoPrefix);
    }

    return false;
}
