/*
 * Copyright 2017 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.
 */

#pragma once

#ifndef LOG_TAG
#warning "GrallocLoader.h included without LOG_TAG"
#endif

#include <memory>
#include <mutex>
#include <unordered_set>

#include <hardware/gralloc.h>
#include <hardware/hardware.h>
#include <log/log.h>
#include <mapper-hal/2.0/Mapper.h>
#include <mapper-passthrough/2.0/Gralloc0Hal.h>
#include <mapper-passthrough/2.0/Gralloc1Hal.h>

namespace android {
namespace hardware {
namespace graphics {
namespace mapper {
namespace V2_0 {
namespace passthrough {

class GrallocImportedBufferPool {
   public:
    static GrallocImportedBufferPool& getInstance() {
        // GraphicBufferMapper in framework is expected to be valid (and
        // leaked) during process termination.  We need to make sure IMapper,
        // and in turn, GrallocImportedBufferPool is valid as well.  Create
        // imported buffer pool on the heap (and let it leak) for the purpose.
        // Besides, all IMapper instances must share the same pool.  Make it a
        // singleton.
        //
        // However, there is no way to make sure gralloc0/gralloc1 are valid
        // during process termination.  Any use of static/global object in
        // gralloc0/gralloc1 that may be destructed during process termination
        // is potentially broken.
        static GrallocImportedBufferPool* singleton = new GrallocImportedBufferPool;
        return *singleton;
    }

    std::mutex* getMutex() { return &mMutex; }

    void* add(native_handle_t* bufferHandle) {
        std::lock_guard<std::mutex> lock(mMutex);
        return mBufferHandles.insert(bufferHandle).second ? bufferHandle : nullptr;
    }

    void removeLocked(native_handle* bufferHandle) { mBufferHandles.erase(bufferHandle); }

    native_handle_t* get(void* buffer) {
        auto bufferHandle = static_cast<native_handle_t*>(buffer);

        std::lock_guard<std::mutex> lock(mMutex);
        return mBufferHandles.count(bufferHandle) == 1 ? bufferHandle : nullptr;
    }

    const native_handle_t* getConst(void* buffer) {
        auto bufferHandle = static_cast<const native_handle_t*>(buffer);

        std::lock_guard<std::mutex> lock(mMutex);
        return mBufferHandles.count(bufferHandle) == 1 ? bufferHandle : nullptr;
    }

   private:
    std::mutex mMutex;
    std::unordered_set<const native_handle_t*> mBufferHandles;
};

// Inherit from V2_*::hal::Mapper and override imported buffer management functions
template <typename T>
class GrallocMapper : public T {
   protected:
    void* addImportedBuffer(native_handle_t* bufferHandle) override {
        return GrallocImportedBufferPool::getInstance().add(bufferHandle);
    }

    Error freeImportedBuffer(native_handle_t* bufferHandle) override {
        std::lock_guard<std::mutex> lock(*GrallocImportedBufferPool::getInstance().getMutex());
        Error error = this->mHal->freeBuffer(bufferHandle);
        if (error == Error::NONE) {
            GrallocImportedBufferPool::getInstance().removeLocked(bufferHandle);
        }
        return error;
    }

    native_handle_t* getImportedBuffer(void* buffer) const override {
        return GrallocImportedBufferPool::getInstance().get(buffer);
    }

    const native_handle_t* getConstImportedBuffer(void* buffer) const override {
        return GrallocImportedBufferPool::getInstance().getConst(buffer);
    }
};

class GrallocLoader {
   public:
    static IMapper* load() {
        const hw_module_t* module = loadModule();
        if (!module) {
            return nullptr;
        }
        auto hal = createHal(module);
        if (!hal) {
            return nullptr;
        }
        return createMapper(std::move(hal));
    }

    // load the gralloc module
    static const hw_module_t* loadModule() {
        const hw_module_t* module;
        int error = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
        if (error) {
            ALOGE("failed to get gralloc module");
            return nullptr;
        }

        return module;
    }

    // return the major api version of the module
    static int getModuleMajorApiVersion(const hw_module_t* module) {
        return (module->module_api_version >> 8) & 0xff;
    }

    // create a MapperHal instance
    static std::unique_ptr<hal::MapperHal> createHal(const hw_module_t* module) {
        int major = getModuleMajorApiVersion(module);
        switch (major) {
            case 1: {
                auto hal = std::make_unique<Gralloc1Hal>();
                return hal->initWithModule(module) ? std::move(hal) : nullptr;
            }
            case 0: {
                auto hal = std::make_unique<Gralloc0Hal>();
                return hal->initWithModule(module) ? std::move(hal) : nullptr;
            }
            default:
                ALOGE("unknown gralloc module major version %d", major);
                return nullptr;
        }
    }

    // create an IAllocator instance
    static IMapper* createMapper(std::unique_ptr<hal::MapperHal> hal) {
        auto mapper = std::make_unique<GrallocMapper<hal::Mapper>>();
        return mapper->init(std::move(hal)) ? mapper.release() : nullptr;
    }
};

}  // namespace passthrough
}  // namespace V2_0
}  // namespace mapper
}  // namespace graphics
}  // namespace hardware
}  // namespace android
