// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/**
*******************************************************************************
* Copyright (C) 2001-2014, International Business Machines Corporation.
* All Rights Reserved.
*******************************************************************************
*/

#include "unicode/utypes.h"
#include "unicode/localpointer.h"

#if !UCONFIG_NO_SERVICE

#include "serv.h"
#include "umutex.h"

#undef SERVICE_REFCOUNT

// in case we use the refcount stuff

U_NAMESPACE_BEGIN

/*
******************************************************************
*/

const UChar ICUServiceKey::PREFIX_DELIMITER = 0x002F;   /* '/' */

ICUServiceKey::ICUServiceKey(const UnicodeString& id)
: _id(id) {
}

ICUServiceKey::~ICUServiceKey()
{
}

const UnicodeString&
ICUServiceKey::getID() const
{
    return _id;
}

UnicodeString&
ICUServiceKey::canonicalID(UnicodeString& result) const
{
    return result.append(_id);
}

UnicodeString&
ICUServiceKey::currentID(UnicodeString& result) const
{
    return canonicalID(result);
}

UnicodeString&
ICUServiceKey::currentDescriptor(UnicodeString& result) const
{
    prefix(result);
    result.append(PREFIX_DELIMITER);
    return currentID(result);
}

UBool
ICUServiceKey::fallback()
{
    return false;
}

UBool
ICUServiceKey::isFallbackOf(const UnicodeString& id) const
{
    return id == _id;
}

UnicodeString&
ICUServiceKey::prefix(UnicodeString& result) const
{
    return result;
}

UnicodeString&
ICUServiceKey::parsePrefix(UnicodeString& result)
{
    int32_t n = result.indexOf(PREFIX_DELIMITER);
    if (n < 0) {
        n = 0;
    }
    result.remove(n);
    return result;
}

UnicodeString&
ICUServiceKey::parseSuffix(UnicodeString& result)
{
    int32_t n = result.indexOf(PREFIX_DELIMITER);
    if (n >= 0) {
        result.remove(0, n+1);
    }
    return result;
}

#ifdef SERVICE_DEBUG
UnicodeString&
ICUServiceKey::debug(UnicodeString& result) const
{
    debugClass(result);
    result.append((UnicodeString)" id: ");
    result.append(_id);
    return result;
}

UnicodeString&
ICUServiceKey::debugClass(UnicodeString& result) const
{
    return result.append((UnicodeString)"ICUServiceKey");
}
#endif

UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ICUServiceKey)

/*
******************************************************************
*/

ICUServiceFactory::~ICUServiceFactory() {}

SimpleFactory::SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id, UBool visible)
: _instance(instanceToAdopt), _id(id), _visible(visible)
{
}

SimpleFactory::~SimpleFactory()
{
    delete _instance;
}

UObject*
SimpleFactory::create(const ICUServiceKey& key, const ICUService* service, UErrorCode& status) const
{
    if (U_SUCCESS(status)) {
        UnicodeString temp;
        if (_id == key.currentID(temp)) {
            return service->cloneInstance(_instance);
        }
    }
    return NULL;
}

void
SimpleFactory::updateVisibleIDs(Hashtable& result, UErrorCode& status) const
{
    if (_visible) {
        result.put(_id, (void*)this, status); // cast away const
    } else {
        result.remove(_id);
    }
}

UnicodeString&
SimpleFactory::getDisplayName(const UnicodeString& id, const Locale& /* locale */, UnicodeString& result) const
{
    if (_visible && _id == id) {
        result = _id;
    } else {
        result.setToBogus();
    }
    return result;
}

#ifdef SERVICE_DEBUG
UnicodeString&
SimpleFactory::debug(UnicodeString& toAppendTo) const
{
    debugClass(toAppendTo);
    toAppendTo.append((UnicodeString)" id: ");
    toAppendTo.append(_id);
    toAppendTo.append((UnicodeString)", visible: ");
    toAppendTo.append(_visible ? (UnicodeString)"T" : (UnicodeString)"F");
    return toAppendTo;
}

UnicodeString&
SimpleFactory::debugClass(UnicodeString& toAppendTo) const
{
    return toAppendTo.append((UnicodeString)"SimpleFactory");
}
#endif

UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleFactory)

/*
******************************************************************
*/

ServiceListener::~ServiceListener() {}

UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ServiceListener)

/*
******************************************************************
*/

// Record the actual id for this service in the cache, so we can return it
// even if we succeed later with a different id.
class CacheEntry : public UMemory {
private:
    int32_t refcount;

public:
    UnicodeString actualDescriptor;
    UObject* service;

    /**
    * Releases a reference to the shared resource.
    */
    ~CacheEntry() {
        delete service;
    }

    CacheEntry(const UnicodeString& _actualDescriptor, UObject* _service)
        : refcount(1), actualDescriptor(_actualDescriptor), service(_service) {
    }

    /**
    * Instantiation creates an initial reference, so don't call this
    * unless you're creating a new pointer to this.  Management of
    * that pointer will have to know how to deal with refcounts.
    * Return true if the resource has not already been released.
    */
    CacheEntry* ref() {
        ++refcount;
        return this;
    }

    /**
    * Destructions removes a reference, so don't call this unless
    * you're removing pointer to this somewhere.  Management of that
    * pointer will have to know how to deal with refcounts.  Once
    * the refcount drops to zero, the resource is released.  Return
    * false if the resource has been released.
    */
    CacheEntry* unref() {
        if ((--refcount) == 0) {
            delete this;
            return NULL;
        }
        return this;
    }

    /**
    * Return true if there is at least one reference to this and the
    * resource has not been released.
    */
    UBool isShared() const {
        return refcount > 1;
    }
};

// Deleter for serviceCache
U_CDECL_BEGIN
static void U_CALLCONV
cacheDeleter(void* obj) {
    U_NAMESPACE_USE ((CacheEntry*)obj)->unref();
}

U_CDECL_END

/*
******************************************************************
*/

class DNCache : public UMemory {
public:
    Hashtable cache;
    const Locale locale;

    DNCache(const Locale& _locale)
        : cache(), locale(_locale)
    {
        // cache.setKeyDeleter(uprv_deleteUObject);
    }
};


/*
******************************************************************
*/

StringPair*
StringPair::create(const UnicodeString& displayName,
                   const UnicodeString& id,
                   UErrorCode& status)
{
    if (U_SUCCESS(status)) {
        StringPair* sp = new StringPair(displayName, id);
        if (sp == NULL || sp->isBogus()) {
            status = U_MEMORY_ALLOCATION_ERROR;
            delete sp;
            return NULL;
        }
        return sp;
    }
    return NULL;
}

UBool
StringPair::isBogus() const {
    return displayName.isBogus() || id.isBogus();
}

StringPair::StringPair(const UnicodeString& _displayName,
                       const UnicodeString& _id)
: displayName(_displayName)
, id(_id)
{
}

U_CDECL_BEGIN
static void U_CALLCONV
userv_deleteStringPair(void *obj) {
    U_NAMESPACE_USE delete (StringPair*) obj;
}
U_CDECL_END

/*
******************************************************************
*/

static UMutex lock;

ICUService::ICUService()
: name()
, timestamp(0)
, factories(NULL)
, serviceCache(NULL)
, idCache(NULL)
, dnCache(NULL)
{
}

ICUService::ICUService(const UnicodeString& newName)
: name(newName)
, timestamp(0)
, factories(NULL)
, serviceCache(NULL)
, idCache(NULL)
, dnCache(NULL)
{
}

ICUService::~ICUService()
{
    {
        Mutex mutex(&lock);
        clearCaches();
        delete factories;
        factories = NULL;
    }
}

UObject*
ICUService::get(const UnicodeString& descriptor, UErrorCode& status) const
{
    return get(descriptor, NULL, status);
}

UObject*
ICUService::get(const UnicodeString& descriptor, UnicodeString* actualReturn, UErrorCode& status) const
{
    UObject* result = NULL;
    ICUServiceKey* key = createKey(&descriptor, status);
    if (key) {
        result = getKey(*key, actualReturn, status);
        delete key;
    }
    return result;
}

UObject*
ICUService::getKey(ICUServiceKey& key, UErrorCode& status) const
{
    return getKey(key, NULL, status);
}

// this is a vector that subclasses of ICUService can override to further customize the result object
// before returning it.  All other public get functions should call this one.

