/*
 * Copyright (C) 2004 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 COMPUTER, INC. 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. 
 */
#include <Foundation/Foundation.h>

#include <JavascriptCore/internal.h>

#include <objc_instance.h>
#include <objc_utility.h>

#include <runtime_array.h>
#include <runtime_object.h>
#include <runtime_root.h>

#include <WebScriptObjectPrivate.h>


using namespace KJS;
using namespace KJS::Bindings;

/*
    The default name concatenates the components of the
    ObjectiveC selector name and replaces ':' with '_'.  '_' characters
    are escaped with an additional '$', i.e. '_' becomes "$_".  '$' are
    also escaped, i.e.
        ObjectiveC name         Default script name
        moveTo::                move__
        moveTo_                 moveTo$_
        moveTo$_                moveTo$$$_
    @result Returns the name to be used to represent the specificed selector in the
*/
void KJS::Bindings::JSMethodNameToObjCMethodName(const char *name, char *buffer, unsigned int len)
{
    const char *np = name;
    char *bp;

    if (strlen(name)*2+1 > len){
        *buffer = 0;
    }

    bp = buffer;
    while (*np) {
        if (*np == '$') {
            np++;
            *bp++ = *np++;
            continue;
        }
        
        if (*np == '_') {
            np++;
            *bp++ = ':';
        }
        else
            *bp++ = *np++;
    }
    *bp++ = 0;
}

/*

    JavaScript to   ObjC
    Number          coerced to char, short, int, long, float, double, or NSNumber, as appropriate
    String          NSString
    wrapper         id
    Object          WebScriptObject
    [], other       exception

*/
ObjcValue KJS::Bindings::convertValueToObjcValue (KJS::ExecState *exec, const KJS::Value &value, ObjcValueType type)
{
    ObjcValue result;
    double d = 0;
   
    if (value.type() == NumberType || value.type() == StringType || value.type() == BooleanType)
	d = value.toNumber(exec);
	
    switch (type){
        case ObjcObjectType: {
	    KJS::Interpreter *originInterpreter = exec->interpreter();
            const Bindings::RootObject *originExecutionContext = rootForInterpreter(originInterpreter);

	    KJS::Interpreter *interpreter = 0;
	    if (originInterpreter->isGlobalObject(value)) {
		interpreter = originInterpreter->interpreterForGlobalObject (value.imp());
	    }

	    if (!interpreter)
		interpreter = originInterpreter;
		
            const Bindings::RootObject *executionContext = rootForInterpreter(interpreter);
            if (!executionContext) {
                Bindings::RootObject *newExecutionContext = new KJS::Bindings::RootObject(0);
                newExecutionContext->setInterpreter (interpreter);
                executionContext = newExecutionContext;
            }
            result.objectValue = [WebScriptObject _convertValueToObjcValue:value originExecutionContext:originExecutionContext executionContext:executionContext ];
        }
        break;
        
        
        case ObjcCharType: {
            result.charValue = (char)d;
        }
        break;

        case ObjcShortType: {
            result.shortValue = (short)d;
        }
        break;

        case ObjcIntType: {
            result.intValue = (int)d;
        }
        break;

        case ObjcLongType: {
            result.longValue = (long)d;
        }
        break;

        case ObjcFloatType: {
            result.floatValue = (float)d;
        }
        break;

        case ObjcDoubleType: {
            result.doubleValue = (double)d;
        }
        break;

        case ObjcVoidType: {
            bzero (&result, sizeof(ObjcValue));
        }
        break;

        case ObjcInvalidType:
        default:
        {
            // FIXME:  throw an exception
        }
        break;
    }
    return result;
}

Value KJS::Bindings::convertNSStringToString(NSString *nsstring)
{
    unichar *chars;
    unsigned int length = [nsstring length];
    chars = (unichar *)malloc(sizeof(unichar)*length);
    [nsstring getCharacters:chars];
    UString u((const KJS::UChar*)chars, length);
    Value aValue = String (u);
    free((void *)chars);
    return aValue;
}

/*

    ObjC      to    JavaScript
    char            Number
    short
    int
    long
    float
    double
    NSNumber        Number
    NSString        String
    NSArray         Array
    id              Object wrapper
    other           should not happen

*/
Value KJS::Bindings::convertObjcValueToValue (KJS::ExecState *exec, void *buffer, ObjcValueType type)
{
    Value aValue;

    switch (type) {
        case ObjcObjectType:
            {
                ObjectStructPtr *obj = (ObjectStructPtr *)buffer;

                /*
                    NSNumber to Number
                    NSString to String
                    NSArray  to Array
                    id       to Object wrapper
                */
                if ([*obj isKindOfClass:[NSString class]]){
                    NSString *string = (NSString *)*obj;
                    aValue = convertNSStringToString (string);
                }
                else if (*obj == [WebUndefined undefined]) {
                    return Undefined();
                }
                else if ((CFBooleanRef)*obj == kCFBooleanTrue) {
                    aValue = Boolean(true);
                }
                else if ((CFBooleanRef)*obj == kCFBooleanFalse) {
                    aValue = Boolean(false);
                }
                else if ([*obj isKindOfClass:[NSNumber class]]) {
                    aValue = Number([*obj doubleValue]);
                }
                else if ([*obj isKindOfClass:[NSArray class]]) {
                    aValue = Object(new RuntimeArrayImp(exec, new ObjcArray (*obj)));
                }
                else if ([*obj isKindOfClass:[WebScriptObject class]]) {
                    WebScriptObject *jsobject = (WebScriptObject *)*obj;
                    aValue = Object([jsobject _imp]);
                }
                else if (*obj == 0) {
                    return Undefined();
                }
                else {
		    aValue = Instance::createRuntimeObject(Instance::ObjectiveCLanguage, (void *)*obj);
                }
            }
            break;
        case ObjcCharType:
            {
                char *objcVal = (char *)buffer;
                aValue = Number ((short)*objcVal);
            }
            break;
        case ObjcShortType:
            {
                short *objcVal = (short *)buffer;
                aValue = Number ((short)*objcVal);
            }
            break;
        case ObjcIntType:
            {
                int *objcVal = (int *)buffer;
                aValue = Number ((int)*objcVal);
            }
            break;
        case ObjcLongType:
            {
                long *objcVal = (long *)buffer;
                aValue = Number ((long)*objcVal);
            }
            break;
        case ObjcFloatType:
            {
                float *objcVal = (float *)buffer;
                aValue = Number ((float)*objcVal);
            }
            break;
        case ObjcDoubleType:
            {
                double *objcVal = (double *)buffer;
                aValue = Number ((double)*objcVal);
            }
            break;
        default:
            // Should never get here.  Argument types are filtered (and
            // the assert above should have fired in the impossible case
            // of an invalid type anyway).
            fprintf (stderr, "%s:  invalid type (%d)\n", __PRETTY_FUNCTION__, (int)type);
            assert (true);
    }
    
    return aValue;
}


ObjcValueType KJS::Bindings::objcValueTypeForType (const char *type)
{
    int typeLength = strlen(type);
    ObjcValueType objcValueType = ObjcInvalidType;
    
    if (typeLength == 1) {
        char typeChar = type[0];
        switch (typeChar){
            case _C_ID: {
                objcValueType = ObjcObjectType;
            }
            break;
            case _C_CHR: {
                objcValueType = ObjcCharType;
            }
            break;
            case _C_SHT: {
                objcValueType = ObjcShortType;
            }
            break;
            case _C_INT: {
                objcValueType = ObjcIntType;
            }
            break;
            case _C_LNG: {
                objcValueType = ObjcLongType;
            }
            break;
            case _C_FLT: {
                objcValueType = ObjcFloatType;
            }
            break;
            case _C_DBL: {
                objcValueType = ObjcDoubleType;
            }
            break;
            case _C_VOID: {
                objcValueType = ObjcVoidType;
            }
            break;
        }
    }
    return objcValueType;
}


void *KJS::Bindings::createObjcInstanceForValue (const Object &value, const RootObject *origin, const RootObject *current)
{
    if (value.type() != ObjectType)
	return 0;

    ObjectImp *imp = static_cast<ObjectImp*>(value.imp());
    
    return [[[WebScriptObject alloc] _initWithObjectImp:imp originExecutionContext:origin executionContext:current] autorelease];
}
