/* -*- c++ -*- */
/*
 * Copyright 2015 Free Software Foundation, Inc.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

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

#include "atsc_trellis_encoder_impl.h"
#include "gnuradio/dtv/atsc_consts.h"
#include <cstdio>

namespace gr {
namespace dtv {

static constexpr int DIBITS_PER_BYTE = 4;

#define SEGOF(x) ((x) / ((SEGMENT_SIZE + 1) * DIBITS_PER_BYTE))
#define SYMOF(x) (((x) % ((SEGMENT_SIZE + 1) * DIBITS_PER_BYTE)) - 4)

#define ENCODER_SEG_BUMP 4

/* A Segment sync symbol is an 8VSB +5,-5,-5,+5 sequence that occurs at
   the start of each 207-byte segment (including field sync segments).  */
#define DSEG_SYNC_SYM1 0x06 /* +5 */
#define DSEG_SYNC_SYM2 0x01 /* -5 */
#define DSEG_SYNC_SYM3 0x01 /* -5 */
#define DSEG_SYNC_SYM4 0x06 /* +5 */

/* Shift counts to bit numbers (high order, low order); 9x entries unused */
static const int bit1[8] = { 1, 99, 3, 98, 5, 97, 7, 96 };
static const int bit2[8] = { 0, 99, 2, 98, 4, 97, 6, 96 };

atsc_trellis_encoder::sptr atsc_trellis_encoder::make()
{
    return gnuradio::make_block_sptr<atsc_trellis_encoder_impl>();
}

atsc_trellis_encoder_impl::atsc_trellis_encoder_impl()
    : gr::sync_block("atsc_trellis_encoder",
                     gr::io_signature::make(1, 1, sizeof(atsc_mpeg_packet_rs_encoded)),
                     gr::io_signature::make(1, 1, sizeof(atsc_data_segment)))
{
    reset();
    debug = false;
    set_output_multiple(NCODERS);
}

atsc_trellis_encoder_impl::~atsc_trellis_encoder_impl() {}

void atsc_trellis_encoder_impl::reset()
{
    for (int i = 0; i < NCODERS; i++) {
        enc[i].reset();
    }
}

void atsc_trellis_encoder_impl::encode(atsc_data_segment out[NCODERS],
                                       const atsc_mpeg_packet_rs_encoded in[NCODERS])
{
    unsigned char out_copy[OUTPUT_SIZE];
    unsigned char in_copy[INPUT_SIZE];

    static_assert(sizeof(in_copy) == sizeof(in[0].data) * NCODERS, "wrong type size");
    static_assert(sizeof(out_copy) == sizeof(out[0].data) * NCODERS, "wrong type size");

    // copy input into contiguous temporary buffer
    for (int i = 0; i < NCODERS; i++) {
        assert(in[i].pli.regular_seg_p());
        memcpy(&in_copy[i * INPUT_SIZE / NCODERS],
               &in[i].data[0],
               ATSC_MPEG_RS_ENCODED_LENGTH * sizeof(in_copy[0]));
    }

    memset(out_copy, 0, sizeof(out_copy)); // FIXME, sanity check

    // do the deed...
    encode_helper(out_copy, in_copy);

    // copy output from contiguous temp buffer into final output
    for (int i = 0; i < NCODERS; i++) {
        memcpy(&out[i].data[0],
               &out_copy[i * OUTPUT_SIZE / NCODERS],
               ATSC_DATA_SEGMENT_LENGTH * sizeof(out_copy[0]));

        // copy pipeline info
        out[i].pli = in[i].pli;

        assert(out[i].pli.regular_seg_p());
    }
}

/*
 * This code expects contiguous arrays. Use it as is, it computes
 * the correct answer. Maybe someday, when we've run out of better
 * things to do, rework to avoid the copying in encode.
 */
