//
// JSUtils.cpp
//

#include "JSUtils.h"
#include "JSBase.h"
#include "JSObject.h"
#include "JSRun.h"
#include "UserObjectImp.h"
#include "JSValueWrapper.h"
#include "JSObject.h"

struct ObjectImpList {
	ObjectImp* imp;
	ObjectImpList* next;
	CFTypeRef data;
};

static CFTypeRef KJSValueToCFTypeInternal(const Value& inValue, ExecState *exec, ObjectImpList* inImps);


//--------------------------------------------------------------------------
//	CFStringToUString
//--------------------------------------------------------------------------

UString CFStringToUString(CFStringRef inCFString)
{
    InterpreterLock lock;

    UString result;
    if (inCFString) {
        CFIndex len = CFStringGetLength(inCFString);
        UniChar* buffer = (UniChar*)malloc(sizeof(UniChar) * len);
        if (buffer)
        {
            CFStringGetCharacters(inCFString, CFRangeMake(0, len), buffer);
            result = UString((const UChar *)buffer, len);
            free(buffer);
        }
    }
    return result;
}


//--------------------------------------------------------------------------
//	UStringToCFString
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFStringRef
CFStringRef UStringToCFString(const UString& inUString)
{
	return CFStringCreateWithCharacters(NULL, (const UniChar*)inUString.data(), inUString.size());
}


#if JAG_PINK_OR_LATER
//--------------------------------------------------------------------------
//	CFStringToIdentifier
//--------------------------------------------------------------------------

Identifier CFStringToIdentifier(CFStringRef inCFString)
{
	return Identifier(CFStringToUString(inCFString));
}


//--------------------------------------------------------------------------
//	IdentifierToCFString
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFStringRef
CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
{
	return UStringToCFString(inIdentifier.ustring());
}
#endif


//--------------------------------------------------------------------------
//	KJSValueToJSObject
//--------------------------------------------------------------------------
JSUserObject*		KJSValueToJSObject(const Value& inValue, ExecState *exec)
{
	JSUserObject* result = NULL;
#if JAG_PINK_OR_LATER
	UserObjectImp* userObjectImp;
	if (inValue.type() == ObjectType && (userObjectImp = dynamic_cast<UserObjectImp*>(inValue.imp())))
#else
	if (UserObjectImp* userObjectImp = dynamic_cast<UserObjectImp*>(inValue.imp()))
#endif
	{
		result =  userObjectImp->GetJSUserObject();
		if (result) result->Retain();
	}
	else
	{
		JSValueWrapper* wrapperValue = new JSValueWrapper(inValue, exec);
		if (wrapperValue)
		{
			JSObjectCallBacks callBacks;
			JSValueWrapper::GetJSObectCallBacks(callBacks);
			result = (JSUserObject*)JSObjectCreate(wrapperValue, &callBacks);
			if (!result)
			{
				delete wrapperValue;
			}
		}
	}
	return result;
}

//--------------------------------------------------------------------------
//	JSObjectKJSValue
//--------------------------------------------------------------------------
Value JSObjectKJSValue(JSUserObject* ptr)
{
    InterpreterLock lock;

    Value result = Undefined();
    if (ptr)
    {
        bool handled = false;
        
        switch (ptr->DataType())
        {
            case kJSUserObjectDataTypeJSValueWrapper:
            {
                JSValueWrapper* wrapper = (JSValueWrapper*)ptr->GetData();
                if (wrapper)
                {
                    result = wrapper->GetValue();
                    handled = true;
                }
                break;
            }
                
            case kJSUserObjectDataTypeCFType:
            {
                CFTypeRef cfType = (CFTypeRef*)ptr->GetData();
                if (cfType)
                {
                    CFTypeID typeID = CFGetTypeID(cfType);
                    if (typeID == CFStringGetTypeID())
                    {
                        result = String(CFStringToUString((CFStringRef)cfType));
                        handled = true;
                    }
                    else if (typeID == CFNumberGetTypeID())
                    {
                        if (CFNumberIsFloatType((CFNumberRef)cfType))
                        {
                            double num;
                            if (CFNumberGetValue((CFNumberRef)cfType, kCFNumberDoubleType, &num))
                            {
                                result = Number(num);
                                handled = true;
                            }
                        }
                        else
                        {
                            long num;
                            if (CFNumberGetValue((CFNumberRef)cfType, kCFNumberLongType, &num))
                            {
                                result = Number(num);
                                handled = true;
                            }
                        }
                    }
                    else if (typeID == CFBooleanGetTypeID())
                    {
                        result = KJS::Boolean(CFBooleanGetValue((CFBooleanRef)cfType));
                        handled = true;
                    }
                    else if (typeID == CFDateGetTypeID())
                    {
                    }
                    else if (typeID == CFNullGetTypeID())
                    {
                        result = Null();
                        handled = true;
                    }
                }
                break;
            }
        }
        if (!handled)
        {
            result = Object(new UserObjectImp(ptr));
        }
    }
    return result;
}




