#include "UserObjectImp.h"

const ClassInfo UserObjectImp::info = {"UserObject", 0, 0, 0};

class UserObjectPrototypeImp : public UserObjectImp {
  public:
    UserObjectPrototypeImp();
	static UserObjectPrototypeImp* GlobalUserObjectPrototypeImp();
  private:
	static UserObjectPrototypeImp* sUserObjectPrototypeImp;
};

UserObjectPrototypeImp* UserObjectPrototypeImp::sUserObjectPrototypeImp = NULL;

UserObjectPrototypeImp::UserObjectPrototypeImp()
  : UserObjectImp()
{
}

UserObjectPrototypeImp* UserObjectPrototypeImp::GlobalUserObjectPrototypeImp()
{
	if (!sUserObjectPrototypeImp)
	{
		sUserObjectPrototypeImp = new UserObjectPrototypeImp();
		static ProtectedValue protectPrototype = Value(sUserObjectPrototypeImp);
	}
	return sUserObjectPrototypeImp;
}


UserObjectImp::UserObjectImp(): ObjectImp(), fJSUserObject(NULL)
{
}

UserObjectImp::UserObjectImp(JSUserObject* userObject) : 
	ObjectImp(Object(UserObjectPrototypeImp::GlobalUserObjectPrototypeImp())), 
	fJSUserObject((JSUserObject*)userObject->Retain()) 
{ 
}

UserObjectImp::~UserObjectImp() 
{
	if (fJSUserObject)
	{
		fJSUserObject->Release(); 
	}
}
    
const ClassInfo	* UserObjectImp::classInfo() const 
{
	return &info; 
}
        
bool	UserObjectImp::implementsCall() const 
{
	return fJSUserObject ? fJSUserObject->ImplementsCall() : false; 
}

Value	UserObjectImp::call(ExecState *exec, Object &thisObj, const List &args)
{
	Value result = Undefined();
	JSUserObject* jsThisObj = KJSValueToJSObject(thisObj, exec);
	if (jsThisObj)
	{
		CFIndex argCount = args.size();
		CFArrayCallBacks arrayCallBacks;
		JSTypeGetCFArrayCallBacks(&arrayCallBacks);
		CFMutableArrayRef jsArgs = CFArrayCreateMutable(NULL, 0, &arrayCallBacks);
		if (jsArgs)
		{
			for (CFIndex i = 0; i < argCount;  i++)
			{
				JSUserObject* jsArg = KJSValueToJSObject(args[i], exec);
				CFArrayAppendValue(jsArgs, (void*)jsArg);
				jsArg->Release();
			}
		}

#if 0
		JSUserObject* jsResult = fJSUserObject->CallFunction(jsThisObj, jsArgs);
#else
		int lockCount = Interpreter::lockCount();
		int i;
		for (i = 0; i < lockCount; i++)
		{
			Interpreter::unlock();
		}

		JSUserObject* jsResult = fJSUserObject->CallFunction(jsThisObj, jsArgs);

		for (i = 0; i < lockCount; i++)
		{
			Interpreter::lock();
		}
#endif
		if (jsResult)
		{
			result = JSObjectKJSValue(jsResult);
			jsResult->Release();
		}

		ReleaseCFType(jsArgs);
		jsThisObj->Release();
	}
	return result;
}


ReferenceList UserObjectImp::propList(ExecState *exec, bool recursive)
{
	ReferenceList propList;
	JSUserObject* ptr = GetJSUserObject();
	if (ptr)
	{
		Object theObject = toObject(exec);
		CFArrayRef propertyNames = ptr->CopyPropertyNames();
		if (propertyNames)
		{
			CFIndex count = CFArrayGetCount(propertyNames);
			CFIndex i;
			for (i = 0; i < count; i++)
			{
				CFStringRef propertyName = (CFStringRef)CFArrayGetValueAtIndex(propertyNames, i);
				propList.append(Reference(theObject, CFStringToIdentifier(propertyName)));
			}
			CFRelease(propertyNames);
		}
	}
	return propList;
}

bool UserObjectImp::hasOwnProperty(ExecState *exec, const Identifier &propertyName) const
{
    bool hasProperty = false;

    CFStringRef	cfPropName = IdentifierToCFString(propertyName);
    JSUserObject* jsResult = fJSUserObject->CopyProperty(cfPropName);
    if (jsResult) {
        Value result = JSObjectKJSValue(jsResult);
        jsResult->Release();
        if (result.type() != UndefinedType) {
            hasProperty = true;
        }
    } else {
        Value kjsValue = toPrimitive(exec);
        if (kjsValue.type() != NullType && kjsValue.type() != UndefinedType) {
            Object kjsObject = kjsValue.toObject(exec);
            hasProperty = kjsObject.hasOwnProperty(exec, propertyName);
        }
    }

    CFRelease(cfPropName);

    if (hasProperty)
        return true;

    return ObjectImp::hasOwnProperty(exec, propertyName);
}

