/*
 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	CFPlugIn_Factory.c
	Copyright 1999-2002, Apple, Inc. All rights reserved.
	Responsibility: Doug Davidson
*/

#include "CFBundle_Internal.h"
#include "CFInternal.h"

static CFSpinLock_t CFPlugInGlobalDataLock = 0;
static CFMutableDictionaryRef _factoriesByFactoryID = NULL; /* Value is _CFPFactory */
static CFMutableDictionaryRef _factoriesByTypeID = NULL; /* Value is array of _CFPFactory */

static void _CFPFactoryAddToTable(_CFPFactory *factory) {
    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByFactoryID == NULL) {
        CFDictionaryValueCallBacks _factoryDictValueCallbacks = {0, NULL, NULL, NULL, NULL};
        // Use default allocator
        _factoriesByFactoryID = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &_factoryDictValueCallbacks);
    }
    CFDictionarySetValue(_factoriesByFactoryID, factory->_uuid, factory);
    __CFSpinUnlock(&CFPlugInGlobalDataLock);
}

static void _CFPFactoryRemoveFromTable(_CFPFactory *factory) {
    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByFactoryID != NULL) {
        CFDictionaryRemoveValue(_factoriesByFactoryID, factory->_uuid);
    }
    __CFSpinUnlock(&CFPlugInGlobalDataLock);
}

__private_extern__ _CFPFactory *_CFPFactoryFind(CFUUIDRef factoryID, Boolean enabled) {
    _CFPFactory *result = NULL;
    
    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByFactoryID != NULL) {
        result = (_CFPFactory *)CFDictionaryGetValue(_factoriesByFactoryID, factoryID);
        if (result && result->_enabled != enabled) {
            result = NULL;
        }
    }
    __CFSpinUnlock(&CFPlugInGlobalDataLock);
    return result;
}

static void _CFPFactoryDeallocate(_CFPFactory *factory) {
    CFAllocatorRef allocator = factory->_allocator;
    SInt32 c;
    
    _CFPFactoryRemoveFromTable(factory);

    if (factory->_plugIn) {
        _CFPlugInRemoveFactory(factory->_plugIn, factory);
    }

    /* Remove all types for this factory. */
    c = CFArrayGetCount(factory->_types);
    while (c--) {
        _CFPFactoryRemoveType(factory, CFArrayGetValueAtIndex(factory->_types, c));
    }
    CFRelease(factory->_types);

    if (factory->_funcName) {
        CFRelease(factory->_funcName);
    }

    if (factory->_uuid) {
        CFRelease(factory->_uuid);
    }

    CFAllocatorDeallocate(allocator, factory);
    CFRelease(allocator);
}

static _CFPFactory *_CFPFactoryCommonCreate(CFAllocatorRef allocator, CFUUIDRef factoryID) {
    _CFPFactory *factory;
    UInt32 size;
    size = sizeof(_CFPFactory);
    allocator = ((NULL == allocator) ? CFRetain(__CFGetDefaultAllocator()) : CFRetain(allocator));
    factory = CFAllocatorAllocate(allocator, size, 0);
    if (NULL == factory) {
        CFRelease(allocator);
        return NULL;
    }

    factory->_allocator = allocator;

    factory->_uuid = CFRetain(factoryID);
    factory->_enabled = true;
    factory->_instanceCount = 0;

    _CFPFactoryAddToTable(factory);

    factory->_types = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);

    return factory;
}

__private_extern__ _CFPFactory *_CFPFactoryCreate(CFAllocatorRef allocator, CFUUIDRef factoryID, CFPlugInFactoryFunction func) {
    _CFPFactory *factory = _CFPFactoryCommonCreate(allocator, factoryID);

    factory->_func = func;
    factory->_plugIn = NULL;
    factory->_funcName = NULL;

    return factory;
}

__private_extern__ _CFPFactory *_CFPFactoryCreateByName(CFAllocatorRef allocator, CFUUIDRef factoryID, CFPlugInRef plugIn, CFStringRef funcName) {
    _CFPFactory *factory = _CFPFactoryCommonCreate(allocator, factoryID);

    factory->_func = NULL;
    factory->_plugIn = plugIn;
    if (plugIn) {
        _CFPlugInAddFactory(plugIn, factory);
    }
    factory->_funcName = (funcName ? CFStringCreateCopy(allocator, funcName) : NULL);

    return factory;
}

__private_extern__ CFUUIDRef _CFPFactoryGetFactoryID(_CFPFactory *factory) {
    return factory->_uuid;
}

__private_extern__ CFPlugInRef _CFPFactoryGetPlugIn(_CFPFactory *factory) {
    return factory->_plugIn;
}

