/*
 * Copyright (c) 2007 - 2026 Joseph Gaeddert
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "liquid.autotest.h"
#include "liquid.internal.h"

LIQUID_AUTOTEST(gmskframesync_process,"simple recovery of GMSK frame","",0.1)
{
    // initialization and options
    unsigned int msg_len= 40;       // message length [bytes]
    crc_scheme   crc    = LIQUID_CRC_32;
    fec_scheme   fec0   = LIQUID_FEC_NONE;
    fec_scheme   fec1   = LIQUID_FEC_NONE;
    unsigned int context = 0;

    // create objects
    gmskframegen fg = gmskframegen_create();

    // create frame synchronizer
    gmskframesync fs = gmskframesync_create(framing_autotest_callback, (void*)&context);

    // assemble frame with specific data
    LIQUID_CHECK(gmskframegen_is_assembled(fg) ==  0);
    unsigned int i;
    unsigned char header[8];
    unsigned char msg   [msg_len];
    for (i=0; i<8; i++)       header[i] = i;
    for (i=0; i<msg_len; i++) msg[i]    = i & 0xff;
    gmskframegen_assemble(fg, header, msg, msg_len, crc, fec0, fec1);
    LIQUID_CHECK(gmskframegen_is_assembled(fg) ==  1);

    // allocate buffer (irregular size to test write method)
    unsigned int  buf_len = 53;
    float complex buf[buf_len];

    // generate the frame in blocks
    int frame_complete = 0;
    while (!frame_complete) {
        frame_complete = gmskframegen_write(fg, buf, buf_len);
        gmskframesync_execute(fs, buf, buf_len);
    }
    LIQUID_CHECK( gmskframesync_is_frame_open(fs) ==  0 );

    // check to see that frame was recovered
    LIQUID_CHECK( context ==  FRAMING_AUTOTEST_SECRET );

    // parse statistics
    framedatastats_s stats = gmskframesync_get_framedatastats(fs);
    LIQUID_CHECK(stats.num_frames_detected ==  1);
    LIQUID_CHECK(stats.num_headers_valid ==    1);
    LIQUID_CHECK(stats.num_payloads_valid ==   1);
    LIQUID_CHECK(stats.num_bytes_received ==   msg_len);

    // destroy objects
    gmskframegen_destroy(fg);
    gmskframesync_destroy(fs);
}

LIQUID_AUTOTEST(gmskframesync_multiple,"test receiving multiple GMSK frames","",0.1)
{
    // initialization and options
    unsigned int k          =  2;   // samples per symbol
    unsigned int m          = 12;   // filter semi-length
    float        bt         = 0.3f; // bandwidth-time factor
    unsigned int msg_len    = 40;   // message length [bytes]
    unsigned int num_frames = 80;   // number of frames to generate

    // create objects
    gmskframegen fg = gmskframegen_create_set(k,m,bt);
    gmskframesync fs = gmskframesync_create_set(k,m,bt,NULL,NULL);

    // allocate buffer for processing
    unsigned int  buf_len = 200;
    float complex buf[buf_len];

    // generate multiple frames
    unsigned int n;
    for (n=0; n<num_frames; n++) {
        // generate the frame in blocks
        gmskframegen_assemble_default(fg, msg_len);
        int frame_complete = 0;
        while (!frame_complete) {
            frame_complete = gmskframegen_write(fg, buf, buf_len);
            gmskframesync_execute(fs, buf, buf_len);
        }
    }

    // parse statistics
    framedatastats_s stats = gmskframesync_get_framedatastats(fs);
    liquid_log_debug(" detected:%u, headers valid:%u, payloads valid:%u, bytes rx:%u",
        stats.num_frames_detected, stats.num_headers_valid, stats.num_payloads_valid, stats.num_bytes_received);
    LIQUID_CHECK(stats.num_frames_detected ==  num_frames);
    LIQUID_CHECK(stats.num_headers_valid ==    num_frames);
    LIQUID_CHECK(stats.num_payloads_valid ==   num_frames);
    LIQUID_CHECK(stats.num_bytes_received ==   num_frames * msg_len);

    // destroy objects
    gmskframegen_destroy(fg);
    gmskframesync_destroy(fs);
}

// test different configurations
void testbench_gmskframesync(liquid_autotest __q__,
        unsigned int _k, unsigned int _m, float _bt)
{
    // create objects
    gmskframegen  fg = gmskframegen_create_set (_k,_m,_bt);
    gmskframesync fs = gmskframesync_create_set(_k,_m,_bt,NULL,NULL);

    // generate the frame in blocks
    gmskframegen_assemble_default(fg, 80);
    unsigned int  buf_len = 200;
    float complex buf[buf_len];
    int frame_complete = 0;
    while (!frame_complete) {
        frame_complete = gmskframegen_write(fg, buf, buf_len);
        gmskframesync_execute(fs, buf, buf_len);
    }

    // parse statistics
    framedatastats_s stats = gmskframesync_get_framedatastats(fs);
    liquid_log_debug(" detected:%u, headers valid:%u, payloads valid:%u, bytes rx:%u",
        stats.num_frames_detected, stats.num_headers_valid, stats.num_payloads_valid, stats.num_bytes_received);
    LIQUID_CHECK(stats.num_frames_detected ==  1);
    LIQUID_CHECK(stats.num_headers_valid ==    1);
    LIQUID_CHECK(stats.num_payloads_valid ==   1);
    LIQUID_CHECK(stats.num_bytes_received ==   80);

    // destroy objects
    gmskframegen_destroy(fg);
    gmskframesync_destroy(fs);
}

// test specific configurations
LIQUID_AUTOTEST(gmskframesync_k02_m05_bt20,"","",0.1) { testbench_gmskframesync(__q__,  2, 5, 0.20f); }
LIQUID_AUTOTEST(gmskframesync_k02_m05_bt30,"","",0.1) { testbench_gmskframesync(__q__,  2, 5, 0.30f); }
LIQUID_AUTOTEST(gmskframesync_k02_m05_bt40,"","",0.1) { testbench_gmskframesync(__q__,  2, 5, 0.40f); }

// double samples per symbol
LIQUID_AUTOTEST(gmskframesync_k04_m05_bt20,"","",0.1) { testbench_gmskframesync(__q__,  4, 5, 0.20f); }
LIQUID_AUTOTEST(gmskframesync_k04_m05_bt30,"","",0.1) { testbench_gmskframesync(__q__,  4, 5, 0.30f); }
LIQUID_AUTOTEST(gmskframesync_k04_m05_bt40,"","",0.1) { testbench_gmskframesync(__q__,  4, 5, 0.40f); }

// test odd configurations
LIQUID_AUTOTEST(gmskframesync_k03_m07_bt20,"","",0.1) { testbench_gmskframesync(__q__,  3, 7, 0.20f); }
LIQUID_AUTOTEST(gmskframesync_k08_m20_bt15,"","",0.1) { testbench_gmskframesync(__q__,  8,20, 0.15f); }
LIQUID_AUTOTEST(gmskframesync_k15_m02_bt40_s,"gmskframesync_k15_m02_bt40_s","",0.1) { testbench_gmskframesync(__q__, 15, 2, 0.40f); }

LIQUID_AUTOTEST(gmskframe_config,"check configuration validity","",0.1)
{
    _liquid_error_downgrade_enable();

    // create gmskframegen object
    gmskframegen fg = gmskframegen_create();

    // assemble the frame
    LIQUID_CHECK(gmskframegen_assemble(fg,NULL,NULL,                       0, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_NONE)==LIQUID_EICONFIG);
    LIQUID_CHECK(gmskframegen_assemble(fg,NULL,NULL,                       1, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_NONE)==LIQUID_OK      );
    LIQUID_CHECK(gmskframegen_assemble(fg,NULL,NULL,LIQUID_MAX_PAYLOAD_LEN  , LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_NONE)==LIQUID_OK      );
    LIQUID_CHECK(gmskframegen_assemble(fg,NULL,NULL,LIQUID_MAX_PAYLOAD_LEN+1, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_NONE)==LIQUID_EICONFIG);

    gmskframegen_destroy(fg);
    _liquid_error_downgrade_disable();
}

