/*
 * Copyright 2020 Tadashi G. Takaoka
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dis_formatter.h"
#include "str_buffer.h"

namespace libasm {
namespace driver {

DisFormatter::DisFormatter(Disassembler &disassembler, const char *inputName)
    : _disassembler(disassembler),
      _inputName(inputName),
      _formatter(*this),
      _insn(0),
      _operands(_buffer, sizeof(_buffer)),
      _error() {
    setUpperHex(true);
    setUppercase(false);
    setListRadix(_disassembler.listRadix());
}

void DisFormatter::setUpperHex(bool upperHex) {
    _formatter.setUpperHex(upperHex);
    _disassembler.setUpperHex(upperHex);
}

void DisFormatter::setUppercase(bool uppercase) {
    _uppercase = uppercase;
    _disassembler.setUppercase(uppercase);
}

void DisFormatter::setListRadix(Radix listRadix) {
    _formatter.setListRadix(listRadix);
    _disassembler.setListRadix(listRadix);
}

void DisFormatter::reset() {
    /* There is at least one line even when generated byte is zero. */
    _operands.reset();
    _insn.reset(0);
    _error.resetError();
    _errorMessage = _errorContent = _errorLine = false;
    _nextContent = _nextLine = -1;
    _continueMark_P = nullptr;
}

void DisFormatter::set(const ErrorAt &error, const /*PROGMEM*/ char *mark_P) {
    _error.setError(error);
    _errorMessage = _errorContent = _errorLine = _error.getError();
    if (mark_P) {
        _continueMark_P = mark_P;
        _nextContent = _nextLine = -1;
    }
}

void DisFormatter::setCpu(const char *cpu) {
    reset();
    StrCaseBuffer name{_insn.nameBuffer(), _uppercase};
    name.text_P(PSTR("CPU"));
    StrCaseBuffer opr{_operands, _uppercase};
    opr.text(cpu).over(_operands);
}

Error DisFormatter::setOrigin(uint32_t origin) {
    reset();
    const auto error = _disassembler.config().checkAddr(origin);
    if (error)
        return error;
    _insn.reset(origin);
    StrCaseBuffer name{_insn.nameBuffer(), _uppercase};
    name.text_P(PSTR("ORG"));
    _disassembler.outAbsAddr(_operands, origin).over(_operands);
    return OK;
}

bool DisFormatter::hasNextContent() const {
    return _nextContent < bytesSize();
}

static int max(int a, int b) {
    return a < b ? b : a;
}

void DisFormatter::formatComment(StrBuffer &out) {
    out.rtext_P(_disassembler.lineComment_P()).letter(' ');
}

void DisFormatter::formatError(StrBuffer &out) {
    out.rtext(_inputName).rtext_P(PSTR(": error: ")).rtext_P(_error.errorText_P());
    if (*_error.errorAt())
        out.rtext_P(PSTR(R"(: ")")).rtext(_error.errorAt()).rletter('"');
}

void DisFormatter::formatLine(StrBuffer &out, int &next) {
    _formatter.formatAddress(out, startAddress() + next);
    auto pos = out.len();
    char buf[1];
    StrBuffer null{buf, sizeof(buf)};
    auto &bytesOut = _continueMark_P ? null : out;
    const auto formatted = _formatter.formatBytes(bytesOut, next);
    if (next == 0 && *_insn.name()) {
        _formatter.formatTab(out, pos + _formatter.bytesColumnWidth() + 1);
        const auto namePos = out.len() + 8;  // label width
        if (_continueMark_P)
            out.rtext_P(_continueMark_P);
        _formatter.formatTab(out, namePos);
        pos = out.len();
        out.text(_insn.name());
        if (*_operands) {
            const auto nameWidth = max(config().nameMax(), min_nameWidth) + 1;
            _formatter.formatTab(out, pos + nameWidth);
            out.rtext(_operands);
        }
    }
    next += formatted;
}

StrBuffer &DisFormatter::getContent(StrBuffer &out) {
    out.reset();
    if (_errorMessage) {
        _errorMessage = false;
        formatComment(out);
        formatError(out);
    } else if (_errorContent) {
        if (_nextContent < 0)
            _nextContent = 0;
        formatComment(out);
        formatLine(out, _nextContent);
    } else {
        if (_nextContent < 0)
            _nextContent = 0;
        if (*_insn.name()) {
            const auto namePos = out.len() + 6;  // label width
            if (_continueMark_P)
                out.rtext_P(_continueMark_P);
            _formatter.formatTab(out, namePos);
            const auto pos = out.len();
            out.text(_insn.name());
            if (*_operands) {
                const auto nameWidth = max(config().nameMax(), min_nameWidth) + 1;
                _formatter.formatTab(out, pos + nameWidth);
                out.rtext(_operands);
            }
        }
        _nextContent = bytesSize();
    }
    return out;
}

bool DisFormatter::hasNextLine() const {
    return _nextLine < bytesSize();
}

StrBuffer &DisFormatter::getLine(StrBuffer &out) {
    out.reset();
    if (_errorLine) {
        _errorLine = false;
        formatError(out);
    } else {
        if (_nextLine < 0)
            _nextLine = 0;
        formatLine(out, _nextLine);
    }
    return out;
}

}  // namespace driver
}  // namespace libasm

// Local Variables:
// mode: c++
// c-basic-offset: 4
// tab-width: 4
// End:
// vim: set ft=cpp et ts=4 sw=4:
