/*
 * Copyright (C) 2012 Igalia S.L.
 *
 * 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 "WebKitHitTestResult.h"

#include "WebHitTestResultData.h"
#include "WebKitHitTestResultPrivate.h"
#include <glib/gi18n-lib.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>

using namespace WebKit;

/**
 * SECTION: WebKitHitTestResult
 * @Short_description: Result of a Hit Test
 * @Title: WebKitHitTestResult
 * @See_also: #WebKitWebView
 *
 * A Hit Test is an operation to get context information about a given
 * point in a #WebKitWebView. #WebKitHitTestResult represents the
 * result of a Hit Test. It provides context information about what is
 * at the coordinates of the Hit Test, such as if there's a link,
 * an image or a media.
 *
 * You can get the context of the HitTestResult with
 * webkit_hit_test_result_get_context() that returns a bitmask of
 * #WebKitHitTestResultContext flags. You can also use
 * webkit_hit_test_result_context_is_link(), webkit_hit_test_result_context_is_image() and
 * webkit_hit_test_result_context_is_media() to determine whether there's
 * a link, image or a media element at the coordinates of the Hit Test.
 * Note that it's possible that several #WebKitHitTestResultContext flags
 * are active at the same time, for example if there's a link containing an image.
 *
 * When the mouse is moved over a #WebKitWebView a Hit Test is performed
 * for the mouse coordinates and #WebKitWebView::mouse-target-changed
 * signal is emitted with a #WebKitHitTestResult.
 *
 */

enum {
    PROP_0,

    PROP_CONTEXT,
    PROP_LINK_URI,
    PROP_LINK_TITLE,
    PROP_LINK_LABEL,
    PROP_IMAGE_URI,
    PROP_MEDIA_URI
};

struct _WebKitHitTestResultPrivate {
    unsigned int context;
    CString linkURI;
    CString linkTitle;
    CString linkLabel;
    CString imageURI;
    CString mediaURI;
};

WEBKIT_DEFINE_TYPE(WebKitHitTestResult, webkit_hit_test_result, G_TYPE_OBJECT)

static void webkitHitTestResultGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
{
    WebKitHitTestResult* hitTestResult = WEBKIT_HIT_TEST_RESULT(object);

    switch (propId) {
    case PROP_CONTEXT:
        g_value_set_uint(value, webkit_hit_test_result_get_context(hitTestResult));
        break;
    case PROP_LINK_URI:
        g_value_set_string(value, webkit_hit_test_result_get_link_uri(hitTestResult));
        break;
    case PROP_LINK_TITLE:
        g_value_set_string(value, webkit_hit_test_result_get_link_title(hitTestResult));
        break;
    case PROP_LINK_LABEL:
        g_value_set_string(value, webkit_hit_test_result_get_link_label(hitTestResult));
        break;
    case PROP_IMAGE_URI:
        g_value_set_string(value, webkit_hit_test_result_get_image_uri(hitTestResult));
        break;
    case PROP_MEDIA_URI:
        g_value_set_string(value, webkit_hit_test_result_get_media_uri(hitTestResult));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
    }
}

