/*****************************************************************************
 * Copyright (c) 2014-2020 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 "Banner.h"

#include "../Context.h"
#include "../Game.h"
#include "../core/Memory.hpp"
#include "../core/String.hpp"
#include "../interface/Window.h"
#include "../localisation/Formatter.h"
#include "../localisation/Localisation.h"
#include "../management/Finance.h"
#include "../network/network.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
#include "../ride/Track.h"
#include "../windows/Intent.h"
#include "../world/TileElementsView.h"
#include "Map.h"
#include "MapAnimation.h"
#include "Park.h"
#include "Scenery.h"

#include <algorithm>
#include <cstring>
#include <iterator>
#include <limits>

static std::vector<Banner> _banners;

std::string Banner::GetText() const
{
    Formatter ft;
    FormatTextTo(ft);
    return format_string(STR_STRINGID, ft.Data());
}

void Banner::FormatTextTo(Formatter& ft, bool addColour) const
{
    if (addColour)
    {
        auto formatToken = FormatTokenFromTextColour(text_colour);
        auto tokenText = FormatTokenToString(formatToken, true);
        ft.Add<rct_string_id>(STR_STRING_STRINGID);
        ft.Add<const char*>(tokenText.data());
    }

    FormatTextTo(ft);
}

void Banner::FormatTextTo(Formatter& ft) const
{
    if (flags & BANNER_FLAG_NO_ENTRY)
    {
        ft.Add<rct_string_id>(STR_NO_ENTRY);
    }
    else if (flags & BANNER_FLAG_LINKED_TO_RIDE)
    {
        auto ride = get_ride(ride_index);
        if (ride != nullptr)
        {
            ride->FormatNameTo(ft);
        }
        else
        {
            ft.Add<rct_string_id>(STR_DEFAULT_SIGN);
        }
    }
    else if (text.empty())
    {
        ft.Add<rct_string_id>(STR_DEFAULT_SIGN);
    }
    else
    {
        ft.Add<rct_string_id>(STR_STRING).Add<const char*>(text.c_str());
    }
}

/**
 *
 *  rct2: 0x006B7EAB
 */
