/*
 * Copyright (C) 2010 Apple 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:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
 */

#ifndef PluginTest_h
#define PluginTest_h

#include <WebKit/npfunctions.h>
#include <assert.h>
#include <map>
#include <string>

#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif

// Helper classes for implementing has_member
typedef char (&no_tag)[1];
typedef char (&yes_tag)[2];

#define DEFINE_HAS_MEMBER_CHECK(member, returnType, argumentTypes) \
template<typename T, returnType (T::*member) argumentTypes> struct pmf_##member##_helper {}; \
template<typename T> no_tag has_member_##member##_helper(...); \
template<typename T> yes_tag has_member_##member##_helper(pmf_##member##_helper<T, &T::member >*); \
template<typename T> struct has_member_##member { \
static const bool value = sizeof(has_member_##member##_helper<T>(0)) == sizeof(yes_tag); \
};

DEFINE_HAS_MEMBER_CHECK(hasMethod, bool, (NPIdentifier methodName));
DEFINE_HAS_MEMBER_CHECK(invoke, bool, (NPIdentifier methodName, const NPVariant*, uint32_t, NPVariant* result));
DEFINE_HAS_MEMBER_CHECK(invokeDefault, bool, (const NPVariant*, uint32_t, NPVariant* result));
DEFINE_HAS_MEMBER_CHECK(hasProperty, bool, (NPIdentifier propertyName));
DEFINE_HAS_MEMBER_CHECK(getProperty, bool, (NPIdentifier propertyName, NPVariant* result));
DEFINE_HAS_MEMBER_CHECK(removeProperty, bool, (NPIdentifier propertyName));

class PluginTest {
public:
    static PluginTest* create(NPP, const std::string& identifier);
    virtual ~PluginTest();

    static void NP_Shutdown();

    // NPP functions.
    virtual NPError NPP_New(NPMIMEType pluginType, uint16_t mode, int16_t argc, char *argn[], char *argv[], NPSavedData *saved);
    virtual NPError NPP_Destroy(NPSavedData**);
    virtual NPError NPP_SetWindow(NPWindow*);
    virtual NPError NPP_NewStream(NPMIMEType, NPStream*, NPBool seekable, uint16_t* stype);
    virtual NPError NPP_DestroyStream(NPStream*, NPReason);
    virtual int32_t NPP_WriteReady(NPStream*);
    virtual int32_t NPP_Write(NPStream*, int32_t offset, int32_t len, void* buffer);
    
    virtual int16_t NPP_HandleEvent(void* event);
    virtual bool NPP_URLNotify(const char* url, NPReason, void* notifyData);
    virtual void NPP_URLRedirectNotify(const char* url, int32_t status, void* notifyData);
    virtual NPError NPP_GetValue(NPPVariable, void* value);
    virtual NPError NPP_SetValue(NPNVariable, void *value);

    // NPN functions.
    NPError NPN_GetURL(const char* url, const char* target);
    NPError NPN_GetURLNotify(const char* url, const char* target, void* notifyData);
    NPError NPN_PostURLNotify(const char *url, const char *target, uint32_t len, const char* buf, NPBool file, void *notifyData);
    NPError NPN_GetValue(NPNVariable, void* value);
    void NPN_InvalidateRect(NPRect* invalidRect);
    bool NPN_Invoke(NPObject *, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result);
    void* NPN_MemAlloc(uint32_t size);

    // NPRuntime NPN functions.
    NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name);
    NPIdentifier NPN_GetIntIdentifier(int32_t intid);
    bool NPN_IdentifierIsString(NPIdentifier);
    NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier);
    int32_t NPN_IntFromIdentifier(NPIdentifier);

    NPObject* NPN_CreateObject(NPClass*);
    NPObject* NPN_RetainObject(NPObject*);
    void NPN_ReleaseObject(NPObject*);
    bool NPN_RemoveProperty(NPObject*, NPIdentifier propertyName);
    void NPN_ReleaseVariantValue(NPVariant*);
    void NPN_URLRedirectResponse(void* notifyData, NPBool allow);

#ifdef XP_MACOSX
    bool NPN_ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace);
#endif

    bool executeScript(const NPString*, NPVariant* result);
    void executeScript(const char*);
    void log(const char* format, ...);

    void registerNPShutdownFunction(void (*)());

    static void indicateTestFailure();

    template<typename TestClassTy> class Register {
    public:
        Register(const std::string& identifier)
        {
            registerCreateTestFunction(identifier, Register::create);
        }
    
    private:
        static PluginTest* create(NPP npp, const std::string& identifier) 
        {
            return new TestClassTy(npp, identifier);
        }
    };

