/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
 */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Sun Microsystems, Inc.
 * Portions created by Sun Microsystems are Copyright (C) 2002 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Original Author: Bolian Yin (bolian.yin@sun.com)
 *
 * Contributor(s): John Sun (john.sun@sun.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsMai.h"
#include "nsDocAccessibleWrap.h"
#include "nsAccessibleEventData.h"

#include <atk/atk.h>
#include <glib.h>
#include <glib-object.h>

//----- nsDocAccessibleWrap -----

/*
 * Must keep sychronization with enumerate AtkProperty in 
 * accessible/src/base/nsAccessibleEventData.h
 */
static char * sAtkPropertyNameArray[PROP_LAST] = {
    0,
    "accessible_name",
    "accessible_description",
    "accessible_parent",
    "accessible_value",
    "accessible_role",
    "accessible_layer",
    "accessible_mdi_zorder",
    "accessible_table_caption",
    "accessible_table_column_description",
    "accessible_table_column_header",
    "accessible_table_row_description",
    "accessible_table_row_header",
    "accessible_table_summary"
};

static  AtkStateType TranslateAState(PRUint32 aState, PRUint32 aExtState);

NS_IMPL_ISUPPORTS_INHERITED2(nsDocAccessibleWrap, nsDocAccessible, nsIAccessibleText, nsIAccessibleEditableText)

nsDocAccessibleWrap::nsDocAccessibleWrap(nsIDOMNode *aDOMNode,
                                         nsIWeakReference *aShell): 
  nsDocAccessible(aDOMNode, aShell), nsAccessibleEditableText(aDOMNode)
{
}

nsDocAccessibleWrap::~nsDocAccessibleWrap()
{
}

NS_IMETHODIMP nsDocAccessibleWrap::FireToolkitEvent(PRUint32 aEvent,
                                                    nsIAccessible* aAccessible,
                                                    void* aEventData)
{
    NS_ENSURE_ARG_POINTER(aAccessible);

    // First fire nsIObserver event for internal xpcom accessibility clients
    nsDocAccessible::FireToolkitEvent(aEvent, aAccessible, aEventData);

    nsresult rv = NS_ERROR_FAILURE;

    nsAccessibleWrap *accWrap =
        NS_STATIC_CAST(nsAccessibleWrap *, aAccessible);
    MAI_LOG_DEBUG(("\n\nReceived event: aEvent=%u, obj=0x%x, data=0x%x \n",
                   aEvent, aAccessible, aEventData));

    nsAccessibleWrap *oldAccWrap = nsnull, *newAccWrap = nsnull;
    AtkTableChange * pAtkTableChange = nsnull;

    switch (aEvent) {
    case nsIAccessibleEvent::EVENT_FOCUS:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_FOCUS\n"));
        atk_focus_tracker_notify(accWrap->GetAtkObject());
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_STATE_CHANGE:
        AtkStateChange *pAtkStateChange;
        AtkStateType atkState;

        MAI_LOG_DEBUG(("\n\nReceived: EVENT_STATE_CHANGE\n"));
        if (!aEventData)
            break;

        pAtkStateChange = NS_REINTERPRET_CAST(AtkStateChange *, aEventData);

        switch (pAtkStateChange->state) {
        case nsIAccessible::STATE_INVISIBLE:
            atkState = ATK_STATE_VISIBLE;
            pAtkStateChange->enable = !pAtkStateChange->enable;
            break;
        case nsIAccessible::STATE_UNAVAILABLE:
            atkState = ATK_STATE_ENABLED;
            pAtkStateChange->enable = !pAtkStateChange->enable;
            break;
        case nsIAccessible::STATE_READONLY:
            atkState = ATK_STATE_EDITABLE;
            pAtkStateChange->enable = !pAtkStateChange->enable;
            break;
        default:
            atkState = TranslateAState(pAtkStateChange->state, pAtkStateChange->extState);
        }

        atk_object_notify_state_change(accWrap->GetAtkObject(),
                                       atkState, pAtkStateChange->enable);
        rv = NS_OK;
        break;
      
        /*
         * More complex than I ever thought.
         * Need handle them separately.
         */
    case nsIAccessibleEvent::EVENT_ATK_PROPERTY_CHANGE :
        AtkPropertyChange *pAtkPropChange;
        AtkPropertyValues values;

        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_PROPERTY_CHANGE\n"));
        if (!aEventData)
            break;

        pAtkPropChange = NS_REINTERPRET_CAST(AtkPropertyChange *, aEventData);
        values.property_name = sAtkPropertyNameArray[pAtkPropChange->type];
        
        MAI_LOG_DEBUG(("\n\nthe type of EVENT_ATK_PROPERTY_CHANGE: %d\n\n",
                       pAtkPropChange->type));

        switch (pAtkPropChange->type) {
        case PROP_TABLE_CAPTION:
        case PROP_TABLE_SUMMARY:

            if (pAtkPropChange->oldvalue)
                oldAccWrap = NS_REINTERPRET_CAST(nsAccessibleWrap *,
                                                 pAtkPropChange->oldvalue);

            if (pAtkPropChange->newvalue)
                newAccWrap = NS_REINTERPRET_CAST(nsAccessibleWrap *,
                                                 pAtkPropChange->newvalue);

            if (oldAccWrap && newAccWrap) {
                g_value_init(&values.old_value, G_TYPE_POINTER);
                g_value_set_pointer(&values.old_value,
                                    oldAccWrap->GetAtkObject());
                g_value_init(&values.new_value, G_TYPE_POINTER);
                g_value_set_pointer(&values.new_value,
                                    newAccWrap->GetAtkObject());
                rv = NS_OK;
            }
            break;

        case PROP_TABLE_COLUMN_DESCRIPTION:
        case PROP_TABLE_COLUMN_HEADER:
        case PROP_TABLE_ROW_HEADER:
        case PROP_TABLE_ROW_DESCRIPTION:
            g_value_init(&values.new_value, G_TYPE_INT);
            g_value_set_int(&values.new_value,
                            *NS_REINTERPRET_CAST(gint *,
                                                 pAtkPropChange->newvalue));
            rv = NS_OK;
            break;
  
            //Perhaps need more cases in the future
        default:
            g_value_init (&values.old_value, G_TYPE_POINTER);
            g_value_set_pointer (&values.old_value, pAtkPropChange->oldvalue);
            g_value_init (&values.new_value, G_TYPE_POINTER);
            g_value_set_pointer (&values.new_value, pAtkPropChange->newvalue);
            rv = NS_OK;
        }
        if (NS_SUCCEEDED(rv)) {
            char *signal_name = g_strconcat("property_change::",
                                            values.property_name, NULL);
            g_signal_emit_by_name(accWrap->GetAtkObject(), signal_name,
                                  &values, NULL);
            g_free (signal_name);
        }

        break;

    case nsIAccessibleEvent::EVENT_ATK_SELECTION_CHANGE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_SELECTION_CHANGE\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "selection_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TEXT_CHANGE:
        AtkTextChange *pAtkTextChange;

        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TEXT_CHANGE\n"));
        if (!aEventData)
            break;

        pAtkTextChange = NS_REINTERPRET_CAST(AtkTextChange *, aEventData);
        g_signal_emit_by_name (accWrap->GetAtkObject(),
                               pAtkTextChange->add ? \
                               "text_changed::insert":"text_changed::delete",
                               pAtkTextChange->start,
                               pAtkTextChange->length);
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TEXT_SELECTION_CHANGE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TEXT_SELECTION_CHANGE\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "text_selection_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TEXT_CARET_MOVE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TEXT_CARET_MOVE\n"));
        if (!aEventData)
            break;

        MAI_LOG_DEBUG(("\n\nCaret postion: %d", *(gint *)aEventData ));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "text_caret_moved",
                              // Curent caret position
                              *(gint *)aEventData);
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TABLE_MODEL_CHANGE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_MODEL_CHANGE\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "model_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TABLE_ROW_INSERT:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_ROW_INSERT\n"));
        if (!aEventData)
            break;

        pAtkTableChange = NS_REINTERPRET_CAST(AtkTableChange *, aEventData);

        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "row_inserted",
                              // After which the rows are inserted
                              pAtkTableChange->index,
                              // The number of the inserted
                              pAtkTableChange->count);
        rv = NS_OK;
        break;
        
    case nsIAccessibleEvent::EVENT_ATK_TABLE_ROW_DELETE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_ROW_DELETE\n"));
        if (!aEventData)
            break;

        pAtkTableChange = NS_REINTERPRET_CAST(AtkTableChange *, aEventData);

        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "row_deleted",
                              // After which the rows are deleted
                              pAtkTableChange->index,
                              // The number of the deleted
                              pAtkTableChange->count);
        rv = NS_OK;
        break;
        
    case nsIAccessibleEvent::EVENT_ATK_TABLE_ROW_REORDER:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_ROW_REORDER\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "row_reordered");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TABLE_COLUMN_INSERT:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_COLUMN_INSERT\n"));
        if (!aEventData)
            break;

        pAtkTableChange = NS_REINTERPRET_CAST(AtkTableChange *, aEventData);

        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "column_inserted",
                              // After which the columns are inserted
                              pAtkTableChange->index,
                              // The number of the inserted
                              pAtkTableChange->count);
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TABLE_COLUMN_DELETE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_COLUMN_DELETE\n"));
        if (!aEventData)
            break;

        pAtkTableChange = NS_REINTERPRET_CAST(AtkTableChange *, aEventData);

        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "column_deleted",
                              // After which the columns are deleted
                              pAtkTableChange->index,
                              // The number of the deleted
                              pAtkTableChange->count);
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_TABLE_COLUMN_REORDER:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_TABLE_COLUMN_REORDER\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "column_reordered");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_VISIBLE_DATA_CHANGE:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_VISIBLE_DATA_CHANGE\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "visible_data_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_LINK_SELECTED:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_LINK_SELECTED\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "link_selected",
                              // Selected link index 
                              *(gint *)aEventData);
        rv = NS_OK;
        break;

        // Is a superclass of ATK event children_changed
    case nsIAccessibleEvent::EVENT_REORDER:
        AtkChildrenChange *pAtkChildrenChange;

        MAI_LOG_DEBUG(("\n\nReceived: EVENT_REORDER(children_change)\n"));

        pAtkChildrenChange = NS_REINTERPRET_CAST(AtkChildrenChange *,
                                                 aEventData);
        nsAccessibleWrap *childAccWrap;
        if (pAtkChildrenChange && pAtkChildrenChange->child) {
            childAccWrap = NS_STATIC_CAST(nsAccessibleWrap *,
                                          pAtkChildrenChange->child);
            g_signal_emit_by_name (accWrap->GetAtkObject(),
                                   pAtkChildrenChange->add ? \
                                   "children_changed::add" : \
                                   "children_changed::remove",
                                   pAtkChildrenChange->index,
                                   childAccWrap->GetAtkObject(),
                                   NULL);
        }
        else {
            //
            // EVENT_REORDER is normally fired by "HTML Document".
            //
            // In GOK, [only] "children_changed::add" can cause foreground
            // window accessible to update it children, which will
            // refresh "UI-Grab" window.
            //
            g_signal_emit_by_name (accWrap->GetAtkObject(),
                                   "children_changed::add",
                                   -1, NULL, NULL);
        }

        rv = NS_OK;
        break;

        /*
         * Because dealing with menu is very different between nsIAccessible
         * and ATK, and the menu activity is important, specially transfer the
         * following two event.
         * Need more verification by AT test.
         */
    case nsIAccessibleEvent::EVENT_MENUSTART:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENUSTART\n"));
        atk_focus_tracker_notify(accWrap->GetAtkObject());
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "selection_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_MENUEND:
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENUEND\n"));
        g_signal_emit_by_name(accWrap->GetAtkObject(),
                              "selection_changed");
        rv = NS_OK;
        break;

    case nsIAccessibleEvent::EVENT_ATK_WINDOW_ACTIVATE:
      {
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_WINDOW_ACTIVATED\n"));
        AtkObject *accessible = accWrap->GetAtkObject();
        guint id = g_signal_lookup ("activate", MAI_TYPE_ATK_OBJECT);
        g_signal_emit(accessible, id, 0);
        rv = NS_OK;
      } break;

    case nsIAccessibleEvent::EVENT_ATK_WINDOW_DEACTIVATE:
      {
        MAI_LOG_DEBUG(("\n\nReceived: EVENT_ATK_WINDOW_DEACTIVATED\n"));
        AtkObject *accessible = accWrap->GetAtkObject();
        guint id = g_signal_lookup ("deactivate", MAI_TYPE_ATK_OBJECT);
        g_signal_emit(accessible, id, 0);
        rv = NS_OK;
      } break;

    default:
        // Don't transfer others
        MAI_LOG_DEBUG(("\n\nReceived an unknown event=0x%u\n", aEvent));
        break;
    }

    return rv;
}