UObject*
ICUService::getKey(ICUServiceKey& key, UnicodeString* actualReturn, UErrorCode& status) const
{
    return getKey(key, actualReturn, NULL, status);
}

// make it possible to call reentrantly on systems that don't have reentrant mutexes.
// we can use this simple approach since we know the situation where we're calling
// reentrantly even without knowing the thread.
class XMutex : public UMemory {
public:
    inline XMutex(UMutex *mutex, UBool reentering)
        : fMutex(mutex)
        , fActive(!reentering)
    {
        if (fActive) umtx_lock(fMutex);
    }
    inline ~XMutex() {
        if (fActive) umtx_unlock(fMutex);
    }

private:
    UMutex  *fMutex;
    UBool fActive;
};

// called only by factories, treat as private
UObject*
ICUService::getKey(ICUServiceKey& key, UnicodeString* actualReturn, const ICUServiceFactory* factory, UErrorCode& status) const
{
    if (U_FAILURE(status)) {
        return NULL;
    }

    if (isDefault()) {
        return handleDefault(key, actualReturn, status);
    }

    ICUService* ncthis = (ICUService*)this; // cast away semantic const

    CacheEntry* result = NULL;
    {
        // The factory list can't be modified until we're done,
        // otherwise we might update the cache with an invalid result.
        // The cache has to stay in synch with the factory list.
        // ICU doesn't have monitors so we can't use rw locks, so
        // we single-thread everything using this service, for now.

        // if factory is not null, we're calling from within the mutex,
        // and since some unix machines don't have reentrant mutexes we
        // need to make sure not to try to lock it again.
        XMutex mutex(&lock, factory != NULL);

        if (serviceCache == NULL) {
            ncthis->serviceCache = new Hashtable(status);
            if (ncthis->serviceCache == NULL) {
                status = U_MEMORY_ALLOCATION_ERROR;
                return NULL;
            }
            if (U_FAILURE(status)) {
                delete serviceCache;
                return NULL;
            }
            serviceCache->setValueDeleter(cacheDeleter);
        }

        UnicodeString currentDescriptor;
        LocalPointer<UVector> cacheDescriptorList;
        UBool putInCache = false;

        int32_t startIndex = 0;
        int32_t limit = factories->size();
        UBool cacheResult = true;

        if (factory != NULL) {
            for (int32_t i = 0; i < limit; ++i) {
                if (factory == (const ICUServiceFactory*)factories->elementAt(i)) {
                    startIndex = i + 1;
                    break;
                }
            }
            if (startIndex == 0) {
                // throw new InternalError("Factory " + factory + "not registered with service: " + this);
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return NULL;
            }
            cacheResult = false;
        }

        do {
            currentDescriptor.remove();
            key.currentDescriptor(currentDescriptor);
            result = (CacheEntry*)serviceCache->get(currentDescriptor);
            if (result != NULL) {
                break;
            }

            // first test of cache failed, so we'll have to update
            // the cache if we eventually succeed-- that is, if we're
            // going to update the cache at all.
            putInCache = true;

            int32_t index = startIndex;
            while (index < limit) {
                ICUServiceFactory* f = (ICUServiceFactory*)factories->elementAt(index++);
                LocalPointer<UObject> service(f->create(key, this, status));
                if (U_FAILURE(status)) {
                    return NULL;
                }
                if (service.isValid()) {
                    result = new CacheEntry(currentDescriptor, service.getAlias());
                    if (result == NULL) {
                        status = U_MEMORY_ALLOCATION_ERROR;
                        return NULL;
                    }
                    service.orphan(); // result now owns service.

                    goto outerEnd;
                }
            }

            // prepare to load the cache with all additional ids that
            // will resolve to result, assuming we'll succeed.  We
            // don't want to keep querying on an id that's going to
            // fallback to the one that succeeded, we want to hit the
            // cache the first time next goaround.
            if (cacheDescriptorList.isNull()) {
                cacheDescriptorList.adoptInsteadAndCheckErrorCode(new UVector(uprv_deleteUObject, NULL, 5, status), status);
                if (U_FAILURE(status)) {
                    return NULL;
                }
            }

            LocalPointer<UnicodeString> idToCache(new UnicodeString(currentDescriptor), status);
            if (U_FAILURE(status)) {
                return NULL;
            }
            if (idToCache->isBogus()) {
                status = U_MEMORY_ALLOCATION_ERROR;
                return NULL;
            }
            cacheDescriptorList->adoptElement(idToCache.orphan(), status);
            if (U_FAILURE(status)) {
                return NULL;
            }
        } while (key.fallback());
outerEnd:

        if (result != NULL) {
            if (putInCache && cacheResult) {
                serviceCache->put(result->actualDescriptor, result, status);
                if (U_FAILURE(status)) {
                    return NULL;
                }

                if (cacheDescriptorList.isValid()) {
                    for (int32_t i = cacheDescriptorList->size(); --i >= 0;) {
                        UnicodeString* desc = (UnicodeString*)cacheDescriptorList->elementAt(i);

                        serviceCache->put(*desc, result, status);
                        if (U_FAILURE(status)) {
                            return NULL;
                        }

                        result->ref();
                        cacheDescriptorList->removeElementAt(i);
                    }
                }
            }

            if (actualReturn != NULL) {
                // strip null prefix
                if (result->actualDescriptor.indexOf((UChar)0x2f) == 0) { // U+002f=slash (/)
                    actualReturn->remove();
                    actualReturn->append(result->actualDescriptor,
                        1,
                        result->actualDescriptor.length() - 1);
                } else {
                    *actualReturn = result->actualDescriptor;
                }

                if (actualReturn->isBogus()) {
                    status = U_MEMORY_ALLOCATION_ERROR;
                    delete result;
                    return NULL;
                }
            }

            UObject* service = cloneInstance(result->service);
            if (putInCache && !cacheResult) {
                delete result;
            }
            return service;
        }
    }

    return handleDefault(key, actualReturn, status);
}