__private_extern__ void *_CFPFactoryCreateInstance(CFAllocatorRef allocator, _CFPFactory *factory, CFUUIDRef typeID) {
    void *result = NULL;
    if (factory->_enabled) {
        if (factory->_func == NULL) {
            factory->_func = CFBundleGetFunctionPointerForName(factory->_plugIn, factory->_funcName);
            if (factory->_func == NULL) {
                CFLog(__kCFLogPlugIn, CFSTR("Cannot find function pointer %@ for factory %@ in %@"), factory->_funcName, factory->_uuid, factory->_plugIn);
            }
#if defined(__MACH__) && defined(__ppc__)
            else {
                // return values from CFBundleGetFunctionPointerForName will always be dyld, but
                // we must force-fault them because pointers to glue code do not fault correctly
                factory->_func = (void *)((uint32_t)(factory->_func) | 0x1);
            }
#endif
        }
        if (factory->_func) {
#if 1
            // UPPGOOP
            FAULT_CALLBACK((void **)&(factory->_func));
            result = (void *)INVOKE_CALLBACK2(factory->_func, allocator, typeID);
#else
            result = factory->_func(allocator, typeID);
#endif
        }
    } else {
        CFLog(__kCFLogPlugIn, CFSTR("Factory %@ is disabled"), factory->_uuid);
    }
    return result;
}

__private_extern__ void _CFPFactoryDisable(_CFPFactory *factory) {
    factory->_enabled = false;
    if (factory->_instanceCount == 0) {
        _CFPFactoryDeallocate(factory);
    }
}

__private_extern__ Boolean _CFPFactoryIsEnabled(_CFPFactory *factory) {
    return factory->_enabled;
}

__private_extern__ void _CFPFactoryFlushFunctionCache(_CFPFactory *factory) {
    /* MF:!!! Assert that this factory belongs to a plugIn. */
    /* This is called by the factory's plugIn when the plugIn unloads its code. */
    factory->_func = NULL;
}

__private_extern__ void _CFPFactoryAddType(_CFPFactory *factory, CFUUIDRef typeID) {
    CFMutableArrayRef array;
    
    /* Add the type to the factory's type list */
    CFArrayAppendValue(factory->_types, typeID);

    /* Add the factory to the type's array of factories */
    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByTypeID == NULL) {
        // Create this from default allocator
        _factoriesByTypeID = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    }
    array = (CFMutableArrayRef)CFDictionaryGetValue(_factoriesByTypeID, typeID);
    if (array == NULL) {
        CFArrayCallBacks _factoryArrayCallbacks = {0, NULL, NULL, NULL, NULL};
        // Create this from default allocator
        array = CFArrayCreateMutable(NULL, 0, &_factoryArrayCallbacks);
        CFDictionarySetValue(_factoriesByTypeID, typeID, array);
        CFRelease(array);
    }
    CFArrayAppendValue(array, factory);
    __CFSpinUnlock(&CFPlugInGlobalDataLock);
}

__private_extern__ void _CFPFactoryRemoveType(_CFPFactory *factory, CFUUIDRef typeID) {
    /* Remove it from the factory's type list */
    SInt32 idx;

    idx = CFArrayGetFirstIndexOfValue(factory->_types, CFRangeMake(0, CFArrayGetCount(factory->_types)), typeID);
    if (idx >=0) {
        CFArrayRemoveValueAtIndex(factory->_types, idx);
    }

    /* Remove the factory from the type's list of factories */
    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByTypeID != NULL) {
        CFMutableArrayRef array = (CFMutableArrayRef)CFDictionaryGetValue(_factoriesByTypeID, typeID);
        if (array != NULL) {
            idx = CFArrayGetFirstIndexOfValue(array, CFRangeMake(0, CFArrayGetCount(array)), factory);
            if (idx >=0) {
                CFArrayRemoveValueAtIndex(array, idx);
                if (CFArrayGetCount(array) == 0) {
                    CFDictionaryRemoveValue(_factoriesByTypeID, typeID);
                }
            }
        }
    }
    __CFSpinUnlock(&CFPlugInGlobalDataLock);
}

__private_extern__ Boolean _CFPFactorySupportsType(_CFPFactory *factory, CFUUIDRef typeID) {
    SInt32 idx;

    idx = CFArrayGetFirstIndexOfValue(factory->_types, CFRangeMake(0, CFArrayGetCount(factory->_types)), typeID);
    return ((idx >= 0) ? true : false);
}

__private_extern__ CFArrayRef _CFPFactoryFindForType(CFUUIDRef typeID) {
    CFArrayRef result = NULL;

    __CFSpinLock(&CFPlugInGlobalDataLock);
    if (_factoriesByTypeID != NULL) {
        result = CFDictionaryGetValue(_factoriesByTypeID, typeID);
    }
    __CFSpinUnlock(&CFPlugInGlobalDataLock);

    return result;
}

/* These methods are called by CFPlugInInstance when an instance is created or destroyed.  If a factory's instance count goes to 0 and the factory has been disabled, the factory is destroyed. */
__private_extern__ void _CFPFactoryAddInstance(_CFPFactory *factory) {
    /* MF:!!! Assert that factory is enabled. */
    factory->_instanceCount++;
    if (factory->_plugIn) {
        _CFPlugInAddPlugInInstance(factory->_plugIn);
    }
}

__private_extern__ void _CFPFactoryRemoveInstance(_CFPFactory *factory) {
    /* MF:!!! Assert that _instanceCount > 0. */
    factory->_instanceCount--;
    if (factory->_plugIn) {
        _CFPlugInRemovePlugInInstance(factory->_plugIn);
    }
    if ((factory->_instanceCount == 0) && (!factory->_enabled)) {
        _CFPFactoryDeallocate(factory);
    }
}