/* static */
AtkStateType
TranslateAState(PRUint32 aState, PRUint32 aExtState)
{
    switch (aState) {
    case nsIAccessible::STATE_SELECTED:
        return ATK_STATE_SELECTED;
    case nsIAccessible::STATE_FOCUSED:
        return ATK_STATE_FOCUSED;
    case nsIAccessible::STATE_PRESSED:
        return ATK_STATE_PRESSED;
    case nsIAccessible::STATE_CHECKED:
        return ATK_STATE_CHECKED;
    case nsIAccessible::STATE_EXPANDED:
        return ATK_STATE_EXPANDED;
    case nsIAccessible::STATE_COLLAPSED:
        return ATK_STATE_EXPANDABLE;
        // The control can't accept input at this time
    case nsIAccessible::STATE_BUSY:
        return ATK_STATE_BUSY;
    case nsIAccessible::STATE_FOCUSABLE:
        return ATK_STATE_FOCUSABLE;
    case nsIAccessible::STATE_SELECTABLE:
        return ATK_STATE_SELECTABLE;
    case nsIAccessible::STATE_SIZEABLE:
        return ATK_STATE_RESIZABLE;
    case nsIAccessible::STATE_MULTISELECTABLE:
        return ATK_STATE_MULTISELECTABLE;

#if 0
        // The following states are opposite the MSAA states.
        // We need to deal with them specially
    case nsIAccessible::STATE_INVISIBLE:
        return !ATK_STATE_VISIBLE;

    case nsIAccessible::STATE_UNAVAILABLE:
        return !ATK_STATE_ENABLED;

    case nsIAccessible::STATE_READONLY:
        return !ATK_STATE_EDITABLE;
#endif
    }

    // The following state is
    // Extended state flags (for non-MSAA, for Java and Gnome/ATK support)
    switch (aExtState) {
    case nsIAccessible::EXT_STATE_ACTIVE:
        return ATK_STATE_ACTIVE;
    case nsIAccessible::EXT_STATE_EXPANDABLE:
        return ATK_STATE_EXPANDABLE;
#if 0
        // Need change definitions in nsIAccessible.idl to avoid
        // duplicate value
    case nsIAccessible::EXT_STATE_MODAL:
        return ATK_STATE_MODAL;
#endif
    case nsIAccessible::EXT_STATE_MULTI_LINE:
        return ATK_STATE_MULTI_LINE;
    case nsIAccessible::EXT_STATE_SENSITIVE:
        return ATK_STATE_SENSITIVE;
    case nsIAccessible::EXT_STATE_SHOWING:
        return ATK_STATE_SHOWING;
    case nsIAccessible::EXT_STATE_SINGLE_LINE:
        return ATK_STATE_SINGLE_LINE;
    case nsIAccessible::EXT_STATE_TRANSIENT:
        return ATK_STATE_TRANSIENT;
    case nsIAccessible::EXT_STATE_VERTICAL:
        return ATK_STATE_VERTICAL;
    }
    return ATK_STATE_INVALID;
}

