/*
 * Copyright (C) 2005 Apple Computer, 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. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE 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 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.
 */

#include <kjs/wk_config.h>
#include "JavaScriptGlue.h"
#include "JSUtils.h"
#include "JSBase.h"
#include "JSObject.h"
#include "JSRun.h"

static CFTypeRef sJSCFNullRef = 0;

static void CFJSObjectDispose(void *data);
static JSObjectRef CFJSObjectCopyProperty(void *data, CFStringRef propertyName);
static void CFJSObjectSetProperty(void *data, CFStringRef propertyName, JSObjectRef jsValue);
static CFTypeRef CFJSObjectCopyCFValue(void *data);
static UInt8 CFJSObjectEqual(void *data1, void *data2);
static CFArrayRef CFJSObjectCopyPropertyNames(void *data);

void *JSCFRetain(CFAllocatorRef allocator, const void *value);
void JSCFRelease(CFAllocatorRef allocator, const void *value);


void JSSetCFNull(CFTypeRef nullRef)
{
    ReleaseCFType(sJSCFNullRef);
    sJSCFNullRef = RetainCFType(nullRef);
}

CFTypeRef JSGetCFNull(void)
{
    return sJSCFNullRef;
}

/*
    JSRetain
*/
JSTypeRef JSRetain(JSTypeRef ref)
{
    if (ref)
    {
        JSBase* ptr = (JSBase*)ref;
        ptr->Retain();
    }
    return ref;
}

/*
    JSRelease
*/
void JSRelease(JSTypeRef ref)
{
    if (ref)
    {
        JSBase* ptr = (JSBase*)ref;
        ptr->Release();
    }
}

/*
    JSCopyDescription
*/
CFStringRef JSCopyDescription(JSTypeRef ref)
{
    CFStringRef result = 0;
    if (ref)
    {
        JSBase* ptr = (JSBase*)ref;
        ptr->CopyDescription();
    }
    return result;
}

/*
    JSEqual
*/
UInt8 JSEqual(JSTypeRef ref1, JSTypeRef ref2)
{
    UInt8 result = false;
    if (ref1 && ref2)
    {
        JSBase* ptr = (JSBase*)ref1;
        result = ptr->Equal((JSBase*)ref2);
    }
    return result;
}


/*
    JSGetTypeID
*/
JSTypeID JSGetTypeID(JSTypeRef ref)
{
    JSTypeID result = kJSInvalidTypeID;
    if (ref)
    {
        JSBase* ptr = (JSBase*)ref;
        result = ptr->GetTypeID();
    }
    return result;
}


/*
    JSGetRetainCount
*/
CFIndex JSGetRetainCount(JSTypeRef ref)
{
    CFIndex result = -1;
    if (ref)
    {
        JSBase* ptr = (JSBase*)ref;
        result = ptr->RetainCount();
    }
    return result;
}



/*
    JSObjectCreate
*/
JSObjectRef JSObjectCreate(void *data, JSObjectCallBacksPtr callBacks)
{
    JSObjectRef result = JSObjectCreateInternal(data, callBacks, 0, kJSUserObjectDataTypeUnknown);
    return result;
}

/*
    JSObjectCreateInternal
*/
JSObjectRef JSObjectCreateInternal(void *data, JSObjectCallBacksPtr callBacks, JSObjectMarkProcPtr markProc, int type)
{
    JSObjectRef result = 0;
    JSUserObject* ptr = new JSUserObject(callBacks, markProc, data, type);
    result = (JSObjectRef)ptr;
    return result;
}

/*
    JSObjectCopyCFValue
*/
CFTypeRef JSObjectCopyCFValue(JSObjectRef ref)
{
    CFTypeRef result = 0;
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        result = ptr->CopyCFValue();
    }
    return result;
}

/*
    JSObjectGetData
*/
void *JSObjectGetData(JSObjectRef ref)
{
    void *result = 0;
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        result = ptr->GetData();
    }
    return result;
}


/*
    JSObjectCopyProperty
*/
JSObjectRef JSObjectCopyProperty(JSObjectRef ref, CFStringRef propertyName)
{
    JSObjectRef result = 0;
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        result = (JSObjectRef)ptr->CopyProperty(propertyName);
    }
    return result;
}


