/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 * Copyright (C) 2009 Pawel Hajdan (phajdan.jr@chromium.org)
 *
 * 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.
 */

/*
  CppBoundClass class:
  This base class serves as a parent for C++ classes designed to be bound to
  JavaScript objects.

  Subclasses should define the constructor to build the property and method
  lists needed to bind this class to a JS object.  They should also declare
  and define member variables and methods to be exposed to JS through
  that object.
*/

#ifndef CppBoundClass_h
#define CppBoundClass_h

#include "CppVariant.h"
#include <wtf/HashMap.h>
#include <wtf/Noncopyable.h>
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/Vector.h>

namespace WebKit {
class WebFrame;
class WebString;
}

namespace WebTestRunner {

typedef Vector<CppVariant> CppArgumentList;

// CppBoundClass lets you map Javascript method calls and property accesses
// directly to C++ method calls and CppVariant* variable access.
class CppBoundClass {
    WTF_MAKE_NONCOPYABLE(CppBoundClass);
public:
    class PropertyCallback {
    public:
        virtual ~PropertyCallback() { }

        // Sets |value| to the value of the property. Returns false in case of
        // failure. |value| is always non-0.
        virtual bool getValue(CppVariant* result) = 0;

        // sets the property value to |value|. Returns false in case of failure.
        virtual bool setValue(const CppVariant&) = 0;
    };

    // Callback class for "void function(CppVariant*)"
    class GetterCallback {
    public:
        virtual ~GetterCallback() { }
        virtual void run(CppVariant*) = 0;
    };

    // The constructor should call BindMethod, BindProperty, and
    // SetFallbackMethod as needed to set up the methods, properties, and
    // fallback method.
    CppBoundClass() : m_boundToFrame(false) { }
    virtual ~CppBoundClass();

    // Return a CppVariant representing this class, for use with BindProperty().
    // The variant type is guaranteed to be NPVariantType_Object.
    CppVariant* getAsCppVariant();

    // Given a WebFrame, BindToJavascript builds the NPObject that will represent
    // the class and binds it to the frame's window under the given name. This
    // should generally be called from the WebView delegate's
    // WindowObjectCleared(). A class so bound will be accessible to JavaScript
    // as window.<classname>. The owner of the CppBoundObject is responsible for
    // keeping the object around while the frame is alive, and for destroying it
    // afterwards.
    void bindToJavascript(WebKit::WebFrame*, const WebKit::WebString& classname);

    // Used by a test. Returns true if a method with the specified name exists,
    // regardless of whether a fallback is registered.
    bool isMethodRegistered(const std::string&) const;

protected:
    // Callback for "void function(const CppArguemntList&, CppVariant*)"
    class Callback {
    public:
        virtual ~Callback() { }
        virtual void run(const CppArgumentList&, CppVariant*) = 0;
    };

    // Callback for "void T::method(const CppArguemntList&, CppVariant*)"
    template <class T> class MemberCallback : public Callback {
    public:
        typedef void (T::*MethodType)(const CppArgumentList&, CppVariant*);
        MemberCallback(T* object, MethodType method)
            : m_object(object)
            , m_method(method) { }
        virtual ~MemberCallback() { }

        virtual void run(const CppArgumentList& arguments, CppVariant* result)
        {
            (m_object->*m_method)(arguments, result);
        }

    private:
        T* m_object;
        MethodType m_method;
    };

    // Callback class for "void T::method(CppVariant*)"
    template <class T> class MemberGetterCallback : public GetterCallback {
    public:
        typedef void (T::*MethodType)(CppVariant*);
        MemberGetterCallback(T* object, MethodType method)
            : m_object(object)
            , m_method(method) { }
        virtual ~MemberGetterCallback() { }

        virtual void run(CppVariant* result) { (m_object->*m_method)(result); }

    private:
        T* m_object;
        MethodType m_method;
    };

