// See the file "COPYING" in the main distribution directory for copyright.

#include "pac_record.h"

#include "pac_dataptr.h"
#include "pac_exception.h"
#include "pac_expr.h"
#include "pac_exttype.h"
#include "pac_field.h"
#include "pac_output.h"
#include "pac_type.h"
#include "pac_typedecl.h"
#include "pac_utils.h"

RecordType::RecordType(RecordFieldList* record_fields) : Type(RECORD) {
    // Here we assume that the type is a standalone type.
    value_var_ = nullptr;

    // Put all fields in fields_
    foreach (i, RecordFieldList, record_fields)
        AddField(*i);

    // Put RecordField's in record_fields_
    record_fields_ = record_fields;

    parsing_dataptr_var_field_ = nullptr;
}

RecordType::~RecordType() {
    // Do not delete_list(record_fields_)
    // because the fields are also in fields_.
    delete record_fields_;
    delete parsing_dataptr_var_field_;
}

const ID* RecordType::parsing_dataptr_var() const {
    return parsing_dataptr_var_field_ ? parsing_dataptr_var_field_->id() : nullptr;
}

bool RecordType::DefineValueVar() const { return false; }

string RecordType::DataTypeStr() const {
    ASSERT(type_decl());
    return strfmt("%s*", type_decl()->class_name().c_str());
}

void RecordType::Prepare(Env* env, int flags) {
    ASSERT(flags & TO_BE_PARSED);

    RecordField* prev = nullptr;
    int offset = 0;
    int seq = 0;
    foreach (i, RecordFieldList, record_fields_) {
        RecordField* f = *i;
        f->set_record_type(this);
        f->set_prev(prev);
        if ( prev )
            prev->set_next(f);
        prev = f;
        if ( offset >= 0 ) {
            f->set_static_offset(offset);
            int w = f->StaticSize(env, offset);
            if ( w < 0 )
                offset = -1;
            else
                offset += w;
        }
        ++seq;
        f->set_parsing_state_seq(seq);
    }

    if ( incremental_parsing() ) {
#if 0
		ASSERT(! parsing_state_var_field_);
		ID *parsing_state_var_id = new ID("parsing_state");
		parsing_state_var_field_ = new PrivVarField(
			parsing_state_var_id, extern_type_int->Clone());
		AddField(parsing_state_var_field_);

		ID *parsing_dataptr_var_id = new ID("parsing_dataptr");
		parsing_dataptr_var_field_ = new TempVarField(
			parsing_dataptr_var_id, extern_type_const_byteptr->Clone());
		parsing_dataptr_var_field_->Prepare(env);
#endif
    }

    Type::Prepare(env, flags);
}

void RecordType::GenPubDecls(Output* out_h, Env* env) { Type::GenPubDecls(out_h, env); }

void RecordType::GenPrivDecls(Output* out_h, Env* env) { Type::GenPrivDecls(out_h, env); }

void RecordType::GenInitCode(Output* out_cc, Env* env) { Type::GenInitCode(out_cc, env); }

void RecordType::GenCleanUpCode(Output* out_cc, Env* env) { Type::GenCleanUpCode(out_cc, env); }

void RecordType::DoGenParseCode(Output* out_cc, Env* env, const DataPtr& data, int flags) {
    if ( ! incremental_input() && StaticSize(env) >= 0 )
        GenBoundaryCheck(out_cc, env, data);

    if ( incremental_parsing() ) {
        out_cc->println("// NOLINTBEGIN(bugprone-branch-clone)");
        out_cc->println("switch ( %s ) {", env->LValue(parsing_state_id));

        out_cc->println("case 0:");
        out_cc->inc_indent();
        foreach (i, RecordFieldList, record_fields_) {
            RecordField* f = *i;
            f->GenParseCode(out_cc, env);
            out_cc->println("");
        }
        out_cc->println("");
        out_cc->println("%s = true;", env->LValue(parsing_complete_var()));
        out_cc->dec_indent();
        out_cc->println("}");
        out_cc->println("// NOLINTEND(bugprone-branch-clone)");
    }
    else {
        ASSERT(data.id() == begin_of_data && data.offset() == 0);
        foreach (i, RecordFieldList, record_fields_) {
            RecordField* f = *i;
            f->GenParseCode(out_cc, env);
            out_cc->println("");
        }
        if ( incremental_input() ) {
            ASSERT(parsing_complete_var());
            out_cc->println("%s = true;", env->LValue(parsing_complete_var()));
        }
    }

    if ( ! incremental_input() && AddSizeVar(out_cc, env) ) {
        const DataPtr& end_of_record_dataptr = record_fields_->back()->getFieldEnd(out_cc, env);

        out_cc->println("%s = %s - %s;", env->LValue(size_var()), end_of_record_dataptr.ptr_expr(),
                        env->RValue(begin_of_data));
        env->SetEvaluated(size_var());
    }

    if ( ! boundary_checked() ) {
        RecordField* last_field = record_fields_->back();
        if ( ! last_field->BoundaryChecked() )
            GenBoundaryCheck(out_cc, env, data);
    }
}

