/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER 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 "config.h"
#include "AccessibilityUIElement.h"

#include "WebAccessibilityObject.h"
#include "WebCString.h"
#include "WebString.h"
#include <wtf/Assertions.h>

using namespace WebKit;
using namespace std;

// Map role value to string, matching Safari/Mac platform implementation to
// avoid rebaselining layout tests.
static string roleToString(WebAccessibilityRole role)
{
    string result = "AXRole: AX";
    switch (role) {
    case WebAccessibilityRoleButton:
        return result.append("Button");
    case WebAccessibilityRoleRadioButton:
        return result.append("RadioButton");
    case WebAccessibilityRoleCheckBox:
        return result.append("CheckBox");
    case WebAccessibilityRoleSlider:
        return result.append("Slider");
    case WebAccessibilityRoleTabGroup:
        return result.append("TabGroup");
    case WebAccessibilityRoleTextField:
        return result.append("TextField");
    case WebAccessibilityRoleStaticText:
        return result.append("StaticText");
    case WebAccessibilityRoleTextArea:
        return result.append("TextArea");
    case WebAccessibilityRoleScrollArea:
        return result.append("ScrollArea");
    case WebAccessibilityRolePopUpButton:
        return result.append("PopUpButton");
    case WebAccessibilityRoleMenuButton:
        return result.append("MenuButton");
    case WebAccessibilityRoleTable:
        return result.append("Table");
    case WebAccessibilityRoleApplication:
        return result.append("Application");
    case WebAccessibilityRoleGroup:
        return result.append("Group");
    case WebAccessibilityRoleRadioGroup:
        return result.append("RadioGroup");
    case WebAccessibilityRoleList:
        return result.append("List");
    case WebAccessibilityRoleScrollBar:
        return result.append("ScrollBar");
    case WebAccessibilityRoleValueIndicator:
        return result.append("ValueIndicator");
    case WebAccessibilityRoleImage:
        return result.append("Image");
    case WebAccessibilityRoleMenuBar:
        return result.append("MenuBar");
    case WebAccessibilityRoleMenu:
        return result.append("Menu");
    case WebAccessibilityRoleMenuItem:
        return result.append("MenuItem");
    case WebAccessibilityRoleColumn:
        return result.append("Column");
    case WebAccessibilityRoleRow:
        return result.append("Row");
    case WebAccessibilityRoleToolbar:
        return result.append("Toolbar");
    case WebAccessibilityRoleBusyIndicator:
        return result.append("BusyIndicator");
    case WebAccessibilityRoleProgressIndicator:
        return result.append("ProgressIndicator");
    case WebAccessibilityRoleWindow:
        return result.append("Window");
    case WebAccessibilityRoleDrawer:
        return result.append("Drawer");
    case WebAccessibilityRoleSystemWide:
        return result.append("SystemWide");
    case WebAccessibilityRoleOutline:
        return result.append("Outline");
    case WebAccessibilityRoleIncrementor:
        return result.append("Incrementor");
    case WebAccessibilityRoleBrowser:
        return result.append("Browser");
    case WebAccessibilityRoleComboBox:
        return result.append("ComboBox");
    case WebAccessibilityRoleSplitGroup:
        return result.append("SplitGroup");
    case WebAccessibilityRoleSplitter:
        return result.append("Splitter");
    case WebAccessibilityRoleColorWell:
        return result.append("ColorWell");
    case WebAccessibilityRoleGrowArea:
        return result.append("GrowArea");
    case WebAccessibilityRoleSheet:
        return result.append("Sheet");
    case WebAccessibilityRoleHelpTag:
        return result.append("HelpTag");
    case WebAccessibilityRoleMatte:
        return result.append("Matte");
    case WebAccessibilityRoleRuler:
        return result.append("Ruler");
    case WebAccessibilityRoleRulerMarker:
        return result.append("RulerMarker");
    case WebAccessibilityRoleLink:
        return result.append("Link");
    case WebAccessibilityRoleDisclosureTriangle:
        return result.append("DisclosureTriangle");
    case WebAccessibilityRoleGrid:
        return result.append("Grid");
    case WebAccessibilityRoleCell:
        return result.append("Cell");
    case WebAccessibilityRoleColumnHeader:
        return result.append("ColumnHeader");
    case WebAccessibilityRoleRowHeader:
        return result.append("RowHeader");
    case WebAccessibilityRoleWebCoreLink:
        // Maps to Link role.
        return result.append("Link");
    case WebAccessibilityRoleImageMapLink:
        return result.append("ImageMapLink");
    case WebAccessibilityRoleImageMap:
        return result.append("ImageMap");
    case WebAccessibilityRoleListMarker:
        return result.append("ListMarker");
    case WebAccessibilityRoleWebArea:
        return result.append("WebArea");
    case WebAccessibilityRoleHeading:
        return result.append("Heading");
    case WebAccessibilityRoleListBox:
        return result.append("ListBox");
    case WebAccessibilityRoleListBoxOption:
        return result.append("ListBoxOption");
    case WebAccessibilityRoleTableHeaderContainer:
        return result.append("TableHeaderContainer");
    case WebAccessibilityRoleDefinitionListTerm:
        return result.append("DefinitionListTerm");
    case WebAccessibilityRoleDefinitionListDefinition:
        return result.append("DefinitionListDefinition");
    case WebAccessibilityRoleAnnotation:
        return result.append("Annotation");
    case WebAccessibilityRoleSliderThumb:
        return result.append("SliderThumb");
    case WebAccessibilityRoleLandmarkApplication:
        return result.append("LandmarkApplication");
    case WebAccessibilityRoleLandmarkBanner:
        return result.append("LandmarkBanner");
    case WebAccessibilityRoleLandmarkComplementary:
        return result.append("LandmarkComplementary");
    case WebAccessibilityRoleLandmarkContentInfo:
        return result.append("LandmarkContentInfo");
    case WebAccessibilityRoleLandmarkMain:
        return result.append("LandmarkMain");
    case WebAccessibilityRoleLandmarkNavigation:
        return result.append("LandmarkNavigation");
    case WebAccessibilityRoleLandmarkSearch:
        return result.append("LandmarkSearch");
    case WebAccessibilityRoleApplicationLog:
        return result.append("ApplicationLog");
    case WebAccessibilityRoleApplicationMarquee:
        return result.append("ApplicationMarquee");
    case WebAccessibilityRoleApplicationStatus:
        return result.append("ApplicationStatus");
    case WebAccessibilityRoleApplicationTimer:
        return result.append("ApplicationTimer");
    case WebAccessibilityRoleDocument:
        return result.append("Document");
    case WebAccessibilityRoleDocumentArticle:
        return result.append("DocumentArticle");
    case WebAccessibilityRoleDocumentNote:
        return result.append("DocumentNote");
    case WebAccessibilityRoleDocumentRegion:
        return result.append("DocumentRegion");
    case WebAccessibilityRoleUserInterfaceTooltip:
        return result.append("UserInterfaceTooltip");
    default:
        // Also matches WebAccessibilityRoleUnknown.
        return result.append("Unknown");
    }
}