protected:
    PluginTest(NPP npp, const std::string& identifier);

    // FIXME: A plug-in test shouldn't need to know about it's NPP. Make this private.
    NPP m_npp;

    const std::string& identifier() const { return m_identifier; }

    static NPNetscapeFuncs* netscapeFuncs();

    void waitUntilDone();
    void notifyDone();

    // NPObject helper template.
    template<typename T> struct Object : NPObject {
    public:
        static NPObject* create(PluginTest* pluginTest)
        {
            Object* object = static_cast<Object*>(pluginTest->NPN_CreateObject(npClass()));

            object->m_pluginTest = pluginTest;
            return object;
        }
    
        // These should never be called.
        bool hasMethod(NPIdentifier methodName)
        {
            assert(false);
            return false;
        }

        bool invoke(NPIdentifier methodName, const NPVariant*, uint32_t, NPVariant* result)
        {
            assert(false);
            return false;
        }
        
        bool invokeDefault(const NPVariant*, uint32_t, NPVariant* result)
        {
            assert(false);
            return false;
        }

        bool hasProperty(NPIdentifier propertyName)
        {
            assert(false);
            return false;
        }

        bool getProperty(NPIdentifier propertyName, NPVariant* result)
        {
            assert(false);
            return false;
        }

        bool removeProperty(NPIdentifier propertyName)
        {
            assert(false);
            return false;
        }

        // Helper functions.
        bool identifierIs(NPIdentifier identifier, const char* value)
        {
            return pluginTest()->NPN_GetStringIdentifier(value) == identifier;
        }

    protected:
        Object()
            : m_pluginTest(0)
        {
        }
        
        virtual ~Object() 
        { 
        }

        PluginTest* pluginTest() const { return m_pluginTest; }

    private:
        static NPObject* NP_Allocate(NPP npp, NPClass* aClass)
        {
            return new T;
        }

        static void NP_Deallocate(NPObject* npObject)
        {
            delete static_cast<T*>(npObject);
        }

        static bool NP_HasMethod(NPObject* npObject, NPIdentifier methodName)
        {
            return static_cast<T*>(npObject)->hasMethod(methodName);
        }

        static bool NP_Invoke(NPObject* npObject, NPIdentifier methodName, const NPVariant* arguments, uint32_t argumentCount, NPVariant* result)
        {
            return static_cast<T*>(npObject)->invoke(methodName, arguments, argumentCount, result);
        }

        static bool NP_InvokeDefault(NPObject* npObject, const NPVariant* arguments, uint32_t argumentCount, NPVariant* result)
        {
            return static_cast<T*>(npObject)->invokeDefault(arguments, argumentCount, result);
        }

        static bool NP_HasProperty(NPObject* npObject, NPIdentifier propertyName)
        {
            return static_cast<T*>(npObject)->hasProperty(propertyName);
        }

        static bool NP_GetProperty(NPObject* npObject, NPIdentifier propertyName, NPVariant* result)
        {
            return static_cast<T*>(npObject)->getProperty(propertyName, result);
        }

        static bool NP_RemoveProperty(NPObject* npObject, NPIdentifier propertyName)
        {
            return static_cast<T*>(npObject)->removeProperty(propertyName);
        }

        static NPClass* npClass()
        {
            static NPClass npClass = {
                NP_CLASS_STRUCT_VERSION, 
                NP_Allocate,
                NP_Deallocate,
                0, // NPClass::invalidate
                has_member_hasMethod<T>::value ? NP_HasMethod : 0,
                has_member_invoke<T>::value ? NP_Invoke : 0,
                has_member_invokeDefault<T>::value ? NP_InvokeDefault : 0,
                has_member_hasProperty<T>::value ? NP_HasProperty : 0,
                has_member_getProperty<T>::value ? NP_GetProperty : 0,
                0, // NPClass::setProperty
                has_member_removeProperty<T>::value ? NP_RemoveProperty : 0,
                0, // NPClass::enumerate
                0  // NPClass::construct
            };
            
            return &npClass;
        };

        PluginTest* m_pluginTest;
    };
    
private:
    typedef PluginTest* (*CreateTestFunction)(NPP, const std::string&);
    
    static void registerCreateTestFunction(const std::string&, CreateTestFunction);
    static std::map<std::string, CreateTestFunction>& createTestFunctions();
    
    std::string m_identifier;
};

#endif // PluginTest_h