//--------------------------------------------------------------------------
//	KJSValueToCFTypeInternal
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFTypeRef
CFTypeRef KJSValueToCFTypeInternal(const Value& inValue, ExecState *exec, ObjectImpList* inImps)
{
#if JAG_PINK_OR_LATER
	if (inValue.isNull())
		return NULL;
#endif
		
	CFTypeRef result = NULL;
	
        InterpreterLock lock;

	switch (inValue.type())
	{
		case BooleanType:
			{
				result = inValue.toBoolean(exec) ? kCFBooleanTrue : kCFBooleanFalse;
				RetainCFType(result);
			}
			break;
			
		case StringType:
			{
				UString uString = inValue.toString(exec);
				result = UStringToCFString(uString);
			}
			break;
			
		case NumberType:
			{
				double number1 = inValue.toNumber(exec);
				double number2 = (double)inValue.toInteger(exec);
				if (number1 ==  number2)
				{
					int intValue = (int)number2;
					result = CFNumberCreate(NULL, kCFNumberIntType, &intValue);
				}
				else
				{
					result = CFNumberCreate(NULL, kCFNumberDoubleType, &number1);
				}
			}
			break;
			
		case ObjectType:
			{
				if (UserObjectImp* userObjectImp = dynamic_cast<UserObjectImp*>(inValue.imp()))
				{
					JSUserObject* ptr = userObjectImp->GetJSUserObject();
					if (ptr)
					{
						result = ptr->CopyCFValue();
					}
				}
				else
				{
					Object object = inValue.toObject(exec);
					UInt8 isArray = false;

					// if two objects reference each
					ObjectImp* imp = object.imp();
					ObjectImpList* temp = inImps;
					while (temp) {
						if (imp == temp->imp) {
							return CFRetain(GetCFNull());
						}
						temp = temp->next;
					}

					ObjectImpList imps;
					imps.next = inImps;
					imps.imp = imp;

					
//[...] HACK since we do not have access to the class info we use class name instead
#if 0
					if (object.inherits(&ArrayInstanceImp::info))
#else
					if (object.className() == "Array")
#endif
					{
						isArray = true;					
#if JAG_PINK_OR_LATER
						JSInterpreter* intrepreter = (JSInterpreter*)exec->dynamicInterpreter();
						if (intrepreter && (intrepreter->Flags() & kJSFlagConvertAssociativeArray)) {
							ReferenceList propList = object.propList(exec, false);
							ReferenceListIterator iter = propList.begin();
							ReferenceListIterator end = propList.end();
							while(iter != end && isArray)
							{
								Identifier propName = iter->getPropertyName(exec);
								UString ustr = propName.ustring();
								const UniChar* uniChars = (const UniChar*)ustr.data();
								int size = ustr.size();
								while (size--) {
									if (uniChars[size] < '0' || uniChars[size] > '9') {
										isArray = false;
										break;
									}
								}
								iter++;
							}
						}
#endif
					}
					
					if (isArray)
					{				
						// This is an KJS array
						unsigned int length = object.get(exec, "length").toUInt32(exec);
						result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
						if (result)
						{
#if JAG_PINK_OR_LATER
							for (unsigned i = 0; i < length; i++)
							{
								CFTypeRef cfValue = KJSValueToCFTypeInternal(object.get(exec, i), exec, &imps);
								CFArrayAppendValue((CFMutableArrayRef)result, cfValue);
								ReleaseCFType(cfValue);
							}
#else
							for (unsigned int i = 0; i < length; i++)
							{
								UString propertyName = UString::from(i);
								CFTypeRef cfValue = KJSValueToCFTypeInternal(object.get(exec, propertyName), exec, &imps);
								CFArrayAppendValue((CFMutableArrayRef)result, cfValue);
								ReleaseCFType(cfValue);
							}
#endif
						}
					}
					else
					{
#if JAG_PINK_OR_LATER
						// Not an arry, just treat it like a dictionary which contains (property name, property value) paiars
						ReferenceList propList = object.propList(exec, false);
						{
							result = CFDictionaryCreateMutable(NULL, 
															   0, 
															   &kCFTypeDictionaryKeyCallBacks, 
															   &kCFTypeDictionaryValueCallBacks);
							if (result)
							{
								ReferenceListIterator iter = propList.begin();
								ReferenceListIterator end = propList.end();
								while(iter != end)
								{
									Identifier propName = iter->getPropertyName(exec);
									if (object.hasProperty(exec, propName))
									{
										CFStringRef cfKey = IdentifierToCFString(propName);
										CFTypeRef cfValue = KJSValueToCFTypeInternal(object.get(exec, propName), exec, &imps);
										if (cfKey && cfValue)
										{
											CFDictionaryAddValue((CFMutableDictionaryRef)result, cfKey, cfValue);
										}
										ReleaseCFType(cfKey);
										ReleaseCFType(cfValue);
									}
									iter++;
								}
							}
						}
#else
						List propList = object.propList(exec);
						if (propList.size() > 0)
						{
							result = CFDictionaryCreateMutable(NULL, 
															   0, 
															   &kCFTypeDictionaryKeyCallBacks, 
															   &kCFTypeDictionaryValueCallBacks);
							if (result)
							{
								ListIterator iter = propList.begin();
								ListIterator end = propList.end();
								while(iter != end)
								{
									UString propName = iter->getPropertyName(exec);
									if (object.hasProperty(exec, propName))
									{
										CFStringRef cfKey = UStringToCFString(propName);
										CFTypeRef cfValue = KJSValueToCFTypeInternal(iter->getValue(exec), exec, &imps);
										if (cfKey && cfValue)
										{
											CFDictionaryAddValue((CFMutableDictionaryRef)result, cfKey, cfValue);
										}
										ReleaseCFType(cfKey);
										ReleaseCFType(cfValue);
									}
									++iter;
								}
							}
						}
#endif
					}
				}
			}
			break;

#if !JAG_PINK_OR_LATER
		case ReferenceType:
			{
				Value value = inValue.getValue(exec);
				result = KJSValueToCFTypeInternal(value, exec, NULL);
			}
			break;

		case ListType:
			{
				List list = List::dynamicCast(inValue);
				if (!list.isNull())
				{
					result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
					if (result)
					{
						ListIterator iter = list.begin();
						ListIterator end = list.end();
						while (iter != end)
						{
							CFTypeRef cfTypeRef = KJSValueToCFTypeInternal(*iter, exec, NULL);
							if (cfTypeRef)
								CFArrayAppendValue((CFMutableArrayRef)result, cfTypeRef);
							++iter;
						}
					}
				}
			}
			break;
#endif
		
		case NullType:
		case UndefinedType:
		case UnspecifiedType:
			result = RetainCFType(GetCFNull());
			break;
			
#if !JAG_PINK_OR_LATER
		case CompletionType:
			{
				Completion completion = Completion::dynamicCast(inValue);
				if (completion.isValueCompletion())
				{
					result = KJSValueToCFTypeInternal(completion.value(), exec, NULL);
				}
			}
			break;
#endif

#if JAG_PINK_OR_LATER
		default:
			fprintf(stderr, "KJSValueToCFType: wrong value type %d\n", inValue.type());
			break;
#endif
	}
	
	return result;
}

CFTypeRef KJSValueToCFType(const Value& inValue, ExecState *exec)
{
	return KJSValueToCFTypeInternal(inValue, exec, NULL);
}

CFTypeRef GetCFNull(void)
{
	static CFArrayRef sCFNull = CFArrayCreate(NULL, NULL, 0, NULL);
	CFTypeRef result = JSGetCFNull();
	if (!result)
	{
		result = sCFNull;
	}
	return result;
}

