// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "Lightmap.h"
#include "Scene.h"
#include "Engine/Core/Log.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/SceneLightmapsData.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#if USE_EDITOR
#include "Engine/ContentImporters/ImportTexture.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Graphics/Textures/TextureData.h"
#endif
#include "Engine/Serialization/Serialization.h"

void LightmapSettings::Serialize(SerializeStream& stream, const void* otherObj)
{
    SERIALIZE_GET_OTHER_OBJ(LightmapSettings);

    SERIALIZE(IndirectLightingIntensity);
    SERIALIZE(GlobalObjectsScale);
    SERIALIZE(ChartsPadding);
    SERIALIZE(AtlasSize);
    SERIALIZE(BounceCount);
    SERIALIZE(CompressLightmaps);
    SERIALIZE(UseGeometryWithNoMaterials);
    SERIALIZE(Quality);
}

void LightmapSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
    DESERIALIZE(IndirectLightingIntensity);
    DESERIALIZE(GlobalObjectsScale);
    DESERIALIZE(ChartsPadding);
    DESERIALIZE(AtlasSize);
    DESERIALIZE(BounceCount);
    DESERIALIZE(CompressLightmaps);
    DESERIALIZE(UseGeometryWithNoMaterials);
    DESERIALIZE(Quality);
}

Lightmap::Lightmap(SceneLightmapsData* manager, int32 index, const SavedLightmapInfo& info)
    : _manager(manager)
    , _index(index)
{
    // Try to load textures with given IDs
    _textures[0] = Content::LoadAsync<Texture>(info.Lightmap0);
    _textures[1] = Content::LoadAsync<Texture>(info.Lightmap1);
    _textures[2] = Content::LoadAsync<Texture>(info.Lightmap2);
}

void Lightmap::UpdateTexture(Texture* texture, int32 index)
{
    auto& prev = _textures[index];
    if (prev.Get() != texture)
    {
        LOG(Info, "Changing lightmap {0} texture {1} from '{2}' to '{3}'", _index, index, prev ? prev->ToString() : String::Empty, texture ? texture->ToString() : String::Empty);
        prev = texture;
    }
}

void Lightmap::EnsureSize(int32 size)
{
    ASSERT(size >= 4 && size <= 4096);
#if USE_EDITOR
    _size = size;
#endif

    // Check every texture
    for (int32 textureIndex = 0; textureIndex < ARRAY_COUNT(_textures); textureIndex++)
    {
        auto& texture = _textures[textureIndex];

        // Check if has texture linked
        if (texture)
        {
            // Wait for the loading
            if (texture->WaitForLoaded())
            {
                // Unlink texture that cannot be loaded
                LOG(Warning, "Lightmap::EnsureSize failed to load texture");
                texture = nullptr;
            }
            else
            {
                // Check if need to resize texture
                if (texture->GetTexture()->Width() != size || texture->GetTexture()->Height() != size)
                {
                    // Unlink texture and import new with valid size
                    LOG(Info, "Changing lightmap {0}:{1} size from {2} to {3}", _index, textureIndex, texture->GetTexture()->Size(), size);
                    texture = nullptr;
                }
            }
        }

        // Check if has missing texture
        if (texture == nullptr)
        {
#if USE_EDITOR

#if COMPILE_WITH_ASSETS_IMPORTER

            Guid id = Guid::New();
            LOG(Info, "Cannot load lightmap ({1}:{2}). Creating new one with ID={0}.", id, _index, textureIndex);
            String assetPath;
            _manager->GetCachedLightmapPath(&assetPath, _index, textureIndex);

            // Import texture with custom options
            ImportTexture::Options options;
            options.Type = TextureFormatType::HdrRGBA;
            options.IndependentChannels = true;
#if PLATFORM_WINDOWS
            options.Compress = _manager->GetScene()->GetLightmapSettings().CompressLightmaps;
#else
            options.Compress = false; // TODO: use better BC7 compressor that would handle alpha more precisely (otherwise lightmaps have artifacts, see TextureTool.stb.cpp)
#endif
            options.IsAtlas = false;
            options.sRGB = false;
            options.NeverStream = false;
            options.InternalLoad.Bind<Lightmap, &Lightmap::OnInitLightmap>(this);
            if (AssetsImportingManager::Create(AssetsImportingManager::CreateTextureTag, assetPath, id, &options))
            {
                LOG(Error, "Cannot import empty lightmap {0}:{1}", _index, textureIndex);
            }
            auto result = Content::LoadAsync<Texture>(id);
#else
			auto result = nullptr;
#endif
            if (result == nullptr)
            {
                LOG(Error, "Cannot load new lightmap {0}:{1}", _index, textureIndex);
            }

            // Update asset
            texture = result;
#else
			LOG(Warning, "Cannot create empty lightmap. Saving data to the cooked content is disabled.");
#endif
        }
    }
}

bool Lightmap::IsReady() const
{
    // TODO: link for events and cache this to be a boolean value
    return _textures[0] && _textures[0]->GetTexture()->ResidentMipLevels() > 0
            && _textures[1] && _textures[1]->GetTexture()->ResidentMipLevels() > 0
            && _textures[2] && _textures[2]->GetTexture()->ResidentMipLevels() > 0;
}

#if USE_EDITOR

bool Lightmap::OnInitLightmap(TextureData& image)
{
    // Initialize with transparent image
    image.Width = image.Height = _size;
    image.Depth = 1;
    image.Format = PixelFormat::R8G8B8A8_UNorm;
    image.Items.Resize(1);
    image.Items[0].Mips.Resize(1);
    auto& mip = image.Items[0].Mips[0];
    mip.RowPitch = image.Width * 4;
    mip.DepthPitch = mip.RowPitch * image.Height;
    mip.Lines = image.Height;
    mip.Data.Allocate(mip.DepthPitch);
    Platform::MemoryClear(mip.Data.Get(), mip.DepthPitch);
    return false;
}

#endif
