/*****************************************************************************
 * 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 "RideDemolishAction.h"

#include "../Cheats.h"
#include "../Context.h"
#include "../GameState.h"
#include "../core/MemoryStream.h"
#include "../drawing/Drawing.h"
#include "../interface/Window.h"
#include "../localisation/Localisation.h"
#include "../management/NewsItem.h"
#include "../peep/RideUseSystem.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
#include "../ui/UiContext.h"
#include "../ui/WindowManager.h"
#include "../world/Banner.h"
#include "../world/EntityList.h"
#include "../world/Park.h"
#include "MazeSetTrackAction.h"
#include "TrackRemoveAction.h"

using namespace OpenRCT2;

RideDemolishAction::RideDemolishAction(ride_id_t rideIndex, uint8_t modifyType)
    : _rideIndex(rideIndex)
    , _modifyType(modifyType)
{
}

void RideDemolishAction::AcceptParameters(GameActionParameterVisitor& visitor)
{
    visitor.Visit("ride", _rideIndex);
    visitor.Visit("modifyType", _modifyType);
}

uint32_t RideDemolishAction::GetCooldownTime() const
{
    return 1000;
}

void RideDemolishAction::Serialise(DataSerialiser& stream)
{
    GameAction::Serialise(stream);

    stream << DS_TAG(_rideIndex) << DS_TAG(_modifyType);
}

GameActions::Result::Ptr RideDemolishAction::Query() const
{
    auto ride = get_ride(_rideIndex);
    if (ride == nullptr)
    {
        log_warning("Invalid game command for ride %u", uint32_t(_rideIndex));
        return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DEMOLISH_RIDE, STR_NONE);
    }

    if (ride->lifecycle_flags & (RIDE_LIFECYCLE_INDESTRUCTIBLE | RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
        && _modifyType == RIDE_MODIFY_DEMOLISH)
    {
        return std::make_unique<GameActions::Result>(
            GameActions::Status::NoClearance, STR_CANT_DEMOLISH_RIDE,
            STR_LOCAL_AUTHORITY_FORBIDS_DEMOLITION_OR_MODIFICATIONS_TO_THIS_RIDE);
    }

    GameActions::Result::Ptr result = std::make_unique<GameActions::Result>();

    if (_modifyType == RIDE_MODIFY_RENEW)
    {
        if (ride->status != RideStatus::Closed && ride->status != RideStatus::Simulating)
        {
            return std::make_unique<GameActions::Result>(
                GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_MUST_BE_CLOSED_FIRST);
        }

        if (ride->num_riders > 0)
        {
            return std::make_unique<GameActions::Result>(
                GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_RIDE_NOT_YET_EMPTY);
        }

        if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED)
            || ride->GetRideTypeDescriptor().AvailableBreakdowns == 0)
        {
            return std::make_unique<GameActions::Result>(
                GameActions::Status::Disallowed, STR_CANT_REFURBISH_RIDE, STR_CANT_REFURBISH_NOT_NEEDED);
        }

        result->ErrorTitle = STR_CANT_REFURBISH_RIDE;
        result->Cost = GetRefurbishPrice(ride);
    }

    return result;
}

GameActions::Result::Ptr RideDemolishAction::Execute() const
{
    auto ride = get_ride(_rideIndex);
    if (ride == nullptr)
    {
        log_warning("Invalid game command for ride %u", uint32_t(_rideIndex));
        return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DEMOLISH_RIDE, STR_NONE);
    }

    switch (_modifyType)
    {
        case RIDE_MODIFY_DEMOLISH:
            return DemolishRide(ride);
        case RIDE_MODIFY_RENEW:
            return RefurbishRide(ride);
    }

    return std::make_unique<GameActions::Result>(GameActions::Status::InvalidParameters, STR_CANT_DO_THIS, STR_NONE);
}

GameActions::Result::Ptr RideDemolishAction::DemolishRide(Ride* ride) const
{
    money32 refundPrice = DemolishTracks();

    ride_clear_for_construction(ride);
    ride_remove_peeps(ride);
    ride->StopGuestsQueuing();

    ride->ValidateStations();
    ride_clear_leftover_entrances(ride);

    const auto rideId = ride->id;
    News::DisableNewsItems(News::ItemType::Ride, EnumValue(rideId));

    UnlinkAllBannersForRide(ride->id);

    RideUse::GetHistory().RemoveValue(ride->id);
    for (auto peep : EntityList<Guest>())
    {
        peep->RemoveRideFromMemory(ride->id);
    }

    MarketingCancelCampaignsForRide(_rideIndex);

    auto res = std::make_unique<GameActions::Result>();
    res->Expenditure = ExpenditureType::RideConstruction;
    res->Cost = refundPrice;

    if (!ride->overall_view.IsNull())
    {
        auto xy = ride->overall_view.ToTileCentre();
        res->Position = { xy, tile_element_height(xy) };
    }

    ride->Delete();
    gParkValue = GetContext()->GetGameState()->GetPark().CalculateParkValue();

    // Close windows related to the demolished ride
    window_close_by_number(WC_RIDE_CONSTRUCTION, EnumValue(rideId));
    window_close_by_number(WC_RIDE, EnumValue(rideId));
    window_close_by_number(WC_DEMOLISH_RIDE_PROMPT, EnumValue(rideId));
    window_close_by_class(WC_NEW_CAMPAIGN);

    // Refresh windows that display the ride name
    auto windowManager = OpenRCT2::GetContext()->GetUiContext()->GetWindowManager();
    windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_CAMPAIGN_RIDE_LIST));
    windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_RIDE_LIST));
    windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_GUEST_LIST));

    scrolling_text_invalidate();
    gfx_invalidate_screen();

    return res;
}

money32 RideDemolishAction::MazeRemoveTrack(const CoordsXYZD& coords) const
{
    auto setMazeTrack = MazeSetTrackAction(coords, false, _rideIndex, GC_SET_MAZE_TRACK_FILL);
    setMazeTrack.SetFlags(GetFlags());

    auto execRes = GameActions::ExecuteNested(&setMazeTrack);
    if (execRes->Error == GameActions::Status::Ok)
    {
        return execRes->Cost;
    }

    return MONEY32_UNDEFINED;
}

money32 RideDemolishAction::DemolishTracks() const
{
    money32 refundPrice = 0;

    uint8_t oldpaused = gGamePaused;
    gGamePaused = 0;

    tile_element_iterator it;

    tile_element_iterator_begin(&it);
    while (tile_element_iterator_next(&it))
    {
        if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
            continue;

        if (it.element->AsTrack()->GetRideIndex() != static_cast<ride_id_t>(_rideIndex))
            continue;

        auto location = CoordsXYZD(TileCoordsXY(it.x, it.y).ToCoordsXY(), it.element->GetBaseZ(), it.element->GetDirection());
        auto type = it.element->AsTrack()->GetTrackType();

        if (type != TrackElemType::Maze)
        {
            auto trackRemoveAction = TrackRemoveAction(type, it.element->AsTrack()->GetSequenceIndex(), location);
            trackRemoveAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);

            auto removRes = GameActions::ExecuteNested(&trackRemoveAction);

            if (removRes->Error != GameActions::Status::Ok)
            {
                tile_element_remove(it.element);
            }
            else
            {
                refundPrice += removRes->Cost;
            }

            tile_element_iterator_restart_for_tile(&it);
            continue;
        }

        static constexpr const CoordsXY DirOffsets[] = {
            { 0, 0 },
            { 0, 16 },
            { 16, 16 },
            { 16, 0 },
        };

        for (Direction dir : ALL_DIRECTIONS)
        {
            const CoordsXYZ off = { DirOffsets[dir], 0 };
            money32 removePrice = MazeRemoveTrack({ location + off, dir });
            if (removePrice != MONEY32_UNDEFINED)
                refundPrice += removePrice;
            else
                break;
        }

        tile_element_iterator_restart_for_tile(&it);
    }

    gGamePaused = oldpaused;
    return refundPrice;
}

GameActions::Result::Ptr RideDemolishAction::RefurbishRide(Ride* ride) const
{
    auto res = std::make_unique<GameActions::Result>();
    res->Expenditure = ExpenditureType::RideConstruction;
    res->Cost = GetRefurbishPrice(ride);

    ride->Renew();

    ride->lifecycle_flags &= ~RIDE_LIFECYCLE_EVER_BEEN_OPENED;
    ride->last_crash_type = RIDE_CRASH_TYPE_NONE;

    ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_CUSTOMER;

    if (!ride->overall_view.IsNull())
    {
        auto location = ride->overall_view.ToTileCentre();
        res->Position = { location, tile_element_height(location) };
    }

    window_close_by_number(WC_DEMOLISH_RIDE_PROMPT, EnumValue(_rideIndex));

    return res;
}

money32 RideDemolishAction::GetRefurbishPrice(const Ride* ride) const
{
    return -GetRefundPrice(ride) / 2;
}

money32 RideDemolishAction::GetRefundPrice(const Ride* ride) const
{
    return ride_get_refund_price(ride);
}
