//#####################################################################
// Copyright 2012.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYREGHT.txt.
//#####################################################################
#ifndef __PROGRAM_DEFINITION__
#define __PROGRAM_DEFINITION__
#include <Core/Symbolics/INSTRUCTION.h>
#include <Core/Symbolics/PROGRAM.h>
#include <Core/Symbolics/PROGRAM_COMPILER.h>
#include <Core/Symbolics/PROGRAM_CONTEXT.h>
#include <cmath>

namespace PhysBAM{
using std::pow;
//#####################################################################
// Function Execute
//#####################################################################
template<class T> T
Evaluate_Op(OPERATION o,T in0,T in1,T in2)
{
    switch(o.op)
    {
        case op_copy: return in0;
        case op_add: return in0+in1;
        case op_sub: return in0-in1;
        case op_mul: return in0*in1;
        case op_div: return in0/in1;
        case op_neg: return -in0;
        case op_inv: return (T)1/in0;
        case op_sqrt: return sqrt(in0);
        case op_exp: return exp(in0);
        case op_ln: return log(in0);
        case op_pow: return pow(in0,in1);
        case op_sin: return sin(in0);
        case op_cos: return cos(in0);
        case op_asin: return asin(in0);
        case op_acos: return acos(in0);
        case op_atan: return atan(in0);
        case op_atan2: return atan2(in0,in1);
        case op_floor: return floor(in0);
        case op_ceil: return ceil(in0);
        case op_rint: return rint(in0);
        case op_min: return min(in0,in1);
        case op_max: return max(in0,in1);
        case op_abs: return abs(in0);
        case op_if: return !!in0?in1:in2;
        case op_lt: return in0<in1;
        case op_le: return in0<=in1;
        case op_gt: return in0>in1;
        case op_ge: return in0>=in1;
        case op_eq: return in0==in1;
        case op_ne: return in0!=in1;
        case op_not: return !in0;
        case op_or: return in0||in1;
        case op_and: return in0&&in1;
        default: PHYSBAM_FATAL_ERROR("Cannot evaluate this instruction");
    }
}
//#####################################################################
// Function Execute
//#####################################################################
template<class T> void PROGRAM<T>::
Execute_Op(ARRAY<T>& reg,int& ip) const
{
    const RAW_INSTRUCTION& o=code(ip);
    if(o.type.Has_Dest()){
        T in0=0,in1=0,in2=0;
        if(o.type.Has_Src0()) in0=reg(o.src0);
        if(o.type.Has_Src1()) in1=reg(o.src1);
        if(o.type.Has_Src2()) in2=reg(o.src2);
        reg(o.dest)=Evaluate_Op(o.type,in0,in1,in2);
        return;}

    switch(o.type.op){
        case op_nop: break;
        case op_brz: if(!reg(o.src0)) ip=o.dest-1;break;
        case op_brnz: if(!!reg(o.src0)) ip=o.dest-1;break;
        case op_jmp: ip=o.dest-1;break;
        default: PHYSBAM_FATAL_ERROR("Missing instruction");}
}
//#####################################################################
// Function Execute
//#####################################################################
template<class T> void PROGRAM<T>::
Execute(ARRAY<T>& reg) const
{
    for(int ip=0;ip<code.m;ip++)
        Execute_Op(reg,ip);
}
//#####################################################################
// Function Execute
//#####################################################################
template<class T> void PROGRAM<T>::
Execute(PROGRAM_CONTEXT<T>& context) const
{
    Execute(context.reg);
}
//#####################################################################
// Function Print
//#####################################################################
template<class T> void PROGRAM<T>::
Print() const
{
    PhysBAM::Print(code);
}
//#####################################################################
// Function Simple_Compile
//#####################################################################
template<class T> void
Simple_Compile(PROGRAM<T>& prog,const std::string& code,
    const ARRAY<std::string,MEM_IN>& var_in,const ARRAY<std::string,MEM_OUT>& var_out)
{
    PROGRAM_COMPILER<T> pc;
    for(auto v:var_in) pc.Add_Input(v);
    for(auto v:var_out) pc.Add_Output(v);
    pc.Parse(code.c_str());
    pc.Optimize();
    pc.Finalize(prog);
}
template void Simple_Compile<double>(PROGRAM<double>&,std::string const&,
    ARRAY<std::string,MEM_IN> const&,ARRAY<std::string,MEM_OUT> const&);
template void Simple_Compile<float>(PROGRAM<float>&,std::string const&,
    ARRAY<std::string,MEM_IN> const&,ARRAY<std::string,MEM_OUT> const&);
}
#endif