    // Bind the Javascript method called the string parameter to the C++ method.
    void bindCallback(const std::string&, Callback*);

    // A wrapper for bindCallback, to simplify the common case of binding a
    // method on the current object. Though not verified here, the method parameter
    // must be a method of this CppBoundClass subclass.
    template<class T>
    void bindMethod(const std::string& name, void (T::*method)(const CppArgumentList&, CppVariant*))
    {
        Callback* callback = new MemberCallback<T>(static_cast<T*>(this), method);
        bindCallback(name, callback);
    }

    // Bind Javascript property |name| to the C++ getter callback |callback|.
    // This can be used to create read-only properties.
    void bindGetterCallback(const std::string&, PassOwnPtr<GetterCallback>);

    // A wrapper for BindGetterCallback, to simplify the common case of binding a
    // property on the current object. Though not verified here, the method parameter
    // must be a method of this CppBoundClass subclass.
    template<class T>
    void bindProperty(const std::string& name, void (T::*method)(CppVariant*))
    {
        OwnPtr<GetterCallback> callback = adoptPtr(new MemberGetterCallback<T>(static_cast<T*>(this), method));
        bindGetterCallback(name, callback.release());
    }

    // Bind the Javascript property called |name| to a CppVariant.
    void bindProperty(const std::string&, CppVariant*);

    // Bind Javascript property called |name| to a PropertyCallback.
    // CppBoundClass assumes control over the life time of the callback.
    void bindProperty(const std::string&, PropertyCallback*);

    // Set the fallback callback, which is called when when a callback is
    // invoked that isn't bound.
    // If it is 0 (its default value), a JavaScript exception is thrown in
    // that case (as normally expected). If non 0, the fallback method is
    // invoked and the script continues its execution.
    // Passing 0 clears out any existing binding.
    // It is used for tests and should probably only be used in such cases
    // as it may cause unexpected behaviors (a JavaScript object with a
    // fallback always returns true when checked for a method's
    // existence).
    void bindFallbackCallback(PassOwnPtr<Callback> fallbackCallback)
    {
        m_fallbackCallback = fallbackCallback;
    }

    // A wrapper for BindFallbackCallback, to simplify the common case of
    // binding a method on the current object. Though not verified here,
    // |method| must be a method of this CppBoundClass subclass.
    // Passing 0 for |method| clears out any existing binding.
    template<class T>
    void bindFallbackMethod(void (T::*method)(const CppArgumentList&, CppVariant*))
    {
        if (method) {
            OwnPtr<Callback> callback = adoptPtr(new MemberCallback<T>(static_cast<T*>(this), method));
            bindFallbackCallback(callback.release());
        } else
            bindFallbackCallback(nullptr);
    }

    // Some fields are protected because some tests depend on accessing them,
    // but otherwise they should be considered private.

    typedef HashMap<NPIdentifier, PropertyCallback*> PropertyList;
    typedef HashMap<NPIdentifier, Callback*> MethodList;
    // These maps associate names with property and method pointers to be
    // exposed to JavaScript.
    PropertyList m_properties;
    MethodList m_methods;

    // The callback gets invoked when a call is made to an nonexistent method.
    OwnPtr<Callback> m_fallbackCallback;

private:
    // NPObject callbacks.
    friend struct CppNPObject;
    bool hasMethod(NPIdentifier) const;
    bool invoke(NPIdentifier, const NPVariant* args, size_t argCount,
                NPVariant* result);
    bool hasProperty(NPIdentifier) const;
    bool getProperty(NPIdentifier, NPVariant* result) const;
    bool setProperty(NPIdentifier, const NPVariant*);

    // A lazily-initialized CppVariant representing this class. We retain 1
    // reference to this object, and it is released on deletion.
    CppVariant m_selfVariant;

    // True if our np_object has been bound to a WebFrame, in which case it must
    // be unregistered with V8 when we delete it.
    bool m_boundToFrame;
};

}

#endif // CppBoundClass_h