#if JAG_PINK_OR_LATER
Value	UserObjectImp::get(ExecState *exec, const Identifier &propertyName) const
{
	Value result = Undefined();
	CFStringRef	cfPropName = IdentifierToCFString(propertyName);
	JSUserObject* jsResult = fJSUserObject->CopyProperty(cfPropName);
	if (jsResult)
	{
		result = JSObjectKJSValue(jsResult);
		jsResult->Release();
	}
	else
	{
		Value kjsValue = toPrimitive(exec);
		if (kjsValue.type() != NullType && kjsValue.type() != UndefinedType)
		{
			Object kjsObject = kjsValue.toObject(exec);
			Value kjsObjectProp = kjsObject.get(exec, propertyName);
			result = kjsObjectProp;
		}
	}
	ReleaseCFType(cfPropName);
	return result;

}

void	UserObjectImp::put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr)
{
	CFStringRef	cfPropName = IdentifierToCFString(propertyName);
	JSUserObject* jsValueObj = KJSValueToJSObject(value, exec);

	fJSUserObject->SetProperty(cfPropName, jsValueObj);

	if (jsValueObj) jsValueObj->Release();	
	ReleaseCFType(cfPropName);
}
#else	//JAG_PINK_OR_LATER
Value	UserObjectImp::get(ExecState* exec, const UString& propertyName) const
{
	Value result = Undefined();
	CFStringRef	cfPropName = UStringToCFString(propertyName);
	JSUserObject* jsResult = fJSUserObject->CopyProperty(cfPropName);
	if (jsResult)
	{
		result = JSObjectKJSValue(jsResult);
		jsResult->Release();
	}
	else
	{
		Value kjsValue = toPrimitive(exec);
		if (kjsValue != Null() && kjsValue != Undefined())
		{
			Object kjsObject = kjsValue.toObject(exec);
			Value kjsObjectProp = kjsObject.get(exec, propertyName);
			result = kjsObjectProp;
		}
	}
	ReleaseCFType(cfPropName);
	return result;

}

void	UserObjectImp::put(ExecState* exec, const UString& propertyName, const Value& value, int attr)
{
	CFStringRef	cfPropName = UStringToCFString(propertyName);
	JSUserObject* jsValueObj = KJSValueToJSObject(value, exec);

	fJSUserObject->SetProperty(cfPropName, jsValueObj);

	if (jsValueObj) jsValueObj->Release();	
	ReleaseCFType(cfPropName);
}
#endif	//JAG_PINK_OR_LATER
        
JSUserObject* UserObjectImp::GetJSUserObject() const 
{ 
	return fJSUserObject; 
}



Value UserObjectImp::toPrimitive(ExecState *exec, Type preferredType) const
{
	Value result = Undefined();
	JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec);
	CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : NULL;
	if (cfValue)
	{
		CFTypeID cfType = CFGetTypeID(cfValue);  // toPrimitive
		if (cfValue == GetCFNull())
		{
			result = Null();
		}
		else if (cfType == CFBooleanGetTypeID())
		{
			if (cfValue == kCFBooleanTrue)
			{
				result = KJS::Boolean(true);
			}
			else 
			{
				result = KJS::Boolean(false);
			}
		}
		else if (cfType == CFStringGetTypeID())
		{
			result = KJS::String(CFStringToUString((CFStringRef)cfValue));
		}
		else if (cfType == CFNumberGetTypeID())
		{
			double d = 0.0;
			CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &d);
			result = KJS::Number(d);
		}
		else if (cfType == CFURLGetTypeID())
		{
			CFURLRef absURL = CFURLCopyAbsoluteURL((CFURLRef)cfValue);
			if (absURL)
			{
				result = KJS::String(CFStringToUString(CFURLGetString(absURL)));
				ReleaseCFType(absURL);
			}
		}		
		ReleaseCFType(cfValue);
	}
	if (jsObjPtr) jsObjPtr->Release();
	return result;
}