/*
    JSObjectSetProperty
*/
void JSObjectSetProperty(JSObjectRef ref, CFStringRef propertyName, JSObjectRef value)
{
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        ptr->SetProperty(propertyName, (JSUserObject*)value);
    }
}


/*
    JSObjectCallFunction
*/
JSObjectRef JSObjectCallFunction(JSObjectRef ref, JSObjectRef thisObj, CFArrayRef args)
{
    JSObjectRef result = 0;
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        result = (JSObjectRef)ptr->CallFunction((JSUserObject*)thisObj, args);
    }
    return result;
}


/*
    JSRunCreate
*/
JSRunRef JSRunCreate(CFStringRef jsSource, JSFlags inFlags)
{
    JSRunRef result = 0;
    if (jsSource)
    {
        JSLock lock;
        result = (JSRunRef) new JSRun(jsSource, inFlags);
    }
    return result;
}

/*
    JSRunCopySource
*/
CFStringRef JSRunCopySource(JSRunRef ref)
{
    CFStringRef result = 0;
    JSRun* ptr = (JSRun*)ref;
    if (ptr)
    {
        result = UStringToCFString(ptr->GetSource());
    }
    return result;
}


/*
    JSRunCopyGlobalObject
*/
JSObjectRef JSRunCopyGlobalObject(JSRunRef ref)
{
    JSObjectRef result = 0;
    JSRun* ptr = (JSRun*)ref;
    if (ptr)
    {
        JSObject *globalObject = ptr->GlobalObject();
        result = (JSObjectRef)KJSValueToJSObject(globalObject, ptr->GetInterpreter()->globalExec());
    }
    return result;
}

/*
    JSRunEvaluate
*/
JSObjectRef JSRunEvaluate(JSRunRef ref)
{
    JSObjectRef result = 0;
    JSRun* ptr = (JSRun*)ref;
    if (ptr)
    {
        JSLock lock;
        Completion completion = ptr->Evaluate();
        if (completion.isValueCompletion())
        {
            result = (JSObjectRef)KJSValueToJSObject(completion.value(), ptr->GetInterpreter()->globalExec());
        }

        if (completion.complType() == Throw)
        {
            JSFlags flags = ptr->Flags();
            if (flags & kJSFlagDebug)
            {
                CFTypeRef error = JSObjectCopyCFValue(result);
                if (error)
                {
                    CFShow(error);
                    CFRelease(error);
                }
            }
        }
    }
    return result;
}

/*
    JSRunCheckSyntax
    Return true if no syntax error
*/
bool JSRunCheckSyntax(JSRunRef ref)
{
    bool result = false;
    JSRun* ptr = (JSRun*)ref;
    if (ptr)
    {
            JSLock lock;
            result = ptr->CheckSyntax();
    }
    return result;
}

/*
    JSCollect - trigger garbage collection
*/
void JSCollect(void)
{
    JSLock lock;
    Collector::collect();
}

/*
    JSTypeGetCFArrayCallBacks
*/
void JSTypeGetCFArrayCallBacks(CFArrayCallBacks* outCallBacks)
{
    if (outCallBacks)
    {
        outCallBacks->version = 1;
        outCallBacks->retain = (CFArrayRetainCallBack)JSCFRetain;
        outCallBacks->release = (CFArrayReleaseCallBack)JSCFRelease;
        outCallBacks->copyDescription = (CFArrayCopyDescriptionCallBack)JSCopyDescription;
        outCallBacks->equal = (CFArrayEqualCallBack)JSEqual;
    }
}


/*
    JSCFRetain
*/
void *JSCFRetain(CFAllocatorRef allocator, const void *value)
{
    JSRetain((JSTypeRef)value);
    return (void*)value;
}

/*
    JSCFRelease
*/
void JSCFRelease(CFAllocatorRef allocator, const void *value)
{
    JSRelease((JSTypeRef)value);
}


