/*
 * This file is part of the Colobot: Gold Edition source code
 * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam
 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://gnu.org/licenses
 */

#include "level/scene_conditions.h"

#include "level/parser/parserline.h"

#include "math/geometry.h"

#include "object/object.h"
#include "object/object_manager.h"

#include "object/interface/power_container_object.h"
#include "object/interface/slotted_object.h"
#include "object/interface/transportable_object.h"

#include <limits>


void CObjectCondition::Read(CLevelParserLine* line)
{
    this->pos      = line->GetParam("pos")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit;
    this->dist     = line->GetParam("dist")->AsFloat(std::numeric_limits<float>::infinity())*g_unit;
    this->type     = line->GetParam("type")->AsObjectType(OBJECT_NULL);
    this->powermin = line->GetParam("powermin")->AsFloat(-1);
    this->powermax = line->GetParam("powermax")->AsFloat(100);
    this->tool     = line->GetParam("tool")->AsToolType(ToolType::Other);
    this->drive    = line->GetParam("drive")->AsDriveType(DriveType::Other);
    this->countTransported = line->GetParam("countTransported")->AsBool(true);
    this->team     = line->GetParam("team")->AsInt(0);
}

bool CObjectCondition::CheckForObject(CObject* obj)
{
    if (!this->countTransported)
    {
        if (IsObjectBeingTransported(obj)) return false;
    }

    ObjectType type = obj->GetType();

    ToolType tool = GetToolFromObject(type);
    DriveType drive = GetDriveFromObject(type);
    if (this->tool != ToolType::Other &&
        tool != this->tool)
        return false;

    if (this->drive != DriveType::Other &&
        drive != this->drive)
        return false;

    if (this->tool == ToolType::Other &&
        this->drive == DriveType::Other &&
        type != this->type &&
        this->type != OBJECT_NULL)
        return false;

    if ((this->team > 0 && obj->GetTeam() != this->team) ||
        (this->team < 0 && (obj->GetTeam() == -(this->team) || obj->GetTeam() == 0)))
        return false;

    float energyLevel = -1;
    CPowerContainerObject* power = nullptr;
    if (obj->Implements(ObjectInterfaceType::PowerContainer))
    {
        power = dynamic_cast<CPowerContainerObject*>(obj);
    }
    else
    {
        power = GetObjectPowerCell(obj);
    }

    if (power != nullptr)
    {
        energyLevel = power->GetEnergy();
        if (power->GetCapacity() > 1.0f) energyLevel *= 10; // TODO: Who designed it like that ?!?!
    }
    if (energyLevel < this->powermin || energyLevel > this->powermax) return false;

    Math::Vector oPos;
    if (IsObjectBeingTransported(obj))
        oPos = dynamic_cast<CTransportableObject&>(*obj).GetTransporter()->GetPosition();
    else
        oPos = obj->GetPosition();
    oPos.y = 0.0f;

    Math::Vector bPos = this->pos;
    bPos.y = 0.0f;

    if (Math::DistanceProjected(oPos, bPos) <= this->dist)
        return true;

    return false;
}

int CObjectCondition::CountObjects()
{
    int nb = 0;
    for (CObject* obj : CObjectManager::GetInstancePointer()->GetAllObjects())
    {
        if (!obj->GetActive()) continue;
        if (!CheckForObject(obj)) continue;
        nb ++;
    }
    return nb;
}

void CSceneCondition::Read(CLevelParserLine* line)
{
    CObjectCondition::Read(line);

    // Scene conditions STILL use a different default value
    // See issue #759
    this->dist     = line->GetParam("dist")->AsFloat(8.0f)*g_unit;

    this->min      = line->GetParam("min")->AsInt(1);
    this->max      = line->GetParam("max")->AsInt(9999);
}

bool CSceneCondition::Check()
{
    int nb = CountObjects();
    return nb >= this->min && nb <= this->max;
}

void CSceneEndCondition::Read(CLevelParserLine* line)
{
    CSceneCondition::Read(line);
    this->winTeam  = line->GetParam("winTeam")->AsInt(0);
    this->lost     = line->GetParam("lost")->AsInt(-1);
    this->immediat = line->GetParam("immediat")->AsBool(false);
}

bool CSceneEndCondition::CheckLost()
{
    int nb = CountObjects();
    return nb <= this->lost;
}

Error CSceneEndCondition::GetMissionResult()
{
    if (CheckLost())
    {
        if (this->type == OBJECT_HUMAN)
            return INFO_LOSTq;
        else
            return INFO_LOST;
    }

    if (!Check())
    {
        return ERR_MISSION_NOTERM;
    }

    return ERR_OK;
}


void CAudioChangeCondition::Read(CLevelParserLine* line)
{
    CSceneCondition::Read(line);
    this->music    = line->GetParam("filename")->AsPath("music");
    this->repeat   = line->GetParam("repeat")->AsBool(true);
}
