//! Rust library for CCExtractor
//!
//! Currently we are in the process of porting the 708 decoder to rust. See [decoder]

// Allow C naming style
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

use decoder::Dtvcc;
use env_logger::{builder, Target};
use log::{warn, LevelFilter};
use std::{io::Write, os::raw::c_int};

/// CCExtractor C bindings generated by bindgen
#[allow(clippy::all)]
pub mod bindings {
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

pub mod decoder;
use bindings::*;

extern "C" {
    static mut cb_708: c_int;
}

/// Initialize env logger
#[no_mangle]
pub extern "C" fn ccxr_init_logger() {
    builder()
        .format(|buf, record| writeln!(buf, "[CEA-708] {}", record.args()))
        .filter_level(LevelFilter::Debug)
        .target(Target::Stdout)
        .init();
}

/// Process cc_data
///
/// # Safety
/// dec_ctx should not be a null pointer
/// data should point to cc_data of length cc_count
#[no_mangle]
extern "C" fn ccxr_process_cc_data(
    dec_ctx: *mut lib_cc_decode,
    data: *const ::std::os::raw::c_uchar,
    cc_count: c_int,
) -> c_int {
    let mut ret = -1;
    let mut cc_data: Vec<u8> = (0..cc_count * 3)
        .map(|x| unsafe { *data.add(x as usize) })
        .collect();
    let dec_ctx = unsafe { &mut *dec_ctx };
    let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc };
    let mut dtvcc = Dtvcc::new(dtvcc_ctx);
    for cc_block in cc_data.chunks_exact_mut(3) {
        if validate_cc_pair(cc_block) {
            continue;
        }
        let success = do_cb(dec_ctx, &mut dtvcc, cc_block);
        if success {
            ret = 0;
        }
    }
    ret
}

pub fn validate_cc_pair(cc_block: &[u8]) -> bool {
    let cc_valid = (cc_block[0] & 4) >> 2;
    if cc_valid == 0 {
        return true;
    }
    false
}

pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool {
    let cc_valid = (cc_block[0] & 4) >> 2;
    let cc_type = cc_block[0] & 3;
    let mut timeok = true;

    if ctx.write_format != ccx_output_format_CCX_OF_DVDRAW
        && ctx.write_format != ccx_output_format_CCX_OF_RAW
        && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD)
        && (cc_block[1] & 0x7F) == 0
        && (cc_block[2] & 0x7F) == 0
    {
        return true;
    }

    if cc_valid == 1 || cc_type == 3 {
        ctx.cc_stats[cc_type as usize] += 1;
        match cc_type {
            0 | 1 => {}
            2 | 3 => {
                let current_time = unsafe { get_fts(ctx.timing, ctx.current_field) };
                ctx.current_field = 3;
                if ctx.extraction_start.set == 1 && current_time < ctx.extraction_start.time_in_ms {
                    timeok = false;
                }

                if ctx.extraction_end.set == 1 && current_time > ctx.extraction_end.time_in_ms {
                    timeok = false;
                    ctx.processed_enough = 1;
                }

                if timeok && ctx.write_format != ccx_output_format_CCX_OF_RAW {
                    dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]);
                }
                unsafe { cb_708 += 1 }
            }
            _ => warn!("Invalid cc_type"),
        }
    }
    true
}
