/*
 * Carla library counter
 * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#ifndef CARLA_LIB_COUNTER_HPP_INCLUDED
#define CARLA_LIB_COUNTER_HPP_INCLUDED

#include "CarlaLibUtils.hpp"
#include "CarlaMutex.hpp"
#include "LinkedList.hpp"

// -----------------------------------------------------------------------

class LibCounter
{
public:
    LibCounter() noexcept
        : fMutex(),
          fLibs() {}

    ~LibCounter() noexcept
    {
        // might have some leftovers
        for (LinkedList<Lib>::Itenerator it = fLibs.begin2(); it.valid(); it.next())
        {
            static Lib libFallback = { nullptr, nullptr, 0, false };

            Lib& lib(it.getValue(libFallback));
            CARLA_SAFE_ASSERT_CONTINUE(lib.count > 0);
            CARLA_SAFE_ASSERT_CONTINUE(lib.lib != nullptr);

            // all libs should be closed by now except those explicitly marked non-delete
            CARLA_SAFE_ASSERT(! lib.canDelete);

            if (! lib_close(lib.lib))
                carla_stderr("LibCounter cleanup failed, reason:\n%s", lib_error(lib.filename));

            lib.lib = nullptr;

            if (lib.filename != nullptr)
            {
                delete[] lib.filename;
                lib.filename = nullptr;
            }
        }

        fLibs.clear();
    }

    lib_t open(const char* const filename, const bool canDelete = true) noexcept
    {
        CARLA_SAFE_ASSERT_RETURN(filename != nullptr && filename[0] != '\0', nullptr);

        // try duplicating filename first, it can throw
        const char* dfilename = nullptr;

        try {
            dfilename = carla_strdup(filename);
        } CARLA_SAFE_EXCEPTION_RETURN("LibCounter::open", nullptr);

        const CarlaMutexLocker cml(fMutex);

        for (LinkedList<Lib>::Itenerator it = fLibs.begin2(); it.valid(); it.next())
        {
            static Lib libFallback = { nullptr, nullptr, 0, false };

            Lib& lib(it.getValue(libFallback));
            CARLA_SAFE_ASSERT_CONTINUE(lib.count > 0);
            CARLA_SAFE_ASSERT_CONTINUE(lib.filename != nullptr);

            if (std::strcmp(lib.filename, filename) == 0)
            {
                // will not be needed
                delete[] dfilename;

                ++lib.count;
                return lib.lib;
            }
        }

        const lib_t libPtr = lib_open(filename);

        if (libPtr == nullptr)
        {
            delete[] dfilename;
            return nullptr;
        }

        Lib lib;
        lib.lib       = libPtr;
        lib.filename  = dfilename;
        lib.count     = 1;
        lib.canDelete = canDelete;
#ifdef BUILD_BRIDGE
        lib.canDelete = true;
#endif

        if (fLibs.append(lib))
            return libPtr;

        delete[] dfilename;
        return nullptr;
    }

    bool close(const lib_t libPtr) noexcept
    {
        CARLA_SAFE_ASSERT_RETURN(libPtr != nullptr, false);

        const CarlaMutexLocker cml(fMutex);

        for (LinkedList<Lib>::Itenerator it = fLibs.begin2(); it.valid(); it.next())
        {
            static Lib libFallback = { nullptr, nullptr, 0, false };

            Lib& lib(it.getValue(libFallback));
            CARLA_SAFE_ASSERT_CONTINUE(lib.count > 0);
            CARLA_SAFE_ASSERT_CONTINUE(lib.lib != nullptr);

            if (lib.lib != libPtr)
                continue;

            if (lib.count == 1 && ! lib.canDelete)
                return true;

            if (--lib.count == 0)
            {
                if (! lib_close(lib.lib))
                    carla_stderr("LibCounter::close() failed, reason:\n%s", lib_error(lib.filename));

                lib.lib = nullptr;

                if (lib.filename != nullptr)
                {
                    delete[] lib.filename;
                    lib.filename = nullptr;
                }

                fLibs.remove(it);
            }

            return true;
        }

        carla_safe_assert("invalid lib pointer", __FILE__, __LINE__);
        return false;
    }

    void setCanDelete(const lib_t libPtr, const bool canDelete)
    {
        CARLA_SAFE_ASSERT_RETURN(libPtr != nullptr,);

        const CarlaMutexLocker cml(fMutex);

        for (LinkedList<Lib>::Itenerator it = fLibs.begin2(); it.valid(); it.next())
        {
            static Lib libFallback = { nullptr, nullptr, 0, false };

            Lib& lib(it.getValue(libFallback));
            CARLA_SAFE_ASSERT_CONTINUE(lib.lib != nullptr);

            if (lib.lib != libPtr)
                continue;

            lib.canDelete = canDelete;
            return;
        }
    }

private:
    struct Lib {
        lib_t lib;
        const char* filename;
        int count;
        bool canDelete;
    };

    CarlaMutex fMutex;
    LinkedList<Lib> fLibs;
};

// -----------------------------------------------------------------------

#endif // CARLA_LIB_COUNTER_HPP_INCLUDED
