/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef __nsMenuObject_h__
#define __nsMenuObject_h__

#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"

#include "nsDbusmenu.h"
#include "nsNativeMenuDocListener.h"

class nsIAtom;
class nsIContent;
class nsStyleContext;
class nsMenuContainer;
class nsMenuObjectIconLoader;

#define DBUSMENU_PROPERTIES \
    DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \
    DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \
    DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \
    DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \
    DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \
    DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \
    DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \
    DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \
    DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8)

/*
 * This is the base class for all menu nodes. Each instance represents
 * a single node in the menu hierarchy. It wraps the corresponding DOM node and
 * native menu node, keeps them in sync and transfers events between the two.
 * It is not reference counted - each node is owned by its parent (the top
 * level menubar is owned by the window) and keeps a weak pointer to its
 * parent (which is guaranteed to always be valid because a node will never
 * outlive its parent). It is not safe to keep a reference to nsMenuObject
 * externally.
 */
class nsMenuObject : public nsNativeMenuChangeObserver {
public:
    enum EType {
        eType_MenuBar,
        eType_Menu,
        eType_MenuItem
    };

    virtual ~nsMenuObject();

    // Get the native menu item node
    DbusmenuMenuitem* GetNativeData() const { return mNativeData; }

    // Get the parent menu object
    nsMenuContainer* Parent() const { return mParent; }

    // Get the content node
    nsIContent* ContentNode() const { return mContent; }

    // Get the type of this node. Must be provided by subclasses
    virtual EType Type() const = 0;

    // Get the document listener
    nsNativeMenuDocListener* DocListener() const { return mListener; }

    // Create the native menu item node (called by containers)
    void CreateNativeData();

    // Adopt the specified native menu item node (called by containers)
    nsresult AdoptNativeData(DbusmenuMenuitem* aNativeData);

    // Called by the container to tell us that it's opening
    void ContainerIsOpening();

protected:
    nsMenuObject(nsMenuContainer* aParent, nsIContent* aContent);
    nsMenuObject(nsNativeMenuDocListener* aListener, nsIContent* aContent);

    enum PropertyFlags {
#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b),
        DBUSMENU_PROPERTIES
#undef DBUSMENU_PROPERTY
    };

    void UpdateLabel();
    void UpdateVisibility(nsStyleContext* aStyleContext);
    void UpdateSensitivity();
    void UpdateIcon(nsStyleContext* aStyleContext);

    already_AddRefed<nsStyleContext> GetStyleContext();
  
private:
    friend class nsMenuObjectIconLoader;

    // Set up initial properties on the native data, connect to signals etc.
    // This should be implemented by subclasses
    virtual void InitializeNativeData();

    // Return the properties that this menu object type supports
    // This should be implemented by subclasses
    virtual PropertyFlags SupportedProperties() const;

    // Determine whether this menu object could use the specified
    // native item. Returns true by default but can be overridden by subclasses
    virtual bool
    IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const;

    // Update attributes on this objects content node when the container opens.
    // This is called before style resolution, and should be implemented by
    // subclasses who want to modify attributes that might affect style.
    // This will not be called when there are script blockers
    virtual void UpdateContentAttributes();

    // Update properties that should be refreshed when the container opens.
    // This should be implemented by subclasses that have properties which
    // need refreshing
    virtual void Update(nsStyleContext* aStyleContext);

    bool ShouldShowIcon() const;
    void ClearIcon();

    nsCOMPtr<nsIContent> mContent;
    // mListener is a strong ref for simplicity - someone in the tree needs to
    // own it, and this only really needs to be the top-level object (as no
    // children outlives their parent). However, we need to keep it alive until
    // after running the nsMenuObject destructor for the top-level menu object,
    // hence the strong ref
    RefPtr<nsNativeMenuDocListener> mListener;
    nsMenuContainer* mParent; // [weak]
    DbusmenuMenuitem* mNativeData; // [strong]
    RefPtr<nsMenuObjectIconLoader> mIconLoader;
};

// Keep a weak pointer to a menu object
class nsWeakMenuObject {
public:
    nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {}

    nsWeakMenuObject(nsMenuObject* aMenuObject) :
        mPrev(nullptr), mMenuObject(aMenuObject)
    {
        AddWeakReference(this);
    }

    ~nsWeakMenuObject() { RemoveWeakReference(this); }

    nsMenuObject* get() const { return mMenuObject; }

    nsMenuObject* operator->() const { return mMenuObject; }

    explicit operator bool() const { return !!mMenuObject; }

    static void NotifyDestroyed(nsMenuObject* aMenuObject);

private:
    static void AddWeakReference(nsWeakMenuObject* aWeak);
    static void RemoveWeakReference(nsWeakMenuObject* aWeak);

    nsWeakMenuObject* mPrev;
    static nsWeakMenuObject* sHead;

    nsMenuObject* mMenuObject;
};

#endif /* __nsMenuObject_h__ */