bool UserObjectImp::toBoolean(ExecState *exec) const
{
	bool result = false;
	JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec);
	CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : NULL;
	if (cfValue)
	{
		CFTypeID cfType = CFGetTypeID(cfValue);  // toPrimitive
		if (cfValue == GetCFNull())
		{
			//
		}
		else if (cfType == CFBooleanGetTypeID())
		{
			if (cfValue == kCFBooleanTrue)
			{
				result = true;
			}
		}
		else if (cfType == CFStringGetTypeID())
		{
			if (CFStringGetLength((CFStringRef)cfValue))
			{
				result = true;
			}
		}
		else if (cfType == CFNumberGetTypeID())
		{
			if (cfValue != kCFNumberNaN)
			{
				double d;
				if (CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &d))
				{
					if (d != 0)
					{
						result = true;
					}
				}
			}
		}
		else if (cfType == CFArrayGetTypeID())
		{
			if (CFArrayGetCount((CFArrayRef)cfValue))
			{
				result = true;
			}
		}
		else if (cfType == CFDictionaryGetTypeID())
		{
			if (CFDictionaryGetCount((CFDictionaryRef)cfValue))
			{
				result = true;
			}
		}
		else if (cfType == CFSetGetTypeID())
		{
			if (CFSetGetCount((CFSetRef)cfValue))
			{
				result = true;
			}
		}
		else if (cfType == CFURLGetTypeID())
		{
			CFURLRef absURL = CFURLCopyAbsoluteURL((CFURLRef)cfValue);
			if (absURL)
			{
				CFStringRef cfStr = CFURLGetString(absURL);
				if (cfStr && CFStringGetLength(cfStr))
				{
					result = true;
				}
				ReleaseCFType(absURL);
			}
		}
	}
	if (jsObjPtr) jsObjPtr->Release();
	ReleaseCFType(cfValue);
	return result;
}

double UserObjectImp::toNumber(ExecState *exec) const
{
	double result = 0;
	JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec);
	CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : NULL;
	if (cfValue)
	{
		CFTypeID cfType = CFGetTypeID(cfValue);
		
		if (cfValue == GetCFNull())
		{
			//
		}
		else if (cfType == CFBooleanGetTypeID())
		{
			if (cfValue == kCFBooleanTrue)
			{
				result = 1;
			}
		}
		else if (cfType == CFStringGetTypeID())
		{
			result = CFStringGetDoubleValue((CFStringRef)cfValue);
		}
		else if (cfType == CFNumberGetTypeID())
		{
			CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &result);		
		}
	}
	ReleaseCFType(cfValue);
	if (jsObjPtr) jsObjPtr->Release();
	return result;
}

UString UserObjectImp::toString(ExecState *exec) const
{
	UString result;
	JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec);
	CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : NULL;
	if (cfValue)
	{
		CFTypeID cfType = CFGetTypeID(cfValue);
		if (cfValue == GetCFNull())
		{
			//
		}
		else if (cfType == CFBooleanGetTypeID())
		{
			if (cfValue == kCFBooleanTrue)
			{
				result = "true";
			}
			else
			{
				result = "false";
			}
		}
		else if (cfType == CFStringGetTypeID())
		{
			result = CFStringToUString((CFStringRef)cfValue);
		}
		else if (cfType == CFNumberGetTypeID())
		{
			if (cfValue == kCFNumberNaN)
			{
				result = "Nan";
			}
			else if (CFNumberCompare(kCFNumberPositiveInfinity, (CFNumberRef)cfValue, NULL) == 0)
			{
				result = "Infinity";
			}
			else if (CFNumberCompare(kCFNumberNegativeInfinity, (CFNumberRef)cfValue, NULL) == 0)
			{
				result = "-Infinity";
			}
			else
			{
				CFStringRef cfNumStr = NULL;
				if (CFNumberIsFloatType((CFNumberRef)cfValue))
				{
					double d = 0;
					CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &d);
					cfNumStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%f"), (float)d);
				}
				else
				{
					int i = 0;
					CFNumberGetValue((CFNumberRef)cfValue, kCFNumberIntType, &i);
					cfNumStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), (int)i);
				}
				
				if (cfNumStr)
				{
					result = CFStringToUString(cfNumStr);
					ReleaseCFType(cfNumStr);
				}				
			}
		}
		else if (cfType == CFArrayGetTypeID())
		{
			//
		}
		else if (cfType == CFDictionaryGetTypeID())
		{
			//
		}
		else if (cfType == CFSetGetTypeID())
		{
			//
		}
		else if (cfType == CFURLGetTypeID())
		{
			CFURLRef absURL = CFURLCopyAbsoluteURL((CFURLRef)cfValue);
			if (absURL)
			{
				CFStringRef cfStr = CFURLGetString(absURL);
				if (cfStr)
				{
					result = CFStringToUString(cfStr);
				}
				ReleaseCFType(absURL);
			}
		}		
	}
	ReleaseCFType(cfValue);
	if (jsObjPtr) jsObjPtr->Release();
	return result;
}

void UserObjectImp::mark()
{
	ObjectImp::mark(); // call parent to mark self
	if (fJSUserObject)
	{
		fJSUserObject->Mark(); // mark child
	}
}
