/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * 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.
 */

#include "image_loader_manager.h"

#include <algorithm>

#include <base/containers/array_view.h>
#include <base/containers/string_view.h>
#include <base/containers/type_traits.h>
#include <base/containers/unique_ptr.h>
#include <base/containers/vector.h>
#include <base/namespace.h>
#include <base/util/uid_util.h>
#include <core/image/intf_animated_image.h>
#include <core/image/intf_image_container.h>
#include <core/image/intf_image_loader_manager.h>
#include <core/io/intf_file.h>
#include <core/io/intf_file_manager.h>
#include <core/log.h>
#include <core/namespace.h>
#include <core/perf/cpu_perf_scope.h>

CORE_BEGIN_NAMESPACE()
using BASE_NS::array_view;
using BASE_NS::make_unique;
using BASE_NS::move;
using BASE_NS::string_view;
using BASE_NS::unique_ptr;
using BASE_NS::vector;

ImageLoaderManager::ImageLoaderManager(IFileManager& fileManager) : fileManager_(fileManager)
{
    for (const auto* typeInfo : GetPluginRegister().GetTypeInfos(IImageLoaderManager::ImageLoaderTypeInfo::UID)) {
        if (typeInfo && (typeInfo->typeUid == IImageLoaderManager::ImageLoaderTypeInfo::UID)) {
            const auto* imageLoaderInfo = static_cast<const IImageLoaderManager::ImageLoaderTypeInfo*>(typeInfo);
            if (imageLoaderInfo->createLoader &&
                std::none_of(imageLoaders_.cbegin(),
                    imageLoaders_.cend(),
                    [&uid = imageLoaderInfo->uid](const RegisteredImageLoader& loader) { return loader.uid == uid; })) {
                imageLoaders_.push_back({imageLoaderInfo->uid, imageLoaderInfo->createLoader(imageLoaderInfo->token)});
            }
        }
    }
    GetPluginRegister().AddListener(*this);
}

ImageLoaderManager::~ImageLoaderManager()
{
    GetPluginRegister().RemoveListener(*this);
}

void ImageLoaderManager::RegisterImageLoader(IImageLoader::Ptr imageLoader)
{
    if (!imageLoader) {
        CORE_LOG_D("imageLoader is null, Not adding.");
        return;
    }

    // NOTE: We just add the registered unique pointers to a vector. The vector is not really used for anything else.
    // And the loaders cannot be currently unregistered.
    imageLoaders_.push_back({{}, move(imageLoader)});
}

ImageLoaderManager::LoadResult ImageLoaderManager::LoadImage(const string_view uri, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadImage()", uri, CORE_PROFILER_DEFAULT_COLOR);

    // Load 12 bytes (maximum header size of currently implemented file types)
    IFile::Ptr file = fileManager_.OpenFile(uri);
    if (!file) {
        return ResultFailure("Can not open image.");
    }

    return LoadImage(*file, loadFlags);
}

ImageLoaderManager::LoadResult ImageLoaderManager::LoadImage(
    const BASE_NS::string_view uri, uint32_t loadFlags, uint32_t rowCount, uint32_t columnCount)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadImage()", uri, CORE_PROFILER_DEFAULT_COLOR);

    // Load 12 bytes (maximum header size of currently implemented file types)
    IFile::Ptr file = fileManager_.OpenFile(uri);
    if (!file) {
        return ResultFailure("Can not open image.");
    }

    CORE_CPU_PERF_SCOPE("CORE", "LoadImage(file)", "", CORE_PROFILER_DEFAULT_COLOR);

    const uint64_t byteLength = 12u;

    // Read header of the file to a buffer.
    unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(byteLength));
    const uint64_t read = file->Read(buffer.get(), byteLength);
    if (read != byteLength) {
        CORE_LOG_E("Can not read file header!!!");
        return ResultFailure("Can not read file header.");
    }
    file->Seek(0);
    for (auto& loader : imageLoaders_) {
        if (loader.instance) {
            if (loader.instance->CanLoad(array_view<const uint8_t>(buffer.get(), static_cast<size_t>(byteLength)))) {
                LoadResult res = loader.instance->Load(*file, loadFlags, rowCount, columnCount);
                return res;
            }
        }
    }
    return ResultFailure("Image loader not found for this format.");
}

ImageLoaderManager::LoadResult ImageLoaderManager::LoadImage(IFile& file, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadImage(file)", "", CORE_PROFILER_DEFAULT_COLOR);

    const uint64_t byteLength = 12u;

    // Read header of the file to a buffer.
    unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(byteLength));
    const uint64_t read = file.Read(buffer.get(), byteLength);
    if (read != byteLength) {
        return ResultFailure("Can not read file header.");
    }
    file.Seek(0);

    for (auto& loader : imageLoaders_) {
        if (loader.instance &&
            loader.instance->CanLoad(array_view<const uint8_t>(buffer.get(), static_cast<size_t>(byteLength)))) {
            return loader.instance->Load(file, loadFlags);
        }
    }
    return ResultFailure("Image loader not found for this format.");
}

ImageLoaderManager::LoadResult ImageLoaderManager::LoadImage(
    array_view<const uint8_t> imageFileBytes, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadImage(bytes)", "", CORE_PROFILER_DEFAULT_COLOR);

    for (auto& loader : imageLoaders_) {
        if (loader.instance && loader.instance->CanLoad(imageFileBytes)) {
            return loader.instance->Load(imageFileBytes, loadFlags);
        }
    }

    return ResultFailure("Image loader not found for this format.");
}