void RecordType::GenDynamicSize(Output* out_cc, Env* env, const DataPtr& data) { GenParseCode(out_cc, env, data, 0); }

int RecordType::StaticSize(Env* env) const {
    int tot_w = 0;
    foreach (i, RecordFieldList, record_fields_) {
        RecordField* f = *i;
        int w = f->StaticSize(env, tot_w);
        if ( w < 0 )
            return -1;
        tot_w += w;
    }
    return tot_w;
}

void RecordType::SetBoundaryChecked() {
    Type::SetBoundaryChecked();

    if ( StaticSize(env()) < 0 || attr_length_expr_ )
        // Don't assume sufficient bounds checking has been done on fields
        // if the record is of variable size or if its size is set from &length
        // (whose value is not necessarily trustworthy).
        return;

    foreach (i, RecordFieldList, record_fields_) {
        RecordField* f = *i;
        f->SetBoundaryChecked();
    }
}

void RecordType::DoMarkIncrementalInput() {
    foreach (i, RecordFieldList, record_fields_) {
        RecordField* f = *i;
        f->type()->MarkIncrementalInput();
    }
}

bool RecordType::DoTraverse(DataDepVisitor* visitor) { return Type::DoTraverse(visitor); }

bool RecordType::ByteOrderSensitive() const {
    foreach (i, RecordFieldList, record_fields_) {
        RecordField* f = *i;
        if ( f->RequiresByteOrder() )
            return true;
    }
    return false;
}

RecordField::RecordField(FieldType tof, ID* id, Type* type)
    : Field(tof, TYPE_TO_BE_PARSED | CLASS_MEMBER | PUBLIC_READABLE, id, type) {
    begin_of_field_dataptr = nullptr;
    end_of_field_dataptr = nullptr;
    field_size_expr = nullptr;
    field_offset_expr = nullptr;
    end_of_field_dataptr_var = nullptr;
    record_type_ = nullptr;
    prev_ = nullptr;
    next_ = nullptr;
    static_offset_ = -1;
    parsing_state_seq_ = 0;
    boundary_checked_ = false;
}

RecordField::~RecordField() {
    delete begin_of_field_dataptr;
    delete end_of_field_dataptr;
    delete[] field_size_expr;
    delete[] field_offset_expr;
    delete end_of_field_dataptr_var;
}

const DataPtr& RecordField::getFieldBegin(Output* out_cc, Env* env) {
    if ( prev() )
        return prev()->getFieldEnd(out_cc, env);
    else {
        // The first field
        if ( ! begin_of_field_dataptr ) {
            begin_of_field_dataptr = new DataPtr(env, begin_of_data, 0);
        }
        return *begin_of_field_dataptr;
    }
}

const DataPtr& RecordField::getFieldEnd(Output* out_cc, Env* env) {
    if ( end_of_field_dataptr )
        return *end_of_field_dataptr;

    const DataPtr& begin_ptr = getFieldBegin(out_cc, env);

    if ( record_type()->incremental_parsing() ) {
        ASSERT(0);
        if ( ! end_of_field_dataptr ) {
            const ID* dataptr_var = record_type()->parsing_dataptr_var();
            ASSERT(dataptr_var);

            end_of_field_dataptr = new DataPtr(env, dataptr_var, 0);
        }
    }
    else {
        int field_offset;
        if ( begin_ptr.id() == begin_of_data )
            field_offset = begin_ptr.offset();
        else
            field_offset = -1; // unknown

        int field_size = StaticSize(env, field_offset);
        if ( field_size >= 0 ) // can be statically determinted
        {
            end_of_field_dataptr = new DataPtr(env, begin_ptr.id(), begin_ptr.offset() + field_size);
        }
        else {
            // If not, we add a variable for the offset after the field
            end_of_field_dataptr_var = new ID(strfmt("dataptr_after_%s", id()->Name()));
            env->AddID(end_of_field_dataptr_var, TEMP_VAR, extern_type_const_byteptr);

            GenFieldEnd(out_cc, env, begin_ptr);

            end_of_field_dataptr = new DataPtr(env, end_of_field_dataptr_var, 0);
        }
    }

    return *end_of_field_dataptr;
}

