#include "tty_protocol_writer.h"

#include <stdexcept>
#include <utility>

#ifdef _WIN32

TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
    throw std::runtime_error("tty output is supported only on Linux/POSIX");
}

TtyProtocolWriter::~TtyProtocolWriter() = default;

TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept = default;

TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept = default;

void TtyProtocolWriter::emit_packet_start() const {}

void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) const {
    (void) index;
    (void) ch1_avg;
    (void) ch2_avg;
}

const std::string& TtyProtocolWriter::path() const {
    return path_;
}

void TtyProtocolWriter::write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const {
    (void) word0;
    (void) word1;
    (void) word2;
    (void) word3;
}

void TtyProtocolWriter::close_fd() noexcept {}

#else

#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <sstream>
#include <sys/types.h>
#include <unistd.h>

namespace {

std::string io_error(const std::string& action, const std::string& path) {
    std::ostringstream out;
    out << action << " '" << path << "': " << std::strerror(errno);
    return out.str();
}

} // namespace

TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
    fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY);
    if (fd_ < 0) {
        throw std::runtime_error(io_error("Cannot open tty output", path_));
    }
}

TtyProtocolWriter::~TtyProtocolWriter() {
    close_fd();
}

TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept
    : path_(std::move(other.path_)),
      fd_(other.fd_) {
    other.fd_ = -1;
}

TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept {
    if (this != &other) {
        close_fd();
        path_ = std::move(other.path_);
        fd_ = other.fd_;
        other.fd_ = -1;
    }
    return *this;
}

void TtyProtocolWriter::emit_packet_start() const {
    write_frame(0x000A, 0xFFFF, 0xFFFF, 0xFFFF);
}

void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) const {
    write_frame(0x000A,
                index,
                static_cast<uint16_t>(ch1_avg),
                static_cast<uint16_t>(ch2_avg));
}

const std::string& TtyProtocolWriter::path() const {
    return path_;
}

void TtyProtocolWriter::write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const {
    const uint16_t frame[4] = {word0, word1, word2, word3};
    const std::uint8_t* bytes = reinterpret_cast<const std::uint8_t*>(frame);
    std::size_t remaining = sizeof(frame);

    while (remaining != 0U) {
        const ssize_t written = ::write(fd_, bytes, remaining);
        if (written < 0) {
            if (errno == EINTR) {
                continue;
            }
            throw std::runtime_error(io_error("Cannot write tty frame to", path_));
        }
        if (written == 0) {
            throw std::runtime_error("tty write returned 0 bytes for '" + path_ + "'");
        }
        bytes += static_cast<std::size_t>(written);
        remaining -= static_cast<std::size_t>(written);
    }
}

void TtyProtocolWriter::close_fd() noexcept {
    if (fd_ >= 0) {
        ::close(fd_);
        fd_ = -1;
    }
}

#endif
