/* -*- c++ -*- */
/*
 * Copyright 2014 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "hdlc_framer_pb_impl.h"
#include <gnuradio/io_signature.h>
#include <gnuradio/tags.h>
#include <iostream>

namespace gr {
namespace digital {

hdlc_framer_pb::sptr hdlc_framer_pb::make(const std::string frame_tag_name)
{
    return gnuradio::make_block_sptr<hdlc_framer_pb_impl>(frame_tag_name);
}

/*
 * The private constructor
 */
hdlc_framer_pb_impl::hdlc_framer_pb_impl(const std::string frame_tag_name)
    : gr::sync_block("hdlc_framer_pb",
                     gr::io_signature::make(0, 0, 0),
                     gr::io_signature::make(1, 1, sizeof(unsigned char))),
      d_port(pmt::mp("in"))
{
    message_port_register_in(d_port);
    d_frame_tag = pmt::string_to_symbol(frame_tag_name);
    std::stringstream str;
    str << name() << unique_id();
    d_me = pmt::string_to_symbol(str.str());
}

/*
 * Our virtual destructor.
 */
hdlc_framer_pb_impl::~hdlc_framer_pb_impl() {}

// bit stuff
void hdlc_framer_pb_impl::stuff(std::vector<unsigned char>& pkt)
{
    int consec = 0;
    for (size_t i = 0; i < pkt.size(); i++) {
        if (consec == 5) {
            pkt.insert(pkt.begin() + i, 0);
            consec = 0;
        }
        if (pkt[i] == 1)
            consec++;
        else
            consec = 0;
    }
}

// unpack packed (8 bits per byte) into bits, in LSbit order.
// TODO: handle non-multiple-of-8 bit lengths (pad low)
std::vector<unsigned char> hdlc_framer_pb_impl::unpack(std::vector<unsigned char>& data)
{
    std::vector<unsigned char> output(data.size() * 8, 0);
    for (size_t i = 0; i < data.size(); i++) {
        for (size_t j = 0; j < 8; j++) {
            output[i * 8 + j] = (data[i] >> j) & 1;
        }
    }
    return output;
}

unsigned int hdlc_framer_pb_impl::crc_ccitt(std::vector<unsigned char>& data)
{
    unsigned int POLY = 0x8408; // reflected 0x1021
    unsigned short crc = 0xFFFF;
    for (size_t i = 0; i < data.size(); i++) {
        crc ^= data[i];
        for (size_t j = 0; j < 8; j++) {
            if (crc & 0x01)
                crc = (crc >> 1) ^ POLY;
            else
                crc = (crc >> 1);
        }
    }
    return crc ^ 0xFFFF;
}

int hdlc_framer_pb_impl::work(int noutput_items,
                              gr_vector_const_void_star& input_items,
                              gr_vector_void_star& output_items)
{
    unsigned char* out = (unsigned char*)output_items[0];

    // send leftovers one chunk at a time.
    // it'd be more efficient to send as much as possible, i.e.
    // partial packets., but if we're to preserve tag boundaries
    // this is much, much simpler.
    int oidx = 0;
    while (!d_leftovers.empty()) {
        if ((size_t)noutput_items < (oidx + d_leftovers[0].size()))
            return oidx;
        memcpy(out + oidx, &d_leftovers[0][0], d_leftovers[0].size());
        // start tag
        add_item_tag(0,
                     nitems_written(0) + oidx,
                     d_frame_tag,
                     pmt::from_long(d_leftovers[0].size()),
                     d_me);
        oidx += d_leftovers[0].size();
        d_leftovers.erase(d_leftovers.begin());
    }

    // get PDU
    pmt::pmt_t msg(delete_head_nowait(d_port));
    if (msg.get() == NULL)
        return oidx;

    pmt::pmt_t len(pmt::car(msg)); // TODO for non-mult-8 nbits
    pmt::pmt_t blob(pmt::cdr(msg));
    if (!pmt::is_blob(blob))
        throw std::runtime_error("HDLC framer: PMT must be blob");
    std::vector<unsigned char> pkt(pmt::blob_length(blob));
    memcpy(&pkt[0], (const unsigned char*)pmt::blob_data(blob), pkt.size());

    // calc CRC
    unsigned int crc = crc_ccitt(pkt);

    // append CRC
    pkt.insert(pkt.end(), crc & 0xFF);
    pkt.insert(pkt.end(), (crc >> 8) & 0xFF);

    // unpack to LSb bits
    std::vector<unsigned char> pkt_bits = unpack(pkt);

    // bitstuff
    stuff(pkt_bits);

    // add framing
    const unsigned char framing[] = { 0, 1, 1, 1, 1, 1, 1, 0 };
    std::vector<unsigned char> framing_vec(framing, framing + sizeof(framing));
    pkt_bits.insert(pkt_bits.begin(), framing_vec.begin(), framing_vec.end());
    pkt_bits.insert(pkt_bits.end(), framing_vec.begin(), framing_vec.end());

    // make sure we have the space. unfortunately, we didn't know
    // until now, since the stuffing must be calculated. we'll just
    // save it for next time.
    if ((size_t)noutput_items < (oidx + pkt_bits.size())) {
        d_leftovers.insert(d_leftovers.end(), pkt_bits);
        return oidx;
    }

    // produce
    memcpy(out + oidx, &pkt_bits[0], pkt_bits.size());
    // start tag
    add_item_tag(
        0, nitems_written(0) + oidx, d_frame_tag, pmt::from_long(pkt_bits.size()), d_me);
    oidx += pkt_bits.size();

    // return # output bits
    return oidx;
}

} /* namespace digital */
} /* namespace gr */