string getDescription(const WebAccessibilityObject& object)
{
    string description = object.accessibilityDescription().utf8();
    return description.insert(0, "AXDescription: ");
}

string getRole(const WebAccessibilityObject& object)
{
    return roleToString(object.roleValue());
}

string getTitle(const WebAccessibilityObject& object)
{
    string title = object.title().utf8();
    return title.insert(0, "AXTitle: ");
}

string getAttributes(const WebAccessibilityObject& object)
{
    // FIXME: Concatenate all attributes of the AccessibilityObject.
    string attributes(getTitle(object));
    attributes.append("\n");
    attributes.append(getRole(object));
    attributes.append("\n");
    attributes.append(getDescription(object));
    return attributes;
}


// Collects attributes into a string, delimited by dashes. Used by all methods
// that output lists of attributes: attributesOfLinkedUIElementsCallback,
// AttributesOfChildrenCallback, etc.
class AttributesCollector {
public:
    void collectAttributes(const WebAccessibilityObject& object)
    {
        m_attributes.append("\n------------\n");
        m_attributes.append(getAttributes(object));
    }

    string attributes() const { return m_attributes; }

private:
    string m_attributes;
};

AccessibilityUIElement::AccessibilityUIElement(const WebAccessibilityObject& object, Factory* factory)
    : m_accessibilityObject(object)
    , m_factory(factory)
{

    ASSERT(factory);

    bindMethod("allAttributes", &AccessibilityUIElement::allAttributesCallback);
    bindMethod("attributesOfLinkedUIElements",
               &AccessibilityUIElement::attributesOfLinkedUIElementsCallback);
    bindMethod("attributesOfDocumentLinks",
               &AccessibilityUIElement::attributesOfDocumentLinksCallback);
    bindMethod("attributesOfChildren",
               &AccessibilityUIElement::attributesOfChildrenCallback);
    bindMethod("parameterizedAttributeNames",
               &AccessibilityUIElement::parametrizedAttributeNamesCallback);
    bindMethod("lineForIndex", &AccessibilityUIElement::lineForIndexCallback);
    bindMethod("boundsForRange", &AccessibilityUIElement::boundsForRangeCallback);
    bindMethod("stringForRange", &AccessibilityUIElement::stringForRangeCallback);
    bindMethod("childAtIndex", &AccessibilityUIElement::childAtIndexCallback);
    bindMethod("elementAtPoint", &AccessibilityUIElement::elementAtPointCallback);
    bindMethod("attributesOfColumnHeaders",
               &AccessibilityUIElement::attributesOfColumnHeadersCallback);
    bindMethod("attributesOfRowHeaders",
               &AccessibilityUIElement::attributesOfRowHeadersCallback);
    bindMethod("attributesOfColumns",
               &AccessibilityUIElement::attributesOfColumnsCallback);
    bindMethod("attributesOfRows",
               &AccessibilityUIElement::attributesOfRowsCallback);
    bindMethod("attributesOfVisibleCells",
               &AccessibilityUIElement::attributesOfVisibleCellsCallback);
    bindMethod("attributesOfHeader",
               &AccessibilityUIElement::attributesOfHeaderCallback);
    bindMethod("indexInTable", &AccessibilityUIElement::indexInTableCallback);
    bindMethod("rowIndexRange", &AccessibilityUIElement::rowIndexRangeCallback);
    bindMethod("columnIndexRange",
               &AccessibilityUIElement::columnIndexRangeCallback);
    bindMethod("cellForColumnAndRow",
               &AccessibilityUIElement::cellForColumnAndRowCallback);
    bindMethod("titleUIElement", &AccessibilityUIElement::titleUIElementCallback);
    bindMethod("setSelectedTextRange",
               &AccessibilityUIElement::setSelectedTextRangeCallback);
    bindMethod("attributeValue", &AccessibilityUIElement::attributeValueCallback);
    bindMethod("isAttributeSettable",
               &AccessibilityUIElement::isAttributeSettableCallback);
    bindMethod("isActionSupported",
               &AccessibilityUIElement::isActionSupportedCallback);
    bindMethod("parentElement", &AccessibilityUIElement::parentElementCallback);
    bindMethod("increment", &AccessibilityUIElement::incrementCallback);
    bindMethod("decrement", &AccessibilityUIElement::decrementCallback);

    bindProperty("role", &AccessibilityUIElement::roleGetterCallback);
    bindProperty("subrole", &m_subrole);
    bindProperty("title", &AccessibilityUIElement::titleGetterCallback);
    bindProperty("description",
                 &AccessibilityUIElement::descriptionGetterCallback);
    bindProperty("language", &m_language);
    bindProperty("x", &m_x);
    bindProperty("y", &m_y);
    bindProperty("width", &m_width);
    bindProperty("height", &m_height);
    bindProperty("clickPointX", &m_clickPointX);
    bindProperty("clickPointY", &m_clickPointY);
    bindProperty("intValue", &m_intValue);
    bindProperty("minValue", &m_minValue);
    bindProperty("maxValue", &m_maxValue);
    bindProperty("childrenCount",
                 &AccessibilityUIElement::childrenCountGetterCallback);
    bindProperty("insertionPointLineNumber", &m_insertionPointLineNumber);
    bindProperty("selectedTextRange", &m_selectedTextRange);
    bindProperty("isEnabled", &AccessibilityUIElement::isEnabledGetterCallback);
    bindProperty("isRequired", &m_isRequired);
    bindProperty("isSelected", &AccessibilityUIElement::isSelectedGetterCallback);
    bindProperty("valueDescription", &m_valueDescription);

    bindFallbackMethod(&AccessibilityUIElement::fallbackCallback);
}