/*
    JSObjectCreateWithCFType
*/
JSObjectRef JSObjectCreateWithCFType(CFTypeRef inRef)
{
    JSObjectCallBacks callBacks;
    JSObjectRef cfJSObject = nil;
    if (inRef)
    {
        callBacks.dispose = CFJSObjectDispose;
        callBacks.equal = CFJSObjectEqual;
        callBacks.copyCFValue = CFJSObjectCopyCFValue;
        callBacks.copyProperty = CFJSObjectCopyProperty;
        callBacks.setProperty = CFJSObjectSetProperty;
        callBacks.callFunction = 0;
        callBacks.copyPropertyNames = CFJSObjectCopyPropertyNames;
        cfJSObject = JSObjectCreateInternal((void*)CFRetain(inRef), &callBacks, 0, kJSUserObjectDataTypeCFType );
    }
    return cfJSObject;
}

/*
    CFJSObjectDispose
*/
void CFJSObjectDispose(void *data)
{
    if (data)
    {
        CFRelease((JSTypeRef)data);
    }
}

CFArrayRef JSObjectCopyPropertyNames(JSObjectRef ref)
{
    CFArrayRef result = 0;
    JSUserObject* ptr = (JSUserObject*)ref;
    if (ptr && (ptr->GetTypeID() == kJSObjectTypeID))
    {
        result = ptr->CopyPropertyNames();
    }
    return result;
}
/*
    CFJSObjectCopyProperty
*/
JSObjectRef CFJSObjectCopyProperty(void *data, CFStringRef propertyName)
{
    JSObjectRef result = 0;
    if (data && propertyName)
    {
        CFTypeRef cfResult = 0;
        if (CFGetTypeID(data) == CFDictionaryGetTypeID())
        {
            if (CFStringCompare(propertyName, CFSTR("length"), 0) == kCFCompareEqualTo)
            {
                int len = CFDictionaryGetCount((CFDictionaryRef)data);
                cfResult = CFNumberCreate(0, kCFNumberIntType, &len);
            }
            else
            {
                cfResult = RetainCFType(CFDictionaryGetValue((CFDictionaryRef)data, propertyName));
            }
        }
        else if (CFGetTypeID(data) == CFArrayGetTypeID())
        {
            if (CFStringCompare(propertyName, CFSTR("length"), 0) == kCFCompareEqualTo)
            {
                int len = CFArrayGetCount((CFArrayRef)data);
                cfResult = CFNumberCreate(0, kCFNumberIntType, &len);
            }
            else
            {
                SInt32 index = CFStringGetIntValue(propertyName);
                CFIndex arrayCount = CFArrayGetCount((CFArrayRef)data);
                if (index >= 0 && index < arrayCount)
                {
                    cfResult = RetainCFType(CFArrayGetValueAtIndex((CFArrayRef)data, index));
                }
            }
        }
        else if (CFGetTypeID(data) == CFStringGetTypeID())
        {
            if (CFStringCompare(propertyName, CFSTR("length"), 0) == kCFCompareEqualTo)
            {
                int len = CFStringGetLength((CFStringRef)data);
                cfResult = CFNumberCreate(0, kCFNumberIntType, &len);
            }
        }
        if (cfResult)
        {
            result = JSObjectCreateWithCFType(cfResult);
            CFRelease(cfResult);
        }
    }
    return result;
}


/*
    CFJSObjectSetProperty
*/
void CFJSObjectSetProperty(void *data, CFStringRef propertyName, JSObjectRef jsValue)
{
    if (data && propertyName)
    {
        CFTypeRef cfValue = JSObjectCopyCFValue(jsValue);

        if (cfValue)
        {
            if (CFGetTypeID(data) == CFDictionaryGetTypeID())
            {
                CFDictionarySetValue((CFMutableDictionaryRef)data, propertyName, cfValue);
            }
            else if (CFGetTypeID(data) == CFArrayGetTypeID())
            {
                SInt32 index = CFStringGetIntValue(propertyName);
                CFIndex arrayCount = CFArrayGetCount((CFArrayRef)data);
                if (index >= 0)
                {
                    for (; arrayCount < index; arrayCount++)
                    {
                        CFArrayAppendValue((CFMutableArrayRef)data, GetCFNull());
                    }
                    CFArraySetValueAtIndex((CFMutableArrayRef)data, index, cfValue);
                }
            }
            CFRelease(cfValue);
        }
        else
        {
            if (CFGetTypeID(data) == CFDictionaryGetTypeID())
            {
                CFDictionaryRemoveValue((CFMutableDictionaryRef)data, propertyName);
            }
            else if (CFGetTypeID(data) == CFArrayGetTypeID())
            {
                SInt32 index = CFStringGetIntValue(propertyName);
                CFIndex arrayCount = CFArrayGetCount((CFArrayRef)data);
                if (index >= 0)
                {
                    for (; arrayCount < index; arrayCount++)
                    {
                        CFArrayAppendValue((CFMutableArrayRef)data, GetCFNull());
                    }
                    CFArraySetValueAtIndex((CFMutableArrayRef)data, index, GetCFNull());
                }
            }
        }
    }
}


