#include "pac_action.h"

#include "pac_embedded.h"
#include "pac_exception.h"
#include "pac_id.h"
#include "pac_output.h"
#include "pac_type.h"
#include "pac_typedecl.h"
#include "pac_utils.h"

AnalyzerAction::AnalyzerAction(ID* action_id, When when, ActionParam* param, EmbeddedCode* code)
    : AnalyzerElement(ACTION), action_id_(action_id), when_(when), param_(param), code_(code), analyzer_(nullptr) {}

AnalyzerAction::~AnalyzerAction() {
    delete action_id_;
    delete param_;
    delete code_;
}

string AnalyzerAction::action_function() const { return strfmt("Action_%s", action_id_->Name()); }

void AnalyzerAction::InstallHook(AnalyzerDecl* analyzer) {
    ASSERT(0);
    analyzer_ = analyzer;
    // param_->MainDataType()->InstallAction(this);
}

void AnalyzerAction::GenCode(Output* out_h, Output* out_cc, AnalyzerDecl* decl) {
    Env action_func_env(decl->env(), this);
    action_func_env.AddID(param_->id(), TEMP_VAR, param_->DataType());
    action_func_env.SetEvaluated(param_->id());

    string action_func_proto = strfmt("%s(%s)", action_function().c_str(), ParamDecls(&action_func_env).c_str());

    out_h->println("void %s;", action_func_proto.c_str());

    out_cc->println("void %s::%s {", decl->class_name().c_str(), action_func_proto.c_str());
    out_cc->inc_indent();

    code_->GenCode(out_cc, &action_func_env);

    out_cc->println("");
    out_cc->dec_indent();
    out_cc->println("}");
    out_cc->println("");
}

string AnalyzerAction::ParamDecls(Env* env) const { return param_->DeclStr(env); }

Type* ActionParam::MainDataType() const {
    // Note: this is not equal to DataType()
    Type* main_type = TypeDecl::LookUpType(type()->type_id());

    if ( ! main_type ) {
        throw Exception(type()->type_id(), "type not defined");
    }

    return main_type;
}

Type* ActionParam::DataType() const {
    Type* main_type = MainDataType();

    if ( ! type()->field_id() ) {
        return main_type;
    }
    else {
        Type* member_type = main_type->MemberDataType(type()->field_id());
        if ( ! member_type ) {
            throw Exception(type()->field_id(), strfmt("cannot find member type for `%s.%s'", type()->type_id()->Name(),
                                                       type()->field_id()->Name()));
        }
        return member_type;
    }
}

string ActionParam::DeclStr(Env* env) const {
    return strfmt("%s %s", DataType()->DataTypeStr().c_str(), env->LValue(id()));
}