AccessibilityUIElement* AccessibilityUIElement::getChildAtIndex(unsigned index)
{
    return m_factory->create(accessibilityObject().childAt(index));
}

void AccessibilityUIElement::allAttributesCallback(const CppArgumentList&, CppVariant* result)
{
    result->set(getAttributes(accessibilityObject()));
}

void AccessibilityUIElement::attributesOfLinkedUIElementsCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfDocumentLinksCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfChildrenCallback(const CppArgumentList& arguments, CppVariant* result)
{
    AttributesCollector collector;
    unsigned size = accessibilityObject().childCount();
    for (unsigned i = 0; i < size; ++i)
        collector.collectAttributes(accessibilityObject().childAt(i));
    result->set(collector.attributes());
}

void AccessibilityUIElement::parametrizedAttributeNamesCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::lineForIndexCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::boundsForRangeCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::stringForRangeCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::childAtIndexCallback(const CppArgumentList& arguments, CppVariant* result)
{
    if (!arguments.size() || !arguments[0].isNumber()) {
        result->setNull();
        return;
    }

    AccessibilityUIElement* child = getChildAtIndex(arguments[0].toInt32());
    if (!child) {
        result->setNull();
        return;
    }

    result->set(*(child->getAsCppVariant()));
}

void AccessibilityUIElement::elementAtPointCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfColumnHeadersCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfRowHeadersCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfColumnsCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfRowsCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfVisibleCellsCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributesOfHeaderCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::indexInTableCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::rowIndexRangeCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::columnIndexRangeCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::cellForColumnAndRowCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::titleUIElementCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::setSelectedTextRangeCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::attributeValueCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::isAttributeSettableCallback(const CppArgumentList& arguments, CppVariant* result)
{
    if (arguments.size() < 1 && !arguments[0].isString()) {
        result->setNull();
        return;
    }

    string attribute = arguments[0].toString();
    bool settable = false;
    if (attribute == "AXValue")
        settable = accessibilityObject().canSetValueAttribute();
    result->set(settable);
}

