/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __PLUGIN_MANAGER_H__
#define __PLUGIN_MANAGER_H__

#include <dlfcn.h>
#include <sys/types.h>
#include <dirent.h>

#include <utils/String8.h>
#include <utils/Vector.h>
#include <utils/KeyedVector.h>

namespace android {

const char* const PLUGIN_MANAGER_CREATE = "create";
const char* const PLUGIN_MANAGER_DESTROY = "destroy";
const char* const PLUGIN_EXTENSION = ".so";

/**
 * This is the template class for Plugin manager.
 *
 * The DrmManager uses this class to handle the plugins.
 *
 */
template<typename Type>
class TPlugInManager {
private:
    typedef void*      HANDLE;
    typedef Type*      create_t(void);
    typedef void       destroy_t(Type*);
    typedef create_t*  FPCREATE;
    typedef destroy_t* FPDESTORY;

    typedef struct _PlugInContainer {
        String8   sPath;
        HANDLE    hHandle;
        FPCREATE  fpCreate;
        FPDESTORY fpDestory;
        Type*     pInstance;

        _PlugInContainer():
            sPath("")
            ,hHandle(NULL)
            ,fpCreate(NULL)
            ,fpDestory(NULL)
            ,pInstance(NULL)
            {}
    } PlugInContainer;

    typedef KeyedVector<String8, PlugInContainer*> PlugInMap;
    PlugInMap m_plugInMap;

    typedef Vector<String8> PlugInIdList;
    PlugInIdList m_plugInIdList;

public:
    /**
     * Load all the plug-ins in the specified directory
     *
     * @param[in] rsPlugInDirPath
     *     Directory path which plug-ins (dynamic library) are stored
     * @note Plug-ins should be implemented according to the specification
     */
    void loadPlugIns(const String8& rsPlugInDirPath) {
        Vector<String8> plugInFileList = getPlugInPathList(rsPlugInDirPath);

        if (!plugInFileList.isEmpty()) {
            for (unsigned int i = 0; i < plugInFileList.size(); ++i) {
                loadPlugIn(plugInFileList[i]);
            }
        }
    }

    /**
     * Unload all the plug-ins
     *
     */
    void unloadPlugIns() {
        for (unsigned int i = 0; i < m_plugInIdList.size(); ++i) {
            unloadPlugIn(m_plugInIdList[i]);
        }
        m_plugInIdList.clear();
    }

    /**
     * Get all the IDs of available plug-ins
     *
     * @return[in] plugInIdList
     *     String type Vector in which all plug-in IDs are stored
     */
    Vector<String8> getPlugInIdList() const {
        return m_plugInIdList;
    }

    /**
     * Get a plug-in reference of specified ID
     *
     * @param[in] rsPlugInId
     *     Plug-in ID to be used
     * @return plugIn
     *     Reference of specified plug-in instance
     */
    Type& getPlugIn(const String8& rsPlugInId) {
        if (!contains(rsPlugInId)) {
            // This error case never happens
        }
        return *(m_plugInMap.valueFor(rsPlugInId)->pInstance);
    }

public:
    /**
     * Load a plug-in stored in the specified path
     *
     * @param[in] rsPlugInPath
     *     Plug-in (dynamic library) file path
     * @note Plug-in should be implemented according to the specification
     */
    void loadPlugIn(const String8& rsPlugInPath) {
        if (contains(rsPlugInPath)) {
            return;
        }

        PlugInContainer* pPlugInContainer = new PlugInContainer();

        pPlugInContainer->hHandle = dlopen(rsPlugInPath.string(), RTLD_LAZY);

        if (NULL == pPlugInContainer->hHandle) {
            delete pPlugInContainer;
            pPlugInContainer = NULL;
            return;
        }

        pPlugInContainer->sPath = rsPlugInPath;
        pPlugInContainer->fpCreate
                = (FPCREATE)dlsym(pPlugInContainer->hHandle, PLUGIN_MANAGER_CREATE);
        pPlugInContainer->fpDestory
                = (FPDESTORY)dlsym(pPlugInContainer->hHandle, PLUGIN_MANAGER_DESTROY);

        if (NULL != pPlugInContainer->fpCreate && NULL != pPlugInContainer->fpDestory) {
            pPlugInContainer->pInstance = (Type*)pPlugInContainer->fpCreate();
            m_plugInIdList.add(rsPlugInPath);
            m_plugInMap.add(rsPlugInPath, pPlugInContainer);
        } else {
            dlclose(pPlugInContainer->hHandle);
            delete pPlugInContainer;
            pPlugInContainer = NULL;
            return;
        }
    }

    /**
     * Unload a plug-in stored in the specified path
     *
     * @param[in] rsPlugInPath
     *     Plug-in (dynamic library) file path
     */
    void unloadPlugIn(const String8& rsPlugInPath) {
        if (!contains(rsPlugInPath)) {
            return;
        }

        PlugInContainer* pPlugInContainer = m_plugInMap.valueFor(rsPlugInPath);
        pPlugInContainer->fpDestory(pPlugInContainer->pInstance);
        dlclose(pPlugInContainer->hHandle);

        m_plugInMap.removeItem(rsPlugInPath);
        delete pPlugInContainer;
        pPlugInContainer = NULL;
    }

private:
    /**
     * True if TPlugInManager contains rsPlugInId
     */
    bool contains(const String8& rsPlugInId) {
        return m_plugInMap.indexOfKey(rsPlugInId) != NAME_NOT_FOUND;
    }

    /**
     * Return file path list of plug-ins stored in the specified directory
     *
     * @param[in] rsDirPath
     *     Directory path in which plug-ins are stored
     * @return plugInFileList
     *     String type Vector in which file path of plug-ins are stored
     */
    Vector<String8> getPlugInPathList(const String8& rsDirPath) {
        Vector<String8> fileList;
        DIR* pDir = opendir(rsDirPath.string());
        struct dirent* pEntry;

        while (NULL != pDir && NULL != (pEntry = readdir(pDir))) {
            if (!isPlugIn(pEntry)) {
                continue;
            }
            String8 plugInPath;
            plugInPath += rsDirPath;
            plugInPath += "/";
            plugInPath += pEntry->d_name;

            fileList.add(plugInPath);
        }

        if (NULL != pDir) {
            closedir(pDir);
        }

        return fileList;
    }

    /**
     * True if the input name denotes plug-in
     */
    bool isPlugIn(const struct dirent* pEntry) const {
        String8 sName(pEntry->d_name);
        String8 extension(sName.getPathExtension());
        // Note that the plug-in extension must exactly match case
        return extension == String8(PLUGIN_EXTENSION);
    }

    /**
     * True if the input entry is "." or ".."
     */
    bool isDotOrDDot(const struct dirent* pEntry) const {
        String8 sName(pEntry->d_name);
        return "." == sName || ".." == sName;
    }

    /**
     * True if input entry is directory
     */
    bool isDirectory(const struct dirent* pEntry) const {
        return DT_DIR == pEntry->d_type;
    }

    /**
     * True if input entry is regular file
     */
    bool isRegularFile(const struct dirent* pEntry) const {
        return DT_REG == pEntry->d_type;
    }

    /**
     * True if input entry is link
     */
    bool isLink(const struct dirent* pEntry) const {
        return DT_LNK == pEntry->d_type;
    }
};

};

#endif /* __PLUGIN_MANAGER_H__ */