#include "pac_conn.h"

#include "pac_analyzer.h"
#include "pac_dataunit.h"
#include "pac_embedded.h"
#include "pac_exception.h"
#include "pac_expr.h"
#include "pac_flow.h"
#include "pac_output.h"
#include "pac_paramtype.h"
#include "pac_type.h"

ConnDecl::ConnDecl(ID* conn_id, ParamList* params, AnalyzerElementList* elemlist)
    : AnalyzerDecl(conn_id, CONN, params) {
    flows_[0] = flows_[1] = nullptr;
    AddElements(elemlist);
    data_type_ = new ParameterizedType(conn_id->clone(), nullptr);
}

ConnDecl::~ConnDecl() {
    delete flows_[0];
    delete flows_[1];
    delete data_type_;
}

void ConnDecl::AddBaseClass(vector<string>* base_classes) const {
    base_classes->push_back("binpac::ConnectionAnalyzer");
}

void ConnDecl::ProcessFlowElement(AnalyzerFlow* flow_elem) {
    int flow_index;

    if ( flow_elem->dir() == AnalyzerFlow::UP )
        flow_index = 0;
    else
        flow_index = 1;

    if ( flows_[flow_index] ) {
        throw Exception(flow_elem, strfmt("%sflow already defined", flow_index == 0 ? "up" : "down"));
    }

    flows_[flow_index] = flow_elem;
    type_->AddField(flow_elem->flow_field());
}

void ConnDecl::ProcessDataUnitElement(AnalyzerDataUnit* dataunit_elem) {
    throw Exception(dataunit_elem, "dataunit should be defined in only a flow declaration");
}

void ConnDecl::Prepare() {
    AnalyzerDecl::Prepare();

    flows_[0]->flow_decl()->set_conn_decl(this);
    flows_[1]->flow_decl()->set_conn_decl(this);
}

void ConnDecl::GenPubDecls(Output* out_h, Output* out_cc) { AnalyzerDecl::GenPubDecls(out_h, out_cc); }

void ConnDecl::GenPrivDecls(Output* out_h, Output* out_cc) { AnalyzerDecl::GenPrivDecls(out_h, out_cc); }

void ConnDecl::GenEOFFunc(Output* out_h, Output* out_cc) {
    string proto = strfmt("%s(bool is_orig)", kFlowEOF);

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

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

    out_cc->println("if ( is_orig )");
    out_cc->inc_indent();
    out_cc->println("%s->%s();", env_->LValue(upflow_id), kFlowEOF);
    out_cc->dec_indent();
    out_cc->println("else");
    out_cc->inc_indent();
    out_cc->println("%s->%s();", env_->LValue(downflow_id), kFlowEOF);

    foreach (i, AnalyzerHelperList, eof_helpers_) {
        (*i)->GenCode(nullptr, out_cc, this);
    }

    out_cc->dec_indent();

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

void ConnDecl::GenGapFunc(Output* out_h, Output* out_cc) {
    string proto = strfmt("%s(bool is_orig, int gap_length)", kFlowGap);

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

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

    out_cc->println("if ( is_orig )");
    out_cc->inc_indent();
    out_cc->println("%s->%s(gap_length);", env_->LValue(upflow_id), kFlowGap);
    out_cc->dec_indent();
    out_cc->println("else");
    out_cc->inc_indent();
    out_cc->println("%s->%s(gap_length);", env_->LValue(downflow_id), kFlowGap);
    out_cc->dec_indent();

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

void ConnDecl::GenProcessFunc(Output* out_h, Output* out_cc) {
    string proto = strfmt("%s(bool is_orig, const_byteptr begin, const_byteptr end)", kNewData);

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

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

    out_cc->println("if ( is_orig )");
    out_cc->inc_indent();
    out_cc->println("%s->%s(begin, end);", env_->LValue(upflow_id), kNewData);
    out_cc->dec_indent();
    out_cc->println("else");
    out_cc->inc_indent();
    out_cc->println("%s->%s(begin, end);", env_->LValue(downflow_id), kNewData);
    out_cc->dec_indent();

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