const char* RecordField::FieldSize(Output* out_cc, Env* env) {
    if ( field_size_expr )
        return field_size_expr;

    const DataPtr& begin = getFieldBegin(out_cc, env);
    const DataPtr& end = getFieldEnd(out_cc, env);
    if ( begin.id() == end.id() )
        field_size_expr = nfmt("%d", end.offset() - begin.offset());
    else
        field_size_expr = nfmt("(%s - %s)", end.ptr_expr(), begin.ptr_expr());
    return field_size_expr;
}

const char* RecordField::FieldOffset(Output* out_cc, Env* env) {
    if ( field_offset_expr )
        return field_offset_expr;

    const DataPtr& begin = getFieldBegin(out_cc, env);
    if ( begin.id() == begin_of_data )
        field_offset_expr = nfmt("%d", begin.offset());
    else
        field_offset_expr = nfmt("(%s - %s)", begin.ptr_expr(), env->RValue(begin_of_data));
    return field_offset_expr;
}

// The reasoning behind AttemptBoundaryCheck is: "If my next field
// can check its boundary, then I don't have to check mine, and it
// will save me a boundary-check."
bool RecordField::AttemptBoundaryCheck(Output* out_cc, Env* env) {
    if ( boundary_checked_ )
        return true;

    // If I do not even know my size till I parse the data, my
    // next field won't be able to check its boundary now.

    const DataPtr& begin = getFieldBegin(out_cc, env);
    if ( StaticSize(env, begin.AbsOffset(begin_of_data)) < 0 )
        return false;

    // Now we ask the next field to check its boundary.
    if ( next() && next()->AttemptBoundaryCheck(out_cc, env) ) {
        // If it works, we are all set
        SetBoundaryChecked();
        return true;
    }
    else
        // If it fails, then I can still try to do it by myself
        return GenBoundaryCheck(out_cc, env);
}

RecordDataField::RecordDataField(ID* id, Type* type) : RecordField(RECORD_FIELD, id, type) { ASSERT(type_); }

RecordDataField::~RecordDataField() {}

void RecordDataField::Prepare(Env* env) {
    Field::Prepare(env);
    env->SetEvalMethod(id_, this);
    env->SetField(id_, this);
}

void RecordDataField::GenParseCode(Output* out_cc, Env* env) {
    if ( env->Evaluated(id()) )
        return;

    // Always evaluate record fields in order if parsing
    // is incremental.
    if ( record_type()->incremental_parsing() && prev() )
        prev()->GenParseCode(out_cc, env);

    DataPtr data(env, nullptr, 0);
    if ( ! record_type()->incremental_parsing() ) {
        data = getFieldBegin(out_cc, env);

        Expr* len_expr = record_type()->attr_length_expr();
        int len;

        if ( ! record_type()->buffer_input() || (len_expr && len_expr->ConstFold(env, &len)) )
            AttemptBoundaryCheck(out_cc, env);
    }

    out_cc->println("// Parse \"%s\"", id_->Name());
#if 0
	out_cc->println("DEBUG_MSG(\"%%.6f Parse %s\\n\", network_time());",
		id_->Name());
#endif
    type_->GenPreParsing(out_cc, env);
    if ( type_->incremental_input() ) {
        // The enclosing record type must be incrementally parsed
        out_cc->println("%s = %d;", env->LValue(parsing_state_id), parsing_state_seq());
        out_cc->println("/* fall through */");
        out_cc->dec_indent();
        out_cc->println("case %d:", parsing_state_seq());
        out_cc->println("{");
        out_cc->inc_indent();
    }

    type_->GenParseCode(out_cc, env, data, 0);

    if ( record_type()->incremental_parsing() ) {
        ASSERT(type_->incremental_input());

        out_cc->println("if ( ! (%s) )", type_->parsing_complete(env).c_str());
        out_cc->inc_indent();
        out_cc->println("goto %s;", kNeedMoreData);
        out_cc->dec_indent();
    }

    if ( record_type()->incremental_parsing() ) {
#if 0
		const ID *dataptr_var =
			record_type()->parsing_dataptr_var();
		ASSERT(dataptr_var);
		out_cc->println("%s += (%s);",
			env->LValue(dataptr_var),
			type_->DataSize(out_cc, env, data).c_str());
#endif
        out_cc->println("}");
    }

    SetBoundaryChecked();
}