static void webkitHitTestResultSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec)
{
    WebKitHitTestResult* hitTestResult = WEBKIT_HIT_TEST_RESULT(object);

    switch (propId) {
    case PROP_CONTEXT:
        hitTestResult->priv->context = g_value_get_uint(value);
        break;
    case PROP_LINK_URI:
        hitTestResult->priv->linkURI = g_value_get_string(value);
        break;
    case PROP_LINK_TITLE:
        hitTestResult->priv->linkTitle = g_value_get_string(value);
        break;
    case PROP_LINK_LABEL:
        hitTestResult->priv->linkLabel = g_value_get_string(value);
        break;
    case PROP_IMAGE_URI:
        hitTestResult->priv->imageURI = g_value_get_string(value);
        break;
    case PROP_MEDIA_URI:
        hitTestResult->priv->mediaURI = g_value_get_string(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
    }
}

static void webkit_hit_test_result_class_init(WebKitHitTestResultClass* hitTestResultClass)
{
    GObjectClass* objectClass = G_OBJECT_CLASS(hitTestResultClass);
    objectClass->get_property = webkitHitTestResultGetProperty;
    objectClass->set_property = webkitHitTestResultSetProperty;

    GParamFlags paramFlags = static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * WebKitHitTestResult:context:
     *
     * Bitmask of #WebKitHitTestResultContext flags representing
     * the context of the #WebKitHitTestResult.
     */
    g_object_class_install_property(objectClass,
                                    PROP_CONTEXT,
                                    g_param_spec_uint("context",
                                                      _("Context"),
                                                      _("Flags with the context of the WebKitHitTestResult"),
                                                      0, G_MAXUINT, 0,
                                                      paramFlags));

    /**
     * WebKitHitTestResult:link-uri:
     *
     * The URI of the link if flag %WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
     * is present in #WebKitHitTestResult:context
     */
    g_object_class_install_property(objectClass,
                                    PROP_LINK_URI,
                                    g_param_spec_string("link-uri",
                                                        _("Link URI"),
                                                        _("The link URI"),
                                                        0,
                                                        paramFlags));
    /**
     * WebKitHitTestResult:link-title:
     *
     * The title of the link if flag %WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
     * is present in #WebKitHitTestResult:context
     */
    g_object_class_install_property(objectClass,
                                    PROP_LINK_TITLE,
                                    g_param_spec_string("link-title",
                                                        _("Link Title"),
                                                        _("The link title"),
                                                        0,
                                                        paramFlags));
    /**
     * WebKitHitTestResult:link-label:
     *
     * The label of the link if flag %WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
     * is present in #WebKitHitTestResult:context
     */
    g_object_class_install_property(objectClass,
                                    PROP_LINK_LABEL,
                                    g_param_spec_string("link-label",
                                                        _("Link Label"),
                                                        _("The link label"),
                                                        0,
                                                        paramFlags));
    /**
     * WebKitHitTestResult:image-uri:
     *
     * The URI of the image if flag %WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE
     * is present in #WebKitHitTestResult:context
     */
    g_object_class_install_property(objectClass,
                                    PROP_IMAGE_URI,
                                    g_param_spec_string("image-uri",
                                                        _("Image URI"),
                                                        _("The image URI"),
                                                        0,
                                                        paramFlags));
    /**
     * WebKitHitTestResult:media-uri:
     *
     * The URI of the media if flag %WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA
     * is present in #WebKitHitTestResult:context
     */
    g_object_class_install_property(objectClass,
                                    PROP_MEDIA_URI,
                                    g_param_spec_string("media-uri",
                                                        _("Media URI"),
                                                        _("The media URI"),
                                                        0,
                                                        paramFlags));
}

WebKitHitTestResult* webkitHitTestResultCreate(const WebHitTestResultData& hitTestResult)
{
    unsigned context = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT;

    if (!hitTestResult.absoluteLinkURL.isEmpty())
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK;

    if (!hitTestResult.absoluteImageURL.isEmpty())
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE;

    if (!hitTestResult.absoluteMediaURL.isEmpty())
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA;

    if (hitTestResult.isContentEditable)
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE;

    if (hitTestResult.isScrollbar)
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR;

    if (hitTestResult.isSelected)
        context |= WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION;

    return WEBKIT_HIT_TEST_RESULT(g_object_new(WEBKIT_TYPE_HIT_TEST_RESULT,
        "context", context,
        "link-uri", context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK ? hitTestResult.absoluteLinkURL.utf8().data() : nullptr,
        "image-uri", context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE ? hitTestResult.absoluteImageURL.utf8().data() : nullptr,
        "media-uri", context & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA ? hitTestResult.absoluteMediaURL.utf8().data() : nullptr,
        "link-title", !hitTestResult.linkTitle.isEmpty() ? hitTestResult.linkTitle.utf8().data() : nullptr,
        "link-label", !hitTestResult.linkLabel.isEmpty() ? hitTestResult.linkLabel.utf8().data() : nullptr,
        nullptr));
}

static bool stringIsEqualToCString(const String& string, const CString& cString)
{
    return ((string.isEmpty() && cString.isNull()) || (string.utf8() == cString));
}

bool webkitHitTestResultCompare(WebKitHitTestResult* hitTestResult, const WebHitTestResultData& webHitTestResult)
{
    WebKitHitTestResultPrivate* priv = hitTestResult->priv;
    return webHitTestResult.isContentEditable == webkit_hit_test_result_context_is_editable(hitTestResult)
        && webHitTestResult.isScrollbar == webkit_hit_test_result_context_is_scrollbar(hitTestResult)
        && webHitTestResult.isSelected == webkit_hit_test_result_context_is_selection(hitTestResult)
        && stringIsEqualToCString(webHitTestResult.absoluteLinkURL, priv->linkURI)
        && stringIsEqualToCString(webHitTestResult.linkTitle, priv->linkTitle)
        && stringIsEqualToCString(webHitTestResult.linkLabel, priv->linkLabel)
        && stringIsEqualToCString(webHitTestResult.absoluteImageURL, priv->imageURI)
        && stringIsEqualToCString(webHitTestResult.absoluteMediaURL, priv->mediaURI);
}

/**
 * webkit_hit_test_result_get_context:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:context property.
 *
 * Returns: a bitmask of #WebKitHitTestResultContext flags
 */
guint webkit_hit_test_result_get_context(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->context;
}

/**
 * webkit_hit_test_result_context_is_link:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's a link element in the coordinates of the Hit Test,
 *    or %FALSE otherwise
 */
gboolean webkit_hit_test_result_context_is_link(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK;
}

/**
 * webkit_hit_test_result_context_is_image:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's an image element in the coordinates of the Hit Test,
 *    or %FALSE otherwise
 */
gboolean webkit_hit_test_result_context_is_image(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE;
}

/**
 * webkit_hit_test_result_context_is_media:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's a media element in the coordinates of the Hit Test,
 *    or %FALSE otherwise
 */
gboolean webkit_hit_test_result_context_is_media(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA;
}

/**
 * webkit_hit_test_result_context_is_editable:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's an editable element at the coordinates of the @hit_test_result,
 *    or %FALSE otherwise
 */
gboolean webkit_hit_test_result_context_is_editable(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE;
}

/**
 * webkit_hit_test_result_context_is_selection:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's a selected element at the coordinates of the @hit_test_result,
 *    or %FALSE otherwise
 *
 * Since: 2.8
 */
gboolean webkit_hit_test_result_context_is_selection(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION;
}

/**
 * webkit_hit_test_result_get_link_uri:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:link-uri property.
 *
 * Returns: the URI of the link element in the coordinates of the Hit Test,
 *    or %NULL if there isn't a link element in @hit_test_result context
 */
const gchar* webkit_hit_test_result_get_link_uri(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->linkURI.data();
}

/**
 * webkit_hit_test_result_get_link_title:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:link-title property.
 *
 * Returns: the title of the link element in the coordinates of the Hit Test,
 *    or %NULL if there isn't a link element in @hit_test_result context or the
 *    link element doesn't have a title
 */
const gchar* webkit_hit_test_result_get_link_title(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->linkTitle.data();
}

/**
 * webkit_hit_test_result_get_link_label:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:link-label property.
 *
 * Returns: the label of the link element in the coordinates of the Hit Test,
 *    or %NULL if there isn't a link element in @hit_test_result context or the
 *    link element doesn't have a label
 */
const gchar* webkit_hit_test_result_get_link_label(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->linkLabel.data();
}

/**
 * webkit_hit_test_result_get_image_uri:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:image-uri property.
 *
 * Returns: the URI of the image element in the coordinates of the Hit Test,
 *    or %NULL if there isn't an image element in @hit_test_result context
 */
const gchar* webkit_hit_test_result_get_image_uri(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->imageURI.data();
}

/**
 * webkit_hit_test_result_get_media_uri:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets the value of the #WebKitHitTestResult:media-uri property.
 *
 * Returns: the URI of the media element in the coordinates of the Hit Test,
 *    or %NULL if there isn't a media element in @hit_test_result context
 */
const gchar* webkit_hit_test_result_get_media_uri(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), 0);

    return hitTestResult->priv->mediaURI.data();
}

/**
 * webkit_hit_test_result_context_is_scrollbar:
 * @hit_test_result: a #WebKitHitTestResult
 *
 * Gets whether %WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR flag is present in
 * #WebKitHitTestResult:context.
 *
 * Returns: %TRUE if there's a scrollbar element at the coordinates of the @hit_test_result,
 *    or %FALSE otherwise
 */
gboolean webkit_hit_test_result_context_is_scrollbar(WebKitHitTestResult* hitTestResult)
{
    g_return_val_if_fail(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult), FALSE);

    return hitTestResult->priv->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR;
}