/*
    CFJSObjectCopyCFValue
*/
CFTypeRef CFJSObjectCopyCFValue(void *data)
{
    CFTypeRef result = 0;
    if (data)
    {
        result = (CFTypeRef)CFRetain(data);
    }
    return result;
}

/*
    CFJSObjectCopyCFValue
*/
UInt8 CFJSObjectEqual(void *data1, void *data2)
{
    UInt8 result = false;
    if (data1 && data2)
    {
        CFEqual((CFTypeRef)data1, (CFTypeRef)data2);
    }
    return result;
}


/*
    CFJSObjectCopyPropertyNames
*/
CFArrayRef CFJSObjectCopyPropertyNames(void *data)
{
    CFMutableArrayRef result = 0;
    if (data)
    {
        CFTypeID cfType = CFGetTypeID(data);
        if (cfType == CFDictionaryGetTypeID())
        {
            CFIndex count = CFDictionaryGetCount((CFDictionaryRef)data);
            if (count)
            {
                CFTypeRef* keys = (CFTypeRef*)malloc(sizeof(CFTypeRef)*count);
                if (keys)
                {
                    int i;
                    CFDictionaryGetKeysAndValues((CFDictionaryRef)data, (const void **)keys, 0);
                    for (i = 0; i < count; i++)
                    {
                        CFStringRef key = (CFStringRef)keys[i];
                        if (CFGetTypeID(key) != CFStringGetTypeID()) continue;

                        if (!result) result = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
                        if (!result) continue;

                        CFArrayAppendValue(result, key);
                    }
                    free(keys);
                }
            }
        }
    }
    return result;
}




CFMutableArrayRef JSCreateCFArrayFromJSArray(CFArrayRef array)
{
    CFIndex count = array ? CFArrayGetCount(array) : 0;
    CFMutableArrayRef cfArray = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
    CFIndex i;

    for (i = 0; cfArray && i <  count; i++)
    {
        JSObjectRef jsValue = (JSObjectRef)CFArrayGetValueAtIndex(array, i);
        CFTypeRef cfvalue = JSObjectCopyCFValue(jsValue);
        if (cfvalue)
        {
            CFArrayAppendValue(cfArray, cfvalue);
            CFRelease(cfvalue);
        }
        else
        {
            CFArrayAppendValue(cfArray, GetCFNull());
        }
    }
    return cfArray;
}

CFMutableArrayRef JSCreateJSArrayFromCFArray(CFArrayRef array)
{
    CFIndex count = array ? CFArrayGetCount(array) : 0;
    CFArrayCallBacks arrayCallbacks;
    CFMutableArrayRef jsArray;
    CFIndex i;

    JSTypeGetCFArrayCallBacks(&arrayCallbacks);
    jsArray = CFArrayCreateMutable(0, 0, &arrayCallbacks);

    for (i = 0; array && i <  count; i++)
    {
        CFTypeRef cfValue = (CFTypeRef)CFArrayGetValueAtIndex(array, i);
        JSObjectRef jsValue = JSObjectCreateWithCFType(cfValue);

        if (!jsValue) jsValue = JSObjectCreateWithCFType(GetCFNull());
        if (jsValue)
        {
            CFArrayAppendValue(jsArray, jsValue);
            JSRelease(jsValue);
        }
    }
    return jsArray;
}


void JSLockInterpreter()
{
    JSLock::lock();
    JSLock::registerThread();
}


void JSUnlockInterpreter()
{
    JSLock::unlock();
}