UObject*
ICUService::handleDefault(const ICUServiceKey& /* key */, UnicodeString* /* actualIDReturn */, UErrorCode& /* status */) const
{
    return NULL;
}

UVector&
ICUService::getVisibleIDs(UVector& result, UErrorCode& status) const {
    return getVisibleIDs(result, NULL, status);
}

UVector&
ICUService::getVisibleIDs(UVector& result, const UnicodeString* matchID, UErrorCode& status) const
{
    result.removeAllElements();

    if (U_FAILURE(status)) {
        return result;
    }
    UObjectDeleter *savedDeleter = result.setDeleter(uprv_deleteUObject);

    {
        Mutex mutex(&lock);
        const Hashtable* map = getVisibleIDMap(status);
        if (map != NULL) {
            ICUServiceKey* fallbackKey = createKey(matchID, status);

            for (int32_t pos = UHASH_FIRST; U_SUCCESS(status); ) {
                const UHashElement* e = map->nextElement(pos);
                if (e == NULL) {
                    break;
                }

                const UnicodeString* id = (const UnicodeString*)e->key.pointer;
                if (fallbackKey != NULL) {
                    if (!fallbackKey->isFallbackOf(*id)) {
                        continue;
                    }
                }

                LocalPointer<UnicodeString> idClone(id->clone(), status);
                result.adoptElement(idClone.orphan(), status);
            }
            delete fallbackKey;
        }
    }
    if (U_FAILURE(status)) {
        result.removeAllElements();
    }
    result.setDeleter(savedDeleter);
    return result;
}

const Hashtable*
ICUService::getVisibleIDMap(UErrorCode& status) const {
    if (U_FAILURE(status)) return NULL;

    // must only be called when lock is already held

    ICUService* ncthis = (ICUService*)this; // cast away semantic const
    if (idCache == NULL) {
        ncthis->idCache = new Hashtable(status);
        if (idCache == NULL) {
            status = U_MEMORY_ALLOCATION_ERROR;
        } else if (factories != NULL) {
            for (int32_t pos = factories->size(); --pos >= 0;) {
                ICUServiceFactory* f = (ICUServiceFactory*)factories->elementAt(pos);
                f->updateVisibleIDs(*idCache, status);
            }
            if (U_FAILURE(status)) {
                delete idCache;
                ncthis->idCache = NULL;
            }
        }
    }

    return idCache;
}