void atsc_trellis_encoder_impl::encode_helper(unsigned char output[OUTPUT_SIZE],
                                              const unsigned char input[INPUT_SIZE])
{
    int i;
    int encoder;
    unsigned char trellis_buffer[NCODERS];
    int trellis_wherefrom[NCODERS];
    unsigned char *out, *next_out_seg;
    int chunk;
    int shift;
    unsigned char dibit;
    unsigned char symbol;
    int skip_encoder_bump;

    /* FIXME, we may want special processing here for a flag
     * byte to keep track of which part of the field we're in?
     */

    encoder = NCODERS - ENCODER_SEG_BUMP;
    skip_encoder_bump = 0;
    out = output;
    next_out_seg = out;

    for (chunk = 0; chunk < INPUT_SIZE; chunk += NCODERS) {
        /* Load a new chunk of bytes into the Trellis encoder buffers.
         * They get loaded in an order that depends on where we are in
         * the segment sync progress (sigh).
         * GRR! When the chunk reload happens at the same time as the
         * segment boundary, we should bump the encoder NOW for the reload,
         * rather than LATER during the bitshift transition!!!
         */
        if (out >= next_out_seg) {
            encoder = (encoder + ENCODER_SEG_BUMP) % NCODERS;
            skip_encoder_bump = 1;
        }

        for (i = 0; i < NCODERS; i++) {
            /* for debug */
            trellis_wherefrom[encoder] = chunk + i;
            trellis_buffer[encoder] = input[chunk + i];
            encoder++;
            if (encoder >= NCODERS) {
                encoder = 0;
            }
        }

        for (shift = 6; shift >= 0; shift -= 2) {
            /* Segment boundaries happen to occur on some bitshift transitions. */
            if (out >= next_out_seg) {
                /* Segment transition. Output a data segment sync symbol,
                 * and mess with the trellis encoder mux.
                 */
                *out++ = DSEG_SYNC_SYM1;
                *out++ = DSEG_SYNC_SYM2;
                *out++ = DSEG_SYNC_SYM3;
                *out++ = DSEG_SYNC_SYM4;
                if (debug) {
                    printf("SYNC SYNC SYNC SYNC\n");
                }

                next_out_seg = out + (SEGMENT_SIZE * DIBITS_PER_BYTE);

                if (!skip_encoder_bump) {
                    encoder = (encoder + ENCODER_SEG_BUMP) % NCODERS;
                }
                skip_encoder_bump = 0;
            }

            /* Now run each of the 12 Trellis encoders to spit out 12 symbols.
             * Each encoder takes input from the same byte of the chunk, but
             * the outputs of the encoders come out in various orders.
             * NOPE -- this is false. The encoders take input from various
             * bytes of the chunk (which changes at segment sync time), AND
             * they also come out in various orders. You really do have to
             * keep separate track of: the input bytes, the encoders, and
             * the output bytes -- because they're all moving with respect
             * to each other!!!
             */
            for (i = 0; i < NCODERS; i++) {
                dibit = 0x03 & (trellis_buffer[encoder] >> shift);
                if (debug) {
                    printf("Seg %ld Symb %3ld Trell %2d Byte %6d Bits %d-%d = dibit %d ",
                           (long)SEGOF(out - output),
                           (long)SYMOF(out - output),
                           encoder,
                           trellis_wherefrom[encoder],
                           bit1[shift],
                           bit2[shift],
                           dibit);
                }

                symbol = enc[encoder].encode(dibit);
                *out++ = symbol;
                encoder++;
                if (encoder >= NCODERS) {
                    encoder = 0;
                }
                if (debug) {
                    printf("sym %d\n", symbol);
                }

            } /* Encoders */
        }     /* Bit shifts */
    }         /* Chunks */

    /* Check up on ourselves */
    static_assert(0 == (INPUT_SIZE * DIBITS_PER_BYTE) % NCODERS,
                  "size not divisible by NCODERS");
    assert(OUTPUT_SIZE == out - output);
    assert(NCODERS - ENCODER_SEG_BUMP == encoder);
}

int atsc_trellis_encoder_impl::work(int noutput_items,
                                    gr_vector_const_void_star& input_items,
                                    gr_vector_void_star& output_items)
{
    const atsc_mpeg_packet_rs_encoded* in =
        (const atsc_mpeg_packet_rs_encoded*)input_items[0];
    atsc_data_segment* out = (atsc_data_segment*)output_items[0];

    for (int i = 0; i < noutput_items; i += NCODERS) {
        encode(&out[i], &in[i]);
    }

    // Tell runtime system how many output items we produced.
    return noutput_items;
}

} /* namespace dtv */
} /* namespace gr */