/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

#include "CubismFramework.hpp"
#include "Utils/CubismDebug.hpp"
#include "Utils/CubismJson.hpp"
#include "Id/CubismIdManager.hpp"
#include "Rendering/CubismRenderer.hpp"

#ifdef CSM_DEBUG_MEMORY_LEAKING

#include <vector>

#endif


//--------- LIVE2D NAMESPACE ------------
namespace Live2D { namespace Cubism { namespace Framework {

// Framework内で使う定数の宣言
namespace Constant {
    const csmInt32 VertexOffset = 0;
    const csmInt32 VertexStep = 2;
}

// ファイルスコープの変数を初期化
namespace {
csmBool                           s_isStarted = false;
csmBool                           s_isInitialized = false;
ICubismAllocator*                 s_allocator = NULL;
const CubismFramework::Option*    s_option = NULL;
CubismIdManager*                  s_cubismIdManager = NULL;

}

inline ICubismAllocator* GetAllocator()
{
    return s_allocator;
}

#ifdef CSM_DEBUG_MEMORY_LEAKING

namespace {
std::vector<void*>*    s_allocationList = NULL;
}

#endif

csmBool CubismFramework::StartUp(ICubismAllocator* allocator, const Option* option)
{
    if(s_isStarted)
    {
        CubismLogInfo("CubismFramework::StartUp() is already done.");
        return s_isStarted;
    }

    s_option = option;
    if (s_option != NULL)
    {
        Core::csmSetLogFunction(s_option->LogFunction);
    }

    if (allocator == NULL)
    {
        CubismLogWarning("CubismFramework::StartUp() failed, need allocator instance.");
        s_isStarted = false;
    }
    else
    {
        s_allocator = allocator;
        s_isStarted = true;
    }

    // Live2D Cubism Coreバージョン情報を表示
    if(s_isStarted)
    {
        const Core::csmVersion version = Core::csmGetVersion();

        const csmUint32 major = static_cast<csmUint32>((version & 0xFF000000) >> 24);
        const csmUint32 minor = static_cast<csmUint32>((version & 0x00FF0000) >> 16);
        const csmUint32 patch = static_cast<csmUint32>((version & 0x0000FFFF));
        const csmUint32 versionNumber = version;

        CubismLogInfo("Live2D Cubism Core version: %02d.%02d.%04d (%d)", major, minor, patch, versionNumber);
    }

    CubismLogInfo("CubismFramework::StartUp() is complete.");

    return s_isStarted;
}

void CubismFramework::CleanUp()
{
    s_isStarted = false;
    s_isInitialized = false;
    s_allocator = NULL;
    s_option = NULL;
    s_cubismIdManager = NULL;
#ifdef CSM_DEBUG_MEMORY_LEAKING
    s_allocationList = NULL;
#endif
}

csmBool CubismFramework::IsStarted()
{
    return s_isStarted;
}

void CubismFramework::Initialize()
{
    CSM_ASSERT(s_isStarted);
    if(!s_isStarted)
    {
        CubismLogWarning("CubismFramework is not started.");
        return;
    }

    // --- s_isInitializedによる連続初期化ガード ---
    // 連続してリソース確保が行われないようにする。
    // 再度Initialize()するには先にDispose()を実行する必要がある。
    if (s_isInitialized)
    {
        CubismLogWarning("CubismFramework::Initialize() skipped, already initialized.");
        return;
    }

#ifdef CSM_DEBUG_MEMORY_LEAKING
    s_allocationList = CSM_NEW std::vector<void*>();
#endif

    //---- static 初期化 ----
    Utils::Value::StaticInitializeNotForClientCall();

    s_cubismIdManager = CSM_NEW CubismIdManager();

    s_isInitialized = true;

    CubismLogInfo("CubismFramework::Initialize() is complete.");
}

void CubismFramework::Dispose()
{
    CSM_ASSERT(s_isStarted);
    if (!s_isStarted)
    {
        CubismLogWarning("CubismFramework is not started.");
        return;
    }

    // --- s_isInitializedによる未初期化解放ガード ---
    // Dispose()するには先にInitialize()を実行する必要がある。
    if (!s_isInitialized) // false...リソース未確保の場合
    {
        CubismLogWarning("CubismFramework::Dispose() skipped, not initialized.");
        return;
    }

    //---- static 解放 ----
    Utils::Value::StaticReleaseNotForClientCall();

    CSM_DELETE(s_cubismIdManager);

    //レンダラの静的リソース（シェーダプログラム他）を解放する
    Rendering::CubismRenderer::StaticRelease();

#ifdef CSM_DEBUG_MEMORY_LEAKING

    for (csmUint32 i = 0; i < s_allocationList->size(); ++i)
    {
        CubismLogInfo("Memory leaking: 0x%p", s_allocationList->at(i));
    }

    if (s_allocationList->size() == 0)
    {
        CubismLogInfo("No memory leaking");
    }

    CSM_DELETE(s_allocationList);

#endif

    s_isInitialized = false;

    CubismLogInfo("CubismFramework::Dispose() is complete.");
}

csmBool CubismFramework::IsInitialized()
{
    return s_isInitialized;
}

void CubismFramework::CoreLogFunction(const csmChar* message)
{
    // Return if logging not possible.
    if (!Core::csmGetLogFunction())
    {
        return;
    }

    Core::csmGetLogFunction()(message);
}

CubismFramework::Option::LogLevel CubismFramework::GetLoggingLevel()
{
    if (s_option != NULL)
    {
        return s_option->LoggingLevel;
    }
    return Option::LogLevel_Off;
}

CubismIdManager* CubismFramework::GetIdManager()
{
    return s_cubismIdManager;
}

#ifdef CSM_DEBUG_MEMORY_LEAKING

void* CubismFramework::Allocate(csmSizeType size, const csmChar* fileName, csmInt32 lineNumber)
{
    void* address = GetAllocator()->Allocate(size);

    CubismLogVerbose("CubismFramework::Allocate(0x%p, %dbytes) %s(%d)", address, size, fileName, lineNumber);

    if (s_allocationList)
    {
        s_allocationList->push_back(address);
    }

    return address;
}

void* CubismFramework::AllocateAligned(csmSizeType size, csmUint32 alignment, const csmChar* fileName, csmInt32 lineNumber)
{
    void* address = GetAllocator()->AllocateAligned(size, alignment);

    CubismLogVerbose("CubismFramework::AllocateAligned(0x%p, a:%d, %dbytes) %s(%d)", address, alignment, size, fileName, lineNumber);

    if (s_allocationList)
    {
        s_allocationList->push_back(address);
    }

    return address;
}

void CubismFramework::Deallocate(void* address, const csmChar* fileName, csmInt32 lineNumber)
{
    if (!address)
    {
        return;
    }

    CubismLogVerbose("CubismFramework::Deallocate(0x%p) %s(%d)", address, fileName, lineNumber);

    if (s_allocationList)
    {
        for (std::vector<void*>::iterator iter = s_allocationList->begin(); iter != s_allocationList->end(); ++iter)
        {
            if (*iter != address)
            {
                continue;
            }

            s_allocationList->erase(iter);
            break;
        }
    }

    GetAllocator()->Deallocate(address);
}

void CubismFramework::DeallocateAligned(void* address, const csmChar* fileName, csmInt32 lineNumber)
{
    if (!address)
    {
        return;
    }

    CubismLogVerbose("CubismFramework::DeallocateAligned(0x%p) %s(%d)", address, fileName, lineNumber);

    if (s_allocationList)
    {
        for (std::vector<void*>::iterator iter = s_allocationList->begin(); iter != s_allocationList->end(); ++iter)
        {
            if (*iter != address)
            {
                continue;
            }

            s_allocationList->erase(iter);
            break;
        }
    }

    GetAllocator()->DeallocateAligned(address);
}

#else

void* CubismFramework::Allocate(csmSizeType size)
{
    return GetAllocator()->Allocate(size);
}

void* CubismFramework::AllocateAligned(csmSizeType size, csmUint32 alignment)
{
    return GetAllocator()->AllocateAligned(size, alignment);
}

void CubismFramework::Deallocate(void* address)
{
    if (!address)
    {
        return;
    }

    GetAllocator()->Deallocate(address);
}

void CubismFramework::DeallocateAligned(void* address)
{
    if (!address)
    {
        return;
    }

    GetAllocator()->DeallocateAligned(address);
}

#endif

}}}
//--------- LIVE2D NAMESPACE ------------