static RideId banner_get_ride_index_at(const CoordsXYZ& bannerCoords)
{
    TileElement* tileElement = map_get_first_element_at(bannerCoords);
    RideId resultRideIndex = RideId::GetNull();
    if (tileElement == nullptr)
        return resultRideIndex;
    do
    {
        if (tileElement->GetType() != TileElementType::Track)
            continue;

        RideId rideIndex = tileElement->AsTrack()->GetRideIndex();
        auto ride = get_ride(rideIndex);
        if (ride == nullptr || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
            continue;

        if ((tileElement->GetClearanceZ()) + (4 * COORDS_Z_STEP) <= bannerCoords.z)
            continue;

        resultRideIndex = rideIndex;
    } while (!(tileElement++)->IsLastForTile());

    return resultRideIndex;
}

static BannerIndex BannerGetNewIndex()
{
    for (BannerIndex::UnderlyingType bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++)
    {
        if (bannerIndex < _banners.size())
        {
            if (_banners[bannerIndex].IsNull())
            {
                return BannerIndex::FromUnderlying(bannerIndex);
            }
        }
        else
        {
            _banners.emplace_back();
            return BannerIndex::FromUnderlying(bannerIndex);
        }
    }
    return BannerIndex::GetNull();
}

/**
 *
 *  rct2: 0x006B9CB0
 */
void banner_init()
{
    _banners.clear();
}

TileElement* banner_get_tile_element(BannerIndex bannerIndex)
{
    auto banner = GetBanner(bannerIndex);
    if (banner != nullptr)
    {
        auto tileElement = map_get_first_element_at(banner->position);
        if (tileElement != nullptr)
        {
            do
            {
                if (tileElement->GetBannerIndex() == bannerIndex)
                {
                    return tileElement;
                }
            } while (!(tileElement++)->IsLastForTile());
        }
    }
    return nullptr;
}

WallElement* banner_get_scrolling_wall_tile_element(BannerIndex bannerIndex)
{
    auto banner = GetBanner(bannerIndex);
    if (banner == nullptr)
        return nullptr;

    auto tileElement = map_get_first_element_at(banner->position);
    if (tileElement == nullptr)
        return nullptr;

    do
    {
        auto wallElement = tileElement->AsWall();

        if (wallElement == nullptr)
            continue;

        auto* wallEntry = wallElement->GetEntry();
        if (wallEntry->scrolling_mode == SCROLLING_MODE_NONE)
            continue;
        if (wallElement->GetBannerIndex() != bannerIndex)
            continue;
        return wallElement;
    } while (!(tileElement++)->IsLastForTile());

    return nullptr;
}

/**
 *
 *  rct2: 0x006B7D86
 */
RideId banner_get_closest_ride_index(const CoordsXYZ& mapPos)
{
    static constexpr const std::array NeighbourCheckOrder = {
        CoordsXY{ COORDS_XY_STEP, 0 },
        CoordsXY{ -COORDS_XY_STEP, 0 },
        CoordsXY{ 0, COORDS_XY_STEP },
        CoordsXY{ 0, -COORDS_XY_STEP },
        CoordsXY{ -COORDS_XY_STEP, +COORDS_XY_STEP },
        CoordsXY{ +COORDS_XY_STEP, -COORDS_XY_STEP },
        CoordsXY{ +COORDS_XY_STEP, +COORDS_XY_STEP },
        CoordsXY{ -COORDS_XY_STEP, +COORDS_XY_STEP },
        CoordsXY{ 0, 0 },
    };

    for (const auto& neighhbourCoords : NeighbourCheckOrder)
    {
        RideId rideIndex = banner_get_ride_index_at({ CoordsXY{ mapPos } + neighhbourCoords, mapPos.z });
        if (!rideIndex.IsNull())
        {
            return rideIndex;
        }
    }

    auto rideIndex = RideId::GetNull();
    auto resultDistance = std::numeric_limits<int32_t>::max();
    for (auto& ride : GetRideManager())
    {
        if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
            continue;

        auto rideCoords = ride.overall_view;
        if (rideCoords.IsNull())
            continue;

        int32_t distance = abs(mapPos.x - rideCoords.x) + abs(mapPos.y - rideCoords.y);
        if (distance < resultDistance)
        {
            resultDistance = distance;
            rideIndex = ride.id;
        }
    }
    return rideIndex;
}

void banner_reset_broken_index()
{
    for (BannerIndex::UnderlyingType index = 0; index < _banners.size(); index++)
    {
        const auto bannerId = BannerIndex::FromUnderlying(index);
        auto tileElement = banner_get_tile_element(bannerId);
        if (tileElement == nullptr)
        {
            auto banner = GetBanner(bannerId);
            if (banner != nullptr)
            {
                banner->type = BANNER_NULL;
            }
        }
    }
}

void fix_duplicated_banners()
{
    // For each banner in the map, check if the banner index is in use already, and if so, create a new entry for it
    std::vector<bool> activeBanners;
    activeBanners.resize(MAX_BANNERS);

    for (int y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
    {
        for (int x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
        {
            const auto bannerPos = TileCoordsXY{ x, y }.ToCoordsXY();
            for (auto* bannerElement : OpenRCT2::TileElementsView<BannerElement>(bannerPos))
            {
                auto bannerIndex = bannerElement->GetIndex();
                if (bannerIndex == BannerIndex::GetNull())
                    continue;

                const auto index = bannerIndex.ToUnderlying();
                if (activeBanners[index])
                {
                    log_info(
                        "Duplicated banner with index %d found at x = %d, y = %d and z = %d.", index, x, y,
                        bannerElement->base_height);

                    // Banner index is already in use by another banner, so duplicate it
                    auto newBanner = CreateBanner();
                    if (newBanner == nullptr)
                    {
                        log_error("Failed to create new banner.");
                        continue;
                    }
                    Guard::Assert(!activeBanners[newBanner->id.ToUnderlying()]);

                    // Copy over the original banner, but update the location
                    const auto* oldBanner = GetBanner(bannerIndex);
                    if (oldBanner != nullptr)
                    {
                        auto newBannerId = newBanner->id;

                        *newBanner = *oldBanner;
                        newBanner->id = newBannerId;
                        newBanner->position = { x, y };
                    }

                    bannerElement->SetIndex(newBanner->id);
                }

                // Mark banner index as in-use
                activeBanners[index] = true;
            }
        }
    }
}

Banner* BannerElement::GetBanner() const
{
    return ::GetBanner(GetIndex());
}

BannerSceneryEntry* BannerElement::GetEntry() const
{
    auto banner = GetBanner();
    if (banner != nullptr)
    {
        return get_banner_entry(banner->type);
    }
    return nullptr;
}

BannerIndex BannerElement::GetIndex() const
{
    return index;
}

void BannerElement::SetIndex(BannerIndex newIndex)
{
    index = newIndex;
}

uint8_t BannerElement::GetPosition() const
{
    return position;
}

void BannerElement::SetPosition(uint8_t newPosition)
{
    position = newPosition;
}

uint8_t BannerElement::GetAllowedEdges() const
{
    return AllowedEdges & 0b00001111;
}

void BannerElement::SetAllowedEdges(uint8_t newEdges)
{
    AllowedEdges &= ~0b00001111;
    AllowedEdges |= (newEdges & 0b00001111);
}

void BannerElement::ResetAllowedEdges()
{
    AllowedEdges |= 0b00001111;
}

void UnlinkAllRideBanners()
{
    for (auto& banner : _banners)
    {
        if (!banner.IsNull())
        {
            banner.flags &= ~BANNER_FLAG_LINKED_TO_RIDE;
            banner.ride_index = RideId::GetNull();
        }
    }
}

void UnlinkAllBannersForRide(RideId rideId)
{
    for (auto& banner : _banners)
    {
        if (!banner.IsNull() && (banner.flags & BANNER_FLAG_LINKED_TO_RIDE) && banner.ride_index == rideId)
        {
            banner.flags &= ~BANNER_FLAG_LINKED_TO_RIDE;
            banner.ride_index = RideId::GetNull();
            banner.text = {};
        }
    }
}

Banner* GetBanner(BannerIndex id)
{
    const auto index = id.ToUnderlying();
    if (index < _banners.size())
    {
        auto banner = &_banners[index];
        if (banner != nullptr && !banner->IsNull())
        {
            return banner;
        }
    }
    return nullptr;
}

Banner* GetOrCreateBanner(BannerIndex id)
{
    const auto index = id.ToUnderlying();
    if (index < MAX_BANNERS)
    {
        if (index >= _banners.size())
        {
            _banners.resize(index + 1);
        }
        // Create the banner
        auto& banner = _banners[index];
        banner.id = id;
        return &banner;
    }
    return nullptr;
}

Banner* CreateBanner()
{
    auto bannerIndex = BannerGetNewIndex();
    auto banner = GetOrCreateBanner(bannerIndex);
    if (banner != nullptr)
    {
        banner->id = bannerIndex;
        banner->flags = 0;
        banner->type = 0;
        banner->text = {};
        banner->colour = COLOUR_WHITE;
        banner->text_colour = COLOUR_WHITE;
    }
    return banner;
}

void DeleteBanner(BannerIndex id)
{
    auto* const banner = GetBanner(id);
    if (banner != nullptr)
    {
        *banner = {};
    }
}

void TrimBanners()
{
    if (_banners.size() > 0)
    {
        auto lastBannerId = _banners.size() - 1;
        while (lastBannerId != std::numeric_limits<size_t>::max() && _banners[lastBannerId].IsNull())
        {
            lastBannerId--;
        }
        _banners.resize(lastBannerId + 1);
        _banners.shrink_to_fit();
    }
}

size_t GetNumBanners()
{
    size_t count = 0;
    for (const auto& banner : _banners)
    {
        if (!banner.IsNull())
        {
            count++;
        }
    }
    return count;
}

bool HasReachedBannerLimit()
{
    auto numBanners = GetNumBanners();
    return numBanners >= MAX_BANNERS;
}
