#include "pac_decl.h"

#include "pac_attr.h"
#include "pac_context.h"
#include "pac_dataptr.h"
#include "pac_embedded.h"
#include "pac_exception.h"
#include "pac_expr.h"
#include "pac_exttype.h"
#include "pac_id.h"
#include "pac_output.h"
#include "pac_param.h"
#include "pac_record.h"
#include "pac_type.h"
#include "pac_utils.h"

DeclList* Decl::decl_list_ = nullptr;
Decl::DeclMap Decl::decl_map_;

Decl::Decl(ID* id, DeclType decl_type) : id_(id), decl_type_(decl_type), attrlist_(nullptr) {
    decl_map_[id_] = this;
    if ( ! decl_list_ )
        decl_list_ = new DeclList();
    decl_list_->push_back(this);

    DEBUG_MSG("Finished Decl %s\n", id_->Name());

    analyzer_context_ = nullptr;
}

Decl::~Decl() {
    delete id_;
    delete_list(AttrList, attrlist_);
}

void Decl::AddAttrs(AttrList* attrs) {
    if ( ! attrs )
        return;
    if ( ! attrlist_ )
        attrlist_ = new AttrList();
    foreach (i, AttrList, attrs) {
        attrlist_->push_back(*i);
        ProcessAttr(*i);
    }
}

void Decl::ProcessAttr(Attr* attr) { throw Exception(attr, "unhandled attribute"); }

void Decl::SetAnalyzerContext() {
    analyzer_context_ = AnalyzerContextDecl::current_analyzer_context();
    if ( ! analyzer_context_ ) {
        throw Exception(this, "analyzer context not defined");
    }
}

void Decl::ProcessDecls(Output* out_h, Output* out_cc) {
    if ( ! decl_list_ )
        return;

    foreach (i, DeclList, decl_list_) {
        Decl* decl = *i;
        current_decl_id = decl->id();
        decl->Prepare();
    }

    foreach (i, DeclList, decl_list_) {
        Decl* decl = *i;
        current_decl_id = decl->id();
        decl->GenExternDeclaration(out_h);
    }

    out_h->println("namespace binpac {\n");
    out_cc->println("namespace binpac {\n");

    AnalyzerContextDecl* analyzer_context = AnalyzerContextDecl::current_analyzer_context();

    foreach (i, DeclList, decl_list_) {
        Decl* decl = *i;
        current_decl_id = decl->id();
        decl->GenForwardDeclaration(out_h);
    }

    if ( analyzer_context )
        analyzer_context->GenNamespaceEnd(out_h);

    out_h->println("");

    foreach (i, DeclList, decl_list_) {
        Decl* decl = *i;
        current_decl_id = decl->id();
        decl->GenCode(out_h, out_cc);
    }

    if ( analyzer_context ) {
        analyzer_context->GenNamespaceEnd(out_h);
        analyzer_context->GenNamespaceEnd(out_cc);
    }

    out_h->println("}  // namespace binpac");
    out_cc->println("}  // namespace binpac");
}

Decl* Decl::LookUpDecl(const ID* id) {
    DeclMap::iterator it = decl_map_.find(id);
    if ( it == decl_map_.end() )
        return nullptr;
    return it->second;
}

int HelperDecl::helper_id_seq = 0;

HelperDecl::HelperDecl(HelperType helper_type, ID* context_id, EmbeddedCode* code)
    : Decl(new ID(strfmt("helper_%d", ++helper_id_seq)), HELPER),
      helper_type_(helper_type),
      context_id_(context_id),
      code_(code) {}

HelperDecl::~HelperDecl() {
    delete context_id_;
    delete code_;
}

void HelperDecl::Prepare() {
    // Do nothing
}

void HelperDecl::GenExternDeclaration(Output* out_h) {
    if ( helper_type_ == EXTERN )
        code_->GenCode(out_h, global_env());
}

void HelperDecl::GenCode(Output* out_h, Output* out_cc) {
    Env* env = global_env();

#if 0
	if ( context_id_ )
		{
		Decl *decl = Decl::LookUpDecl(context_id_);
		if ( ! decl )
			{
			throw Exception(context_id_,
			                fmt("cannot find declaration for %s",
			                    context_id_->Name()));
			}
		env = decl->env();
		if ( ! env )
			{
			throw Exception(context_id_,
			                fmt("not a type or analyzer: %s",
			                    context_id_->Name()));
			}
		}
#endif

    if ( helper_type_ == HEADER )
        code_->GenCode(out_h, env);
    else if ( helper_type_ == CODE )
        code_->GenCode(out_cc, env);
    else if ( helper_type_ == EXTERN )
        ; // do nothing
    else
        ASSERT(0);
}