#ifdef CSM_DEBUG_MEMORY_LEAKING

void* operator new(Live2D::Cubism::Framework::csmSizeType size, Live2D::Cubism::Framework::CubismAllocationTag tag, const Live2D::Cubism::Framework::csmChar* fileName, Live2D::Cubism::Framework::csmInt32 lineNumber)
{
    return Live2D::Cubism::Framework::CubismFramework::Allocate(size, fileName, lineNumber);
}

void* operator new (Live2D::Cubism::Framework::csmSizeType size, Live2D::Cubism::Framework::csmUint32 alignment, Live2D::Cubism::Framework::CubismAllocationAlignedTag tag, const Live2D::Cubism::Framework::csmChar* fileName, Live2D::Cubism::Framework::csmInt32 lineNumber)
{
    return Live2D::Cubism::Framework::CubismFramework::AllocateAligned(size, alignment, fileName, lineNumber);
}

void operator delete(void* address, Live2D::Cubism::Framework::CubismAllocationTag tag, const Live2D::Cubism::Framework::csmChar* fileName, Live2D::Cubism::Framework::csmInt32 lineNumber)
{
    Live2D::Cubism::Framework::CubismFramework::Deallocate(address, fileName, lineNumber);
}

void  operator delete(void* address, Live2D::Cubism::Framework::CubismAllocationAlignedTag tag, const Live2D::Cubism::Framework::csmChar* fileName, Live2D::Cubism::Framework::csmInt32 lineNumber)
{
    Live2D::Cubism::Framework::CubismFramework::DeallocateAligned(address, fileName, lineNumber);
}

#else

void* operator new(Live2D::Cubism::Framework::csmSizeType size, Live2D::Cubism::Framework::CubismAllocationTag tag)
{
    return Live2D::Cubism::Framework::CubismFramework::Allocate(size);
}

void* operator new (Live2D::Cubism::Framework::csmSizeType size, Live2D::Cubism::Framework::csmUint32 alignment, Live2D::Cubism::Framework::CubismAllocationAlignedTag tag)
{
    return Live2D::Cubism::Framework::CubismFramework::AllocateAligned(size, alignment);
}

void operator delete(void* address, Live2D::Cubism::Framework::CubismAllocationTag tag)
{
    Live2D::Cubism::Framework::CubismFramework::Deallocate(address);
}

void  operator delete(void* address, Live2D::Cubism::Framework::CubismAllocationAlignedTag tag)
{
    Live2D::Cubism::Framework::CubismFramework::DeallocateAligned(address);
}

#endif