void RecordDataField::GenEval(Output* out_cc, Env* env) { GenParseCode(out_cc, env); }

void RecordDataField::GenFieldEnd(Output* out_cc, Env* env, const DataPtr& field_begin) {
    out_cc->println("const_byteptr const %s = %s + (%s);", env->LValue(end_of_field_dataptr_var),
                    field_begin.ptr_expr(), type_->DataSize(out_cc, env, field_begin).c_str());
    env->SetEvaluated(end_of_field_dataptr_var);

    out_cc->println("BINPAC_ASSERT(%s <= %s);", env->RValue(end_of_field_dataptr_var), env->RValue(end_of_data));
}

void RecordDataField::SetBoundaryChecked() {
    RecordField::SetBoundaryChecked();
    type_->SetBoundaryChecked();
}

bool RecordDataField::GenBoundaryCheck(Output* out_cc, Env* env) {
    if ( boundary_checked_ )
        return true;

    type_->GenBoundaryCheck(out_cc, env, getFieldBegin(out_cc, env));

    SetBoundaryChecked();
    return true;
}

bool RecordDataField::DoTraverse(DataDepVisitor* visitor) { return Field::DoTraverse(visitor); }

bool RecordDataField::RequiresAnalyzerContext() const {
    return Field::RequiresAnalyzerContext() || type()->RequiresAnalyzerContext();
}

RecordPaddingField::RecordPaddingField(ID* id, PaddingType ptype, Expr* expr)
    : RecordField(PADDING_FIELD, id, nullptr), ptype_(ptype), expr_(expr) {
    wordsize_ = -1;
}

RecordPaddingField::~RecordPaddingField() {}

void RecordPaddingField::Prepare(Env* env) {
    Field::Prepare(env);
    if ( ptype_ == PAD_TO_NEXT_WORD ) {
        if ( ! expr_->ConstFold(env, &wordsize_) )
            throw ExceptionPaddingError(this, strfmt("padding word size not a constant"));
    }
}

void RecordPaddingField::GenParseCode(Output* out_cc, Env* env) {
    // Always evaluate record fields in order if parsing
    // is incremental.
    if ( record_type()->incremental_parsing() && prev() )
        prev()->GenParseCode(out_cc, env);
}

int RecordPaddingField::StaticSize(Env* env, int offset) const {
    int length;
    int target_offset;
    int offset_in_word;

    switch ( ptype_ ) {
        case PAD_BY_LENGTH: return expr_->ConstFold(env, &length) ? length : -1;

        case PAD_TO_OFFSET:
            // If the current offset cannot be statically
            // determined, we need to Generate code to
            // check the offset
            if ( offset == -1 )
                return -1;

            if ( ! expr_->ConstFold(env, &target_offset) )
                return -1;

            // If both the current and target offsets
            // can be statically computed, we can get its
            // static size
            if ( offset > target_offset )
                throw ExceptionPaddingError(this, strfmt("current offset = %d, "
                                                         "target offset = %d",
                                                         offset, target_offset));
            return target_offset - offset;

        case PAD_TO_NEXT_WORD:
            if ( offset == -1 || wordsize_ == -1 )
                return -1;

            offset_in_word = offset % wordsize_;
            return (offset_in_word == 0) ? 0 : wordsize_ - offset_in_word;
    }

    return -1;
}