ImageLoaderManager::LoadAnimatedResult ImageLoaderManager::LoadAnimatedImage(const string_view uri, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadAnimatedImage()", uri, CORE_PROFILER_DEFAULT_COLOR);

    // Load 12 bytes (maximum header size of currently implemented file types)
    IFile::Ptr file = fileManager_.OpenFile(uri);
    if (!file) {
        return ResultFailureAnimated("Can not open image.");
    }

    return LoadAnimatedImage(*file, loadFlags);
}

ImageLoaderManager::LoadAnimatedResult ImageLoaderManager::LoadAnimatedImage(IFile& file, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadAnimatedImage(file)", "", CORE_PROFILER_DEFAULT_COLOR);

    const uint64_t byteLength = 12u;

    // Read header of the file to a buffer.
    unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(byteLength));
    const uint64_t read = file.Read(buffer.get(), byteLength);
    if (read != byteLength) {
        return ResultFailureAnimated("Can not read file header.");
    }
    file.Seek(0);

    for (auto& loader : imageLoaders_) {
        if (loader.instance &&
            loader.instance->CanLoad(array_view<const uint8_t>(buffer.get(), static_cast<size_t>(byteLength)))) {
            return loader.instance->LoadAnimatedImage(file, loadFlags);
        }
    }
    return ResultFailureAnimated("Image loader not found for this format.");
}

ImageLoaderManager::LoadAnimatedResult ImageLoaderManager::LoadAnimatedImage(
    array_view<const uint8_t> imageFileBytes, uint32_t loadFlags)
{
    CORE_CPU_PERF_SCOPE("CORE", "LoadAnimatedImage(bytes)", "", CORE_PROFILER_DEFAULT_COLOR);

    for (auto& loader : imageLoaders_) {
        if (loader.instance && loader.instance->CanLoad(imageFileBytes)) {
            return loader.instance->LoadAnimatedImage(imageFileBytes, loadFlags);
        }
    }

    return ResultFailureAnimated("Image loader not found for this format.");
}

ImageLoaderManager::LoadResult ImageLoaderManager::ResultFailure(const string_view error)
{
    LoadResult result{
        false,   // if success
        "",      // array error[128];
        nullptr  // the image;
    };

    // Copy the error string
    const auto count = std::min(error.size(), sizeof(result.error) - 1);
    error.copy(result.error, count);
    result.error[count] = '\0';

    return result;
}

ImageLoaderManager::LoadResult ImageLoaderManager::ResultSuccess(IImageContainer::Ptr image)
{
    return LoadResult{
        true,        // if success
        "",          // array error[128];
        move(image)  // the image;
    };
}

ImageLoaderManager::LoadAnimatedResult ImageLoaderManager::ResultFailureAnimated(const string_view error)
{
    LoadAnimatedResult result{
        false,   // if success
        "",      // array error[128];
        nullptr  // the image;
    };

    // Copy the error string
    const auto count = std::min(error.size(), sizeof(result.error) - 1);
    error.copy(result.error, count);
    result.error[count] = '\0';

    return result;
}

ImageLoaderManager::LoadAnimatedResult ImageLoaderManager::ResultSuccessAnimated(IAnimatedImage::Ptr image)
{
    return LoadAnimatedResult{
        true,        // if success
        "",          // array error[128];
        move(image)  // the image;
    };
}

vector<IImageLoaderManager::ImageType> ImageLoaderManager::GetSupportedTypes() const
{
    vector<IImageLoaderManager::ImageType> allTypes;
    for (const auto& loader : imageLoaders_) {
        if (loader.instance) {
            const auto types = loader.instance->GetSupportedTypes();
            allTypes.append(types.cbegin(), types.cend());
        } else {
            CORE_LOG_E("Missing image loader instance (%s)", BASE_NS::to_string(loader.uid).data());
        }
    }
    return allTypes;
}

void ImageLoaderManager::OnTypeInfoEvent(EventType type, array_view<const ITypeInfo* const> typeInfos)
{
    for (const auto* typeInfo : typeInfos) {
        if (typeInfo && (typeInfo->typeUid == IImageLoaderManager::ImageLoaderTypeInfo::UID)) {
            const auto* imageLoaderInfo = static_cast<const IImageLoaderManager::ImageLoaderTypeInfo*>(typeInfo);
            if (type == EventType::ADDED) {
                if (imageLoaderInfo->createLoader &&
                    std::none_of(imageLoaders_.cbegin(),
                        imageLoaders_.cend(),
                        [&uid = imageLoaderInfo->uid](
                            const RegisteredImageLoader& loader) { return loader.uid == uid; })) {
                    imageLoaders_.push_back(
                        {imageLoaderInfo->uid, imageLoaderInfo->createLoader(imageLoaderInfo->token)});
                }
            } else if (type == EventType::REMOVED) {
                imageLoaders_.erase(std::remove_if(imageLoaders_.begin(),
                                        imageLoaders_.end(),
                                        [&uid = imageLoaderInfo->uid](
                                            const RegisteredImageLoader& loader) { return loader.uid == uid; }),
                    imageLoaders_.cend());
            }
        }
    }
}

CORE_END_NAMESPACE()