/*****************************************************************************
 * Copyright (c) 2014-2025 OpenRCT2 developers
 *
 * For a complete list of all authors, please refer to contributors.md
 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is licensed under the GNU General Public License version 3.
 *****************************************************************************/

#include "MusicObject.h"

#include "../AssetPackManager.h"
#include "../Context.h"
#include "../OpenRCT2.h"
#include "../PlatformEnvironment.h"
#include "../audio/AudioContext.h"
#include "../audio/AudioSource.h"
#include "../core/IStream.hpp"
#include "../core/Json.hpp"
#include "../core/Path.hpp"
#include "../drawing/Image.h"
#include "../localisation/StringIds.h"
#include "../ride/Ride.h"
#include "RideObject.h"

#include <memory>

namespace OpenRCT2
{
    constexpr size_t kDefaultBytesPerTick = 1378;

    void MusicObject::Load()
    {
        GetStringTable().Sort();
        NameStringId = LanguageAllocateObjectString(GetName());

        // Start with base images
        _loadedSampleTable.LoadFrom(_sampleTable, 0, _sampleTable.GetCount());

        // Override samples from asset packs
        auto context = GetContext();
        auto assetManager = context->GetAssetPackManager();
        if (assetManager != nullptr)
        {
            assetManager->LoadSamplesForObject(GetIdentifier(), _loadedSampleTable);
        }

        // Load metadata of samples
        auto& audioContext = GetContext()->GetAudioContext();
        for (auto& track : _tracks)
        {
            auto stream = track.Asset.GetStream();
            if (stream != nullptr)
            {
                auto source = audioContext.CreateStreamFromWAV(std::move(stream));
                if (source != nullptr)
                {
                    track.BytesPerTick = source->GetBytesPerSecond() / 40;
                    track.Size = source->GetLength();
                    source->Release();
                }
                else
                {
                    track.BytesPerTick = kDefaultBytesPerTick;
                    track.Size = track.Asset.GetSize();
                }
            }
            else
            {
                track.BytesPerTick = kDefaultBytesPerTick;
                track.Size = track.Asset.GetSize();
            }
        }

        _hasPreview = !!GetImageTable().GetCount();
        _previewImageId = LoadImages();
    }

    void MusicObject::Unload()
    {
        LanguageFreeObjectString(NameStringId);
        UnloadImages();

        _hasPreview = false;
        _previewImageId = 0;
        NameStringId = 0;
    }

    void MusicObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const
    {
        // Write (no image)
        int32_t x = width / 2;
        int32_t y = height / 2;
        if (_hasPreview)
            GfxDrawSprite(rt, ImageId(_previewImageId), { 0, 0 });
        else
            DrawTextBasic(rt, { x, y }, STR_WINDOW_NO_IMAGE, {}, { TextAlignment::CENTRE });
    }

    bool MusicObject::HasPreview() const
    {
        return _hasPreview;
    }

    void MusicObject::ReadJson(IReadObjectContext* context, json_t& root)
    {
        _originalStyleId = {};
        _rideTypes.clear();
        _tracks.clear();
        _niceFactor = MusicNiceFactor::Neutral;

        auto& properties = root["properties"];
        if (properties != nullptr)
        {
            const auto& originalStyleId = properties["originalStyleId"];
            if (originalStyleId.is_number_integer())
            {
                _originalStyleId = originalStyleId.get<uint8_t>();
            }

            const auto& niceFactor = properties["niceFactor"];
            if (niceFactor.is_number_integer())
            {
                _niceFactor = static_cast<MusicNiceFactor>(std::clamp<int8_t>(niceFactor.get<int8_t>(), -1, 1));
            }

            const auto& jRideTypes = properties["rideTypes"];
            if (jRideTypes.is_array())
            {
                ParseRideTypes(jRideTypes);
            }

            auto& jTracks = properties["tracks"];
            if (jTracks.is_array())
            {
                ParseTracks(*context, jTracks);
            }
        }

        PopulateTablesFromJson(context, root);
    }

    void MusicObject::ParseRideTypes(const json_t& jRideTypes)
    {
        for (const auto& jRideType : jRideTypes)
        {
            auto szRideType = Json::GetString(jRideType);
            if (!szRideType.empty())
            {
                auto rideType = RideObject::ParseRideType(szRideType);
                if (rideType != kRideTypeNull)
                {
                    _rideTypes.push_back(rideType);
                }
            }
        }
    }

    void MusicObject::ParseTracks(IReadObjectContext& context, json_t& jTracks)
    {
        auto& entries = _sampleTable.GetEntries();
        for (auto& jTrack : jTracks)
        {
            if (jTrack.is_object())
            {
                MusicObjectTrack track;
                track.Name = Json::GetString(jTrack["name"]);
                track.Composer = Json::GetString(jTrack["composer"]);
                auto source = Json::GetString(jTrack["source"]);
                if (source.empty())
                {
                    context.LogError(ObjectError::InvalidProperty, "Invalid audio track definition.");
                }
                else
                {
                    auto asset = GetAsset(context, source);

                    auto& entry = entries.emplace_back();
                    entry.Asset = asset;

                    track.Asset = asset;
                    _tracks.push_back(std::move(track));
                }
            }
        }
    }

    std::optional<uint8_t> MusicObject::GetOriginalStyleId() const
    {
        return _originalStyleId;
    }

    bool MusicObject::SupportsRideType(ride_type_t rideType, bool onlyAllowIfExplicitlyListed)
    {
        if (_rideTypes.size() == 0)
        {
            // Some rides, like the merry-go-round, need music objects to explicitly list them.
            return !onlyAllowIfExplicitlyListed;
        }

        auto it = std::find(_rideTypes.begin(), _rideTypes.end(), rideType);
        return it != _rideTypes.end();
    }

    size_t MusicObject::GetTrackCount() const
    {
        return _tracks.size();
    }

    const MusicObjectTrack* MusicObject::GetTrack(size_t trackIndex) const
    {
        if (_tracks.size() > trackIndex)
        {
            return &_tracks[trackIndex];
        }
        return {};
    }

    Audio::IAudioSource* MusicObject::GetTrackSample(size_t trackIndex) const
    {
        return _loadedSampleTable.LoadSample(static_cast<uint32_t>(trackIndex));
    }

    ObjectAsset MusicObject::GetAsset(IReadObjectContext& context, std::string_view path)
    {
        if (path.find("$RCT2:DATA/") == 0)
        {
            auto& env = GetContext()->GetPlatformEnvironment();
            auto path2 = env.FindFile(DirBase::rct2, DirId::data, path.substr(11));
            return ObjectAsset(path2);
        }

        return context.GetAsset(path);
    }
} // namespace OpenRCT2