void RecordPaddingField::GenFieldEnd(Output* out_cc, Env* env, const DataPtr& field_begin) {
    ASSERT(! env->Evaluated(end_of_field_dataptr_var));

    char* padding_var;
    switch ( ptype_ ) {
        case PAD_BY_LENGTH:
            out_cc->println("if ( (%s) < 0 ) { // check for negative pad length", expr_->EvalExpr(out_cc, env));
            out_cc->inc_indent();
            out_cc->println("throw binpac::ExceptionInvalidStringLength(\"%s\", %s);", Location(),
                            expr_->EvalExpr(out_cc, env));
            out_cc->dec_indent();
            out_cc->println("}");
            out_cc->println("");

            out_cc->println("const_byteptr const %s = %s + (%s);", env->LValue(end_of_field_dataptr_var),
                            field_begin.ptr_expr(), expr_->EvalExpr(out_cc, env));

            out_cc->println("// Checking out-of-bound padding for \"%s\"", field_id_str_.c_str());
            out_cc->println("if ( %s > %s || %s < %s ) {", env->LValue(end_of_field_dataptr_var),
                            env->RValue(end_of_data), env->LValue(end_of_field_dataptr_var), field_begin.ptr_expr());
            out_cc->inc_indent();
            out_cc->println("throw binpac::ExceptionOutOfBound(\"%s\",", field_id_str_.c_str());
            out_cc->println("	(%s), ", expr_->EvalExpr(out_cc, env));
            out_cc->println("	(%s) - (%s));", env->RValue(end_of_data), env->LValue(end_of_field_dataptr_var));
            out_cc->dec_indent();
            out_cc->println("}");
            out_cc->println("");
            break;

        case PAD_TO_OFFSET:
            out_cc->println("const_byteptr %s = %s + (%s);", env->LValue(end_of_field_dataptr_var),
                            env->RValue(begin_of_data), expr_->EvalExpr(out_cc, env));
            out_cc->println("if ( %s < %s ) {", env->LValue(end_of_field_dataptr_var), field_begin.ptr_expr());
            out_cc->inc_indent();
            out_cc->println("// throw binpac::ExceptionInvalidOffset(\"%s\", %s - %s, %s);", id_->LocName(),
                            field_begin.ptr_expr(), env->RValue(begin_of_data), expr_->EvalExpr(out_cc, env));
            out_cc->println("%s = %s;", env->LValue(end_of_field_dataptr_var), field_begin.ptr_expr());
            out_cc->dec_indent();
            out_cc->println("}");
            out_cc->println("if ( %s > %s ) {", env->LValue(end_of_field_dataptr_var), env->RValue(end_of_data));
            out_cc->inc_indent();
            out_cc->println("throw binpac::ExceptionOutOfBound(\"%s\",", field_id_str_.c_str());
            out_cc->println("	(%s), ", expr_->EvalExpr(out_cc, env));
            out_cc->println("	(%s) - (%s));", env->RValue(end_of_data), env->LValue(end_of_field_dataptr_var));
            out_cc->dec_indent();
            out_cc->println("}");
            break;

        case PAD_TO_NEXT_WORD:
            padding_var = nfmt("%s__size", id()->Name());
            out_cc->println("int %s = (%s - %s) %% %d;", padding_var, field_begin.ptr_expr(),
                            env->RValue(begin_of_data), wordsize_);
            out_cc->println("%s = (%s == 0) ? 0 : %d - %s;", padding_var, padding_var, wordsize_, padding_var);
            out_cc->println("const_byteptr const %s = %s + %s;", env->LValue(end_of_field_dataptr_var),
                            field_begin.ptr_expr(), padding_var);
            delete[] padding_var;
            break;
    }

    env->SetEvaluated(end_of_field_dataptr_var);
}

bool RecordPaddingField::GenBoundaryCheck(Output* out_cc, Env* env) {
    if ( boundary_checked_ )
        return true;

    const DataPtr& begin = getFieldBegin(out_cc, env);

    char* size;
    int ss = StaticSize(env, begin.AbsOffset(begin_of_data));
    ASSERT(ss >= 0);
    size = nfmt("%d", ss);

    begin.GenBoundaryCheck(out_cc, env, size, field_id_str_.c_str());

    delete[] size;

    SetBoundaryChecked();
    return true;
}

bool RecordPaddingField::DoTraverse(DataDepVisitor* visitor) {
    return Field::DoTraverse(visitor) && (! expr_ || expr_->Traverse(visitor));
}
