/*
 * 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 "CBot/CBotInstr/CBotParExpr.h"

#include "CBot/CBotInstr/CBotExpression.h"
#include "CBot/CBotInstr/CBotExprLitBool.h"
#include "CBot/CBotInstr/CBotExprLitChar.h"
#include "CBot/CBotInstr/CBotExprLitNan.h"
#include "CBot/CBotInstr/CBotExprLitNull.h"
#include "CBot/CBotInstr/CBotExprLitNum.h"
#include "CBot/CBotInstr/CBotExprLitString.h"
#include "CBot/CBotInstr/CBotExprUnaire.h"
#include "CBot/CBotInstr/CBotExprVar.h"
#include "CBot/CBotInstr/CBotInstrCall.h"
#include "CBot/CBotInstr/CBotNew.h"
#include "CBot/CBotInstr/CBotPostIncExpr.h"
#include "CBot/CBotInstr/CBotPreIncExpr.h"
#include "CBot/CBotInstr/CBotTwoOpExpr.h"

#include "CBot/CBotVar/CBotVar.h"

#include "CBot/CBotCStack.h"

#include <cmath>

namespace CBot
{

CBotInstr* CBotParExpr::Compile(CBotToken* &p, CBotCStack* pStack, bool bConstExpr)
{
    CBotCStack* pStk = pStack->TokenStack();

    pStk->SetStartError(p->GetStart());

    if (bConstExpr)
        return CBotParExpr::CompileConstExpr(p, pStack);

    // is it an expression in parentheses?
    if (IsOfType(p, ID_OPENPAR))
    {
        CBotInstr* inst = CBotExpression::Compile(p, pStk);

        if (nullptr != inst)
        {
            if (IsOfType(p, ID_CLOSEPAR))
            {
                return pStack->Return(inst, pStk);
            }
            pStk->SetError(CBotErrClosePar, p->GetStart());
        }
        delete inst;
        return pStack->Return(nullptr, pStk);
    }

    // is this a unary operation?
    CBotInstr* inst = CBotExprUnaire::Compile(p, pStk);
    if (inst != nullptr || !pStk->IsOk())
        return pStack->Return(inst, pStk);

    // is it sizeof operator ?
    inst = CBot::CompileSizeOf(p, pStk);
    if (inst != nullptr || !pStk->IsOk())
    {
        return pStack->Return(inst, pStk);
    }

    // is it a variable name?
    if (p->GetType() == TokenTypVar)
    {
        // this may be a method call without the "this." before
        inst =  CBotExprVar::CompileMethode(p, pStk);
        if (inst != nullptr) return pStack->Return(inst, pStk);


        // is it a procedure call?
        inst =  CBotInstrCall::Compile(p, pStk);
        if (inst != nullptr || !pStk->IsOk())
            return pStack->Return(inst, pStk);


        CBotToken* pvar = p;
        // no, it's an "ordinaty" variable
        inst =  CBotExprVar::Compile(p, pStk);

        CBotToken* pp = p;
        // post incremented or decremented?
        if (IsOfType(p, ID_INC, ID_DEC))
        {
            // recompile the variable for read-only
            delete inst;
            p = pvar;
            inst = CBotExprVar::Compile(p, pStk, true);
            if (pStk->GetType() >= CBotTypBoolean)
            {
                pStk->SetError(CBotErrBadType1, pp);
                delete inst;
                return pStack->Return(nullptr, pStk);
            }
            p = p->GetNext();

            CBotPostIncExpr* i = new CBotPostIncExpr();
            i->SetToken(pp);
            i->m_instr = inst;    // associated statement
            return pStack->Return(i, pStk);
        }
        return pStack->Return(inst, pStk);
    }

    // pre increpemted or pre decremented?
    CBotToken* pp = p;
    if (IsOfType(p, ID_INC, ID_DEC))
    {
        if (p->GetType() == TokenTypVar)
        {
            if (nullptr != (inst = CBotExprVar::Compile(p, pStk, true)))
            {
                if (pStk->GetType() < CBotTypBoolean) // a number ?
                {
                    CBotPreIncExpr* i = new CBotPreIncExpr();
                    i->SetToken(pp);
                    i->m_instr = inst;
                    return pStack->Return(i, pStk);
                }
                delete inst;
            }
        }
        pStk->SetError(CBotErrBadType1, pp);
        return pStack->Return(nullptr, pStk);
    }

    return CBotParExpr::CompileLitExpr(p, pStack);
}

////////////////////////////////////////////////////////////////////////////////
CBotInstr* CBotParExpr::CompileLitExpr(CBotToken* &p, CBotCStack* pStack)
{
    CBotCStack* pStk = pStack->TokenStack();

    CBotToken* pp = p;

    // is this a unary operation?
    CBotInstr* inst = CBotExprUnaire::Compile(p, pStk, true);
    if (inst != nullptr || !pStk->IsOk())
        return pStack->Return(inst, pStk);

    // is it a number or DefineNum?
    if (p->GetType() == TokenTypNum ||
        p->GetType() == TokenTypDef )
    {
        CBotInstr* inst = CBot::CompileExprLitNum(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is it sizeof operator ?
    inst = CBot::CompileSizeOf(p, pStk);
    if (inst != nullptr || !pStk->IsOk())
    {
        return pStack->Return(inst, pStk);
    }

    // is this a character?
    if (p->GetType() == TokenTypChar)
    {
        CBotInstr* inst = CBotExprLitChar::Compile(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is this a chaine?
    if (p->GetType() == TokenTypString)
    {
        CBotInstr* inst = CBotExprLitString::Compile(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is a "true" or "false"
    if (p->GetType() == ID_TRUE ||
        p->GetType() == ID_FALSE )
    {
        CBotInstr* inst = CBotExprLitBool::Compile(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is an object to be created with new
    if (p->GetType() == ID_NEW)
    {
        CBotInstr* inst = CBotNew::Compile(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is a null pointer
    if (IsOfType(p, ID_NULL))
    {
        CBotInstr* inst = new CBotExprLitNull();
        inst->SetToken(pp);
        CBotVar* var = CBotVar::Create("", CBotTypNullPointer);
        pStk->SetVar(var);
        return pStack->Return(inst, pStk);
    }

    // is a number nan
    if (IsOfType(p, ID_NAN))
    {
        CBotInstr* inst = new CBotExprLitNan();
        inst->SetToken(pp);
        CBotVar* var = CBotVar::Create("", CBotTypFloat);
        var->SetValFloat(nanf(""));
        pStk->SetVar(var);
        return pStack->Return(inst, pStk);
    }


    return pStack->Return(nullptr, pStk);
}

CBotInstr* CBotParExpr::CompileConstExpr(CBotToken* &p, CBotCStack* pStack)
{
    CBotCStack* pStk = pStack->TokenStack();

    // is it an expression in parentheses?
    if (IsOfType(p, ID_OPENPAR))
    {
        CBotInstr* inst = CBotTwoOpExpr::Compile(p, pStk, nullptr, true);

        if (nullptr != inst)
        {
            if (IsOfType(p, ID_CLOSEPAR))
            {
                return pStack->Return(inst, pStk);
            }
            pStk->SetError(CBotErrClosePar, p->GetStart());
        }
        delete inst;
        return pStack->Return(nullptr, pStk);
    }

    // is this a unary operation?
    CBotInstr* inst = CBotExprUnaire::Compile(p, pStk, true, true);
    if (inst != nullptr || !pStk->IsOk())
        return pStack->Return(inst, pStk);

    // is it a number or DefineNum?
    if (p->GetType() == TokenTypNum ||
        p->GetType() == TokenTypDef )
    {
        CBotInstr* inst = CBot::CompileExprLitNum(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is this a character?
    if (p->GetType() == TokenTypChar)
    {
        CBotInstr* inst = CBotExprLitChar::Compile(p, pStk);
        return pStack->Return(inst, pStk);
    }

    // is it sizeof operator ?
    inst = CBot::CompileSizeOf(p, pStk);
    if (inst != nullptr || !pStk->IsOk())
    {
        return pStack->Return(inst, pStk);
    }

    return pStack->Return(nullptr, pStk);
}

} // namespace CBot