void AccessibilityUIElement::isActionSupportedCallback(const CppArgumentList&, CppVariant* result)
{
    // This one may be really hard to implement.
    // Not exposed by AccessibilityObject.
    result->setNull();
}

void AccessibilityUIElement::parentElementCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::incrementCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::decrementCallback(const CppArgumentList&, CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::fallbackCallback(const CppArgumentList &, CppVariant* result)
{
    // FIXME: Implement this.
    result->setNull();
}

void AccessibilityUIElement::childrenCountGetterCallback(CppVariant* result)
{
    int count = 1; // Root object always has only one child, the WebView.
    if (!isRoot())
        count = accessibilityObject().childCount();
    result->set(count);
}

void AccessibilityUIElement::descriptionGetterCallback(CppVariant* result)
{
    result->set(getDescription(accessibilityObject()));
}

void AccessibilityUIElement::isEnabledGetterCallback(CppVariant* result)
{
    result->set(accessibilityObject().isEnabled());
}

void AccessibilityUIElement::isSelectedGetterCallback(CppVariant* result)
{
    result->setNull();
}

void AccessibilityUIElement::roleGetterCallback(CppVariant* result)
{
    result->set(getRole(accessibilityObject()));
}

void AccessibilityUIElement::titleGetterCallback(CppVariant* result)
{
    result->set(getTitle(accessibilityObject()));
}


RootAccessibilityUIElement::RootAccessibilityUIElement(const WebAccessibilityObject &object, Factory *factory)
    : AccessibilityUIElement(object, factory) { }

AccessibilityUIElement* RootAccessibilityUIElement::getChildAtIndex(unsigned index)
{
    if (index)
        return 0;

    return factory()->create(accessibilityObject());
}


AccessibilityUIElementList ::~AccessibilityUIElementList()
{
    clear();
}

void AccessibilityUIElementList::clear()
{
    for (ElementList::iterator i = m_elements.begin(); i != m_elements.end(); ++i)
        delete (*i);
    m_elements.clear();
}

AccessibilityUIElement* AccessibilityUIElementList::create(const WebAccessibilityObject& object)
{
    if (object.isNull())
        return 0;

    AccessibilityUIElement* element = new AccessibilityUIElement(object, this);
    m_elements.append(element);
    return element;
}

AccessibilityUIElement* AccessibilityUIElementList::createRoot(const WebAccessibilityObject& object)
{
    AccessibilityUIElement* element = new RootAccessibilityUIElement(object, this);
    m_elements.append(element);
    return element;
}