NS_IMETHODIMP nsDocAccessibleWrap::Shutdown()
{
    nsAccessibleEditableText::ShutdownEditor();
    return nsDocAccessible::Shutdown();
}

NS_IMETHODIMP nsDocAccessibleWrap::GetRole(PRUint32 *_retval)
{
    PRBool isEditable;
    GetIsEditable(&isEditable);

    if (isEditable)
        *_retval = ROLE_TEXT;
    else
        *_retval = ROLE_HTML_CONTAINER;

    return NS_OK;
}

void nsDocAccessibleWrap::CheckForEditor()
{
    nsDocAccessible::CheckForEditor();
    if (mEditor)
        SetEditor(mEditor); // set editor for nsAccessibleEditableText
}

NS_IMETHODIMP nsDocAccessibleWrap::FireDocLoadingEvent(PRBool aIsFinished)
{
  if (!mDocument || !mWeakShell)
    return NS_OK;  // Document has been shut down

  if (!aIsFinished) {
    // Load has been verified, it will occur, about to commence
    AtkChildrenChange childrenData;
    childrenData.index = -1;
    childrenData.child = 0;
    childrenData.add = PR_FALSE;
    FireToolkitEvent(nsIAccessibleEvent::EVENT_REORDER, this, &childrenData);
  }

  return nsDocAccessible::FireDocLoadingEvent(aIsFinished);
}