UnicodeString&
ICUService::getDisplayName(const UnicodeString& id, UnicodeString& result) const
{
    return getDisplayName(id, result, Locale::getDefault());
}

UnicodeString&
ICUService::getDisplayName(const UnicodeString& id, UnicodeString& result, const Locale& locale) const
{
    {
        UErrorCode status = U_ZERO_ERROR;
        Mutex mutex(&lock);
        const Hashtable* map = getVisibleIDMap(status);
        if (map != NULL) {
            ICUServiceFactory* f = (ICUServiceFactory*)map->get(id);
            if (f != NULL) {
                f->getDisplayName(id, locale, result);
                return result;
            }

            // fallback
            status = U_ZERO_ERROR;
            ICUServiceKey* fallbackKey = createKey(&id, status);
            while (fallbackKey != NULL && fallbackKey->fallback()) {
                UnicodeString us;
                fallbackKey->currentID(us);
                f = (ICUServiceFactory*)map->get(us);
                if (f != NULL) {
                    f->getDisplayName(id, locale, result);
                    delete fallbackKey;
                    return result;
                }
            }
            delete fallbackKey;
        }
    }
    result.setToBogus();
    return result;
}

UVector&
ICUService::getDisplayNames(UVector& result, UErrorCode& status) const
{
    return getDisplayNames(result, Locale::getDefault(), NULL, status);
}


UVector&
ICUService::getDisplayNames(UVector& result, const Locale& locale, UErrorCode& status) const
{
    return getDisplayNames(result, locale, NULL, status);
}

UVector&
ICUService::getDisplayNames(UVector& result,
                            const Locale& locale,
                            const UnicodeString* matchID,
                            UErrorCode& status) const
{
    result.removeAllElements();
    result.setDeleter(userv_deleteStringPair);
    if (U_SUCCESS(status)) {
        ICUService* ncthis = (ICUService*)this; // cast away semantic const
        Mutex mutex(&lock);

        if (dnCache != NULL && dnCache->locale != locale) {
            delete dnCache;
            ncthis->dnCache = NULL;
        }

        if (dnCache == NULL) {
            const Hashtable* m = getVisibleIDMap(status);
            if (U_FAILURE(status)) {
                return result;
            }
            ncthis->dnCache = new DNCache(locale);
            if (dnCache == NULL) {
                status = U_MEMORY_ALLOCATION_ERROR;
                return result;
            }

            int32_t pos = UHASH_FIRST;
            const UHashElement* entry = NULL;
            while ((entry = m->nextElement(pos)) != NULL) {
                const UnicodeString* id = (const UnicodeString*)entry->key.pointer;
                ICUServiceFactory* f = (ICUServiceFactory*)entry->value.pointer;
                UnicodeString dname;
                f->getDisplayName(*id, locale, dname);
                if (dname.isBogus()) {
                    status = U_MEMORY_ALLOCATION_ERROR;
                } else {
                    dnCache->cache.put(dname, (void*)id, status); // share pointer with visibleIDMap
                    if (U_SUCCESS(status)) {
                        continue;
                    }
                }
                delete dnCache;
                ncthis->dnCache = NULL;
                return result;
            }
        }
    }

    ICUServiceKey* matchKey = createKey(matchID, status);
    /* To ensure that all elements in the hashtable are iterated, set pos to -1.
     * nextElement(pos) will skip the position at pos and begin the iteration
     * at the next position, which in this case will be 0.
     */
    int32_t pos = UHASH_FIRST;
    const UHashElement *entry = NULL;
    while ((entry = dnCache->cache.nextElement(pos)) != NULL) {
        const UnicodeString* id = (const UnicodeString*)entry->value.pointer;
        if (matchKey != NULL && !matchKey->isFallbackOf(*id)) {
            continue;
        }
        const UnicodeString* dn = (const UnicodeString*)entry->key.pointer;
        StringPair* sp = StringPair::create(*id, *dn, status);
        result.adoptElement(sp, status);
        if (U_FAILURE(status)) {
            result.removeAllElements();
            break;
        }
    }
    delete matchKey;

    return result;
}

URegistryKey
ICUService::registerInstance(UObject* objToAdopt, const UnicodeString& id, UErrorCode& status)
{
    return registerInstance(objToAdopt, id, true, status);
}

URegistryKey
ICUService::registerInstance(UObject* objToAdopt, const UnicodeString& id, UBool visible, UErrorCode& status)
{
    ICUServiceKey* key = createKey(&id, status);
    if (key != NULL) {
        UnicodeString canonicalID;
        key->canonicalID(canonicalID);
        delete key;

        ICUServiceFactory* f = createSimpleFactory(objToAdopt, canonicalID, visible, status);
        if (f != NULL) {
            return registerFactory(f, status);
        }
    }
    delete objToAdopt;
    return NULL;
}

ICUServiceFactory*
ICUService::createSimpleFactory(UObject* objToAdopt, const UnicodeString& id, UBool visible, UErrorCode& status)
{
    if (U_SUCCESS(status)) {
        if ((objToAdopt != NULL) && (!id.isBogus())) {
            return new SimpleFactory(objToAdopt, id, visible);
        }
        status = U_ILLEGAL_ARGUMENT_ERROR;
    }
    return NULL;
}

URegistryKey
ICUService::registerFactory(ICUServiceFactory* factoryToAdopt, UErrorCode& status)
{
    LocalPointer<ICUServiceFactory>lpFactoryToAdopt(factoryToAdopt);
    if (U_FAILURE(status) || factoryToAdopt == nullptr) {
        return nullptr;
    }
    {
        Mutex mutex(&lock);

        if (factories == nullptr) {
            LocalPointer<UVector> lpFactories(new UVector(uprv_deleteUObject, nullptr, status), status);
            if (U_FAILURE(status)) {
                return nullptr;
            }
            factories = lpFactories.orphan();
        }
        factories->insertElementAt(lpFactoryToAdopt.orphan(), 0, status);
        if (U_SUCCESS(status)) {
            clearCaches();
        }
    }   // Close of mutex lock block.

    if (U_SUCCESS(status)) {
        notifyChanged();
        return (URegistryKey)factoryToAdopt;
    } else {
        return nullptr;
    }
}

UBool
ICUService::unregister(URegistryKey rkey, UErrorCode& status)
{
    ICUServiceFactory *factory = (ICUServiceFactory*)rkey;
    UBool result = false;
    if (factory != NULL && factories != NULL) {
        Mutex mutex(&lock);

        if (factories->removeElement(factory)) {
            clearCaches();
            result = true;
        } else {
            status = U_ILLEGAL_ARGUMENT_ERROR;
            delete factory;
        }
    }
    if (result) {
        notifyChanged();
    }
    return result;
}

void
ICUService::reset()
{
    {
        Mutex mutex(&lock);
        reInitializeFactories();
        clearCaches();
    }
    notifyChanged();
}

void
ICUService::reInitializeFactories()
{
    if (factories != NULL) {
        factories->removeAllElements();
    }
}

UBool
ICUService::isDefault() const
{
    return countFactories() == 0;
}

ICUServiceKey*
ICUService::createKey(const UnicodeString* id, UErrorCode& status) const
{
    return (U_FAILURE(status) || id == NULL) ? NULL : new ICUServiceKey(*id);
}

void
ICUService::clearCaches()
{
    // callers synchronize before use
    ++timestamp;
    delete dnCache;
    dnCache = NULL;
    delete idCache;
    idCache = NULL;
    delete serviceCache; serviceCache = NULL;
}

void
ICUService::clearServiceCache()
{
    // callers synchronize before use
    delete serviceCache; serviceCache = NULL;
}

UBool
ICUService::acceptsListener(const EventListener& l) const
{
    return dynamic_cast<const ServiceListener*>(&l) != NULL;
}

void
ICUService::notifyListener(EventListener& l) const
{
    ((ServiceListener&)l).serviceChanged(*this);
}

UnicodeString&
ICUService::getName(UnicodeString& result) const
{
    return result.append(name);
}

int32_t
ICUService::countFactories() const
{
    return factories == NULL ? 0 : factories->size();
}

int32_t
ICUService::getTimestamp() const
{
    return timestamp;
}

U_NAMESPACE_END

/* UCONFIG_NO_SERVICE */
#endif
