//! Caption Service Decoder
//!
//! Caption Service decoder processes service blocks and handles the different [commands][super::commands] received

use std::{
    alloc::{alloc, dealloc, Layout},
    os::raw::c_uchar,
};

use super::commands::{self, C0CodeSet, C0Command, C1CodeSet, C1Command};
use super::window::{PenPreset, WindowPreset};
use super::{
    CCX_DTVCC_MAX_COLUMNS, CCX_DTVCC_MAX_ROWS, CCX_DTVCC_SCREENGRID_COLUMNS,
    CCX_DTVCC_SCREENGRID_ROWS,
};
use crate::{
    bindings::*,
    decoder::output::Writer,
    utils::{is_false, is_true},
};

use log::{debug, error, warn};

const CCX_DTVCC_MUSICAL_NOTE_CHAR: u16 = 9836;
const CCX_DTVCC_MAX_WINDOWS: u8 = 8;
const DTVCC_COMMANDS_C0_CODES_DTVCC_C0_EXT1: u8 = 16;

impl dtvcc_service_decoder {
    /// Process service block and call handlers for the respective codesets
    pub fn process_service_block(
        &mut self,
        block: &[u8],
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
        no_rollup: bool,
    ) {
        let mut i = 0;
        while i < block.len() {
            let consumed = if block[i] != DTVCC_COMMANDS_C0_CODES_DTVCC_C0_EXT1 {
                let used = match block[i] {
                    0..=0x1F => self.handle_C0(&block[i..], encoder, timing, no_rollup),
                    0x20..=0x7F => self.handle_G0(&block[i..]),
                    0x80..=0x9F => self.handle_C1(&block[i..], encoder, timing),
                    _ => self.handle_G1(&block[i..]),
                };
                if used == -1 {
                    warn!("dtvcc_process_service_block: There was a problem handling the data.");
                    return;
                }
                used as u8
            } else {
                // EXT1 command - process extended character from next position
                if i + 1 >= block.len() {
                    warn!(
                        "dtvcc_process_service_block: EXT1 at end of block with no following data"
                    );
                    return;
                }
                let mut used = self.handle_extended_char(&block[(i + 1)..]);
                used += 1; // Since we had CCX_DTVCC_C0_EXT1
                used
            };
            i += consumed as usize;
        }
    }

    // -------------------------- C0 Commands-------------------------

    /// C0 - Code Set - Miscellaneous Control Codes
    pub fn handle_C0(
        &mut self,
        block: &[u8],
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
        no_rollup: bool,
    ) -> i32 {
        let code = block[0];
        let C0Command { command, length } = C0Command::new(code);
        debug!("C0: [{:?}] ({})", command, block.len());
        if length as usize > block.len() {
            warn!(
                "dtvcc_handle_C0: command {:#04X} needs {} bytes but block only has {}; skipping",
                code,
                length,
                block.len()
            );
            return -1;
        }
        match command {
            // NUL command does nothing
            C0CodeSet::NUL => {}
            C0CodeSet::ETX => self.process_etx(),
            C0CodeSet::BS => self.process_bs(),
            C0CodeSet::FF => self.process_ff(),
            C0CodeSet::CR => self.process_cr(encoder, timing, no_rollup),
            C0CodeSet::HCR => self.process_hcr(),
            // EXT1 is handled elsewhere as an extended command
            C0CodeSet::EXT1 => {}
            C0CodeSet::P16 => self.process_p16(&block[1..]),
            C0CodeSet::RESERVED => {}
        }
        length as i32
    }

    /// Process Carriage Return(CR)
    ///
    /// Refer Section 7.1.4.1 and 8.4.9.2 CEA-708-E
    ///
    /// Carriage Return (CR) moves the current entry point to the beginning of the next row. If the next row is
    /// below the visible window, the window “rolls up"
    pub fn process_cr(
        &mut self,
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
        no_rollup: bool,
    ) {
        if self.current_window == -1 {
            warn!("dtvcc_process_cr: Window has to be defined first");
            return;
        }
        let window = &mut self.windows[self.current_window as usize];
        let mut rollup_required = false;
        let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
            Ok(val) => val,
            Err(e) => {
                warn!("{e}");
                return;
            }
        };
        match pd {
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
                window.pen_column = 0;
                if window.pen_row + 1 < window.row_count {
                    window.pen_row += 1;
                } else {
                    rollup_required = true;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
                window.pen_column = window.col_count;
                if window.pen_row + 1 < window.row_count {
                    window.pen_row += 1;
                } else {
                    rollup_required = true;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
                window.pen_row = 0;
                if window.pen_column + 1 < window.col_count {
                    window.pen_column += 1;
                } else {
                    rollup_required = true;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
                window.pen_row = window.row_count;
                if window.pen_column + 1 < window.col_count {
                    window.pen_column += 1;
                } else {
                    rollup_required = true;
                }
            }
        };

        if is_true(window.is_defined) {
            let pen_row = window.pen_row;
            window.update_time_hide(timing);

            if rollup_required {
                debug!("dtvcc_process_cr: rolling up");
                self.copy_to_screen(&self.windows[self.current_window as usize]);
                self.screen_print(encoder, timing);
                if no_rollup {
                    self.windows[self.current_window as usize].clear_row(pen_row as usize);
                } else {
                    self.windows[self.current_window as usize].rollup();
                }
            }
            self.windows[self.current_window as usize].update_time_show(timing);
        }
    }

    /// Process Horizontal Carriage Return (HCR)
    ///
    /// Refer Section 7.1.4.1 CEA-708-E
    ///
    /// Horizontal Carriage Return (HCR) moves the current entry point to the beginning of the current row
    /// without row increment or decrement. It shall erase all text on the row
    pub fn process_hcr(&mut self) {
        if self.current_window == -1 {
            warn!("dtvcc_process_hcr: Window has to be defined first");
            return;
        }
        let window = &mut self.windows[self.current_window as usize];
        window.pen_column = 0;
        window.clear_row(window.pen_row as usize);
    }

    /// Process Form Feed (FF)
    ///
    /// Refer Section 7.1.4.1 CEA-708-E
    ///
    /// Form Feed (FF) erases all text in the window and moves the cursor to the first position (0,0)
    pub fn process_ff(&mut self) {
        if self.current_window == -1 {
            warn!("dtvcc_process_ff: Window has to be defined first");
            return;
        }
        let window = &mut self.windows[self.current_window as usize];
        window.pen_column = 0;
        window.pen_row = 0;
        window.clear_text();
    }

    /// Process Backspace (BS)
    ///
    /// Backspace (BS) moves the cursor back by one position in the print direction
    pub fn process_bs(&mut self) {
        if self.current_window == -1 {
            warn!("dtvcc_process_bs: Window has to be defined first");
            return;
        }
        //it looks strange, but in some videos (rarely) we have a backspace command
        //we just print one character over another
        let window = &mut self.windows[self.current_window as usize];
        let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
            Ok(val) => val,
            Err(e) => {
                warn!("{e}");
                return;
            }
        };
        match pd {
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
                if window.pen_column > 0 {
                    window.pen_column -= 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
                if window.pen_column + 1 < window.col_count {
                    window.pen_column += 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
                if window.pen_row > 0 {
                    window.pen_row -= 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
                if window.pen_row + 1 < window.row_count {
                    window.pen_row += 1;
                }
            }
        };
    }

    /// Process P16
    ///
    /// Used for Code space extension for 16 bit charsets
    pub fn process_p16(&mut self, block: &[c_uchar]) {
        if self.current_window == -1 {
            warn!("dtvcc_process_p16: Window has to be defined first");
            return;
        }
        let sym = dtvcc_symbol::new_16(block[0], block[1]);
        debug!("dtvcc_process_p16: [{:4X}]", sym.sym);
        self.process_character(sym);
    }

    /// Process End of Text (ETX)
    pub fn process_etx(&mut self) {}

    // -------------------------- C1 Commands-------------------------

    /// C1 - Code Set - Captioning Command Control Codes
    pub fn handle_C1(
        &mut self,
        block: &[c_uchar],
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
    ) -> i32 {
        let code = block[0];
        let C1Command {
            command,
            length,
            name,
        } = C1Command::new(code);

        if length as usize > block.len() {
            warn!(
                "dtvcc_handle_C1: command {:#04X} ({}) needs {} bytes but block only has {}; skipping",
                code,
                name,
                length,
                block.len()
            );
            return -1;
        }

        debug!("C1: [{command:?}] [{name}] ({length})");
        match command {
            C1CodeSet::CW0
            | C1CodeSet::CW1
            | C1CodeSet::CW2
            | C1CodeSet::CW3
            | C1CodeSet::CW4
            | C1CodeSet::CW5
            | C1CodeSet::CW6
            | C1CodeSet::CW7 => {
                self.handle_set_current_window(code - DTVCC_COMMANDS_C1_CODES_DTVCC_C1_CW0 as u8)
            }
            C1CodeSet::CLW => self.handle_clear_windows(block[1], encoder, timing),
            C1CodeSet::HDW => self.handle_hide_windows(block[1], encoder, timing),
            C1CodeSet::TGW => self.handle_toggle_windows(block[1], encoder, timing),
            C1CodeSet::DLW => self.handle_delete_windows(block[1], encoder, timing),
            C1CodeSet::DSW => self.handle_display_windows(block[1], timing),
            C1CodeSet::DLY => self.handle_delay(block[1]),
            C1CodeSet::DLC => self.handle_delay_cancel(),
            C1CodeSet::RST => self.handle_reset(),
            C1CodeSet::SPA => self.handle_set_pen_attributes(&block[1..]),
            C1CodeSet::SPC => self.handle_set_pen_color(&block[1..]),
            C1CodeSet::SPL => self.handle_set_pen_location(&block[1..]),
            C1CodeSet::SWA => self.handle_set_window_attributes(&block[1..]),
            C1CodeSet::DF0
            | C1CodeSet::DF1
            | C1CodeSet::DF2
            | C1CodeSet::DF3
            | C1CodeSet::DF4
            | C1CodeSet::DF5
            | C1CodeSet::DF6
            | C1CodeSet::DF7 => self.handle_define_windows(
                code - DTVCC_COMMANDS_C1_CODES_DTVCC_C1_DF0 as u8,
                &block[1..],
                timing,
            ),
            C1CodeSet::RESERVED => {
                warn!("Warning, Found Reserved codes, ignored");
            }
        };
        length as i32
    }

    /// CLW Clear Windows
    ///
    /// Clear text from all windows specified by bitmap. If window had content then
    /// all text is copied to the TV screen
    pub fn handle_clear_windows(
        &mut self,
        mut windows_bitmap: u8,
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_CLW_ClearWindows: windows:");
        let mut screen_content_changed = false;
        if windows_bitmap == 0 {
            debug!("none");
        } else {
            for i in 0..CCX_DTVCC_MAX_WINDOWS {
                if windows_bitmap & 1 == 1 {
                    let window = &mut self.windows[i as usize];
                    debug!("[W{i}]");
                    let window_had_content = is_true(window.is_defined)
                        && is_true(window.visible)
                        && is_false(window.is_empty);
                    if window_had_content {
                        screen_content_changed = true;
                        window.update_time_hide(timing);
                        self.copy_to_screen(&self.windows[i as usize]);
                    }
                    self.windows[i as usize].clear_text();
                }
                windows_bitmap >>= 1;
            }
        }
        if screen_content_changed {
            self.screen_print(encoder, timing);
        }
    }

    /// HDW Hide Windows
    ///
    /// Hide all windows specified by bitmap. If window was not empty then
    /// all text is copied to the TV screen
    pub fn handle_hide_windows(
        &mut self,
        mut windows_bitmap: u8,
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_HDW_HideWindows: windows:");
        if windows_bitmap == 0 {
            debug!("none");
        } else {
            let mut screen_content_changed = false;
            for i in 0..CCX_DTVCC_MAX_WINDOWS {
                if windows_bitmap & 1 == 1 {
                    let window = &mut self.windows[i as usize];
                    debug!("[W{i}]");
                    if is_true(window.visible) {
                        screen_content_changed = true;
                        window.visible = 0;
                        window.update_time_hide(timing);
                        if is_false(window.is_empty) {
                            self.copy_to_screen(&self.windows[i as usize]);
                        }
                    }
                }
                windows_bitmap >>= 1;
            }
            if screen_content_changed && !self.has_visible_windows() {
                self.screen_print(encoder, timing);
            }
        }
    }

    /// TGW Toggle Windows
    ///
    /// Toggle visibility of all windows specified by bitmap. If window was not empty then
    /// all text is copied to the TV screen
    pub fn handle_toggle_windows(
        &mut self,
        mut windows_bitmap: u8,
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_TGW_ToggleWindows: windows:");
        if windows_bitmap == 0 {
            debug!("none");
        } else {
            let mut screen_content_changed = false;
            for i in 0..CCX_DTVCC_MAX_WINDOWS {
                let window = &mut self.windows[i as usize];
                if windows_bitmap & 1 == 1 && is_true(window.is_defined) {
                    if is_false(window.visible) {
                        debug!("[W-{i}: 0->1]");
                        window.visible = 1;
                        window.update_time_show(timing);
                    } else {
                        debug!("[W-{i}: 1->0]");
                        window.visible = 0;
                        window.update_time_hide(timing);
                        if is_false(window.is_empty) {
                            screen_content_changed = true;
                            self.copy_to_screen(&self.windows[i as usize]);
                        }
                    }
                }
                windows_bitmap >>= 1;
            }
            if screen_content_changed && !self.has_visible_windows() {
                self.screen_print(encoder, timing);
            }
        }
    }

    /// DLW Delete Windows
    ///
    /// Delete all windows specified by bitmap. If window had content then
    /// all text is copied to the TV screen
    pub fn handle_delete_windows(
        &mut self,
        mut windows_bitmap: u8,
        encoder: &mut encoder_ctx,
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_DLW_DeleteWindows: windows:");
        let mut screen_content_changed = false;
        if windows_bitmap == 0 {
            debug!("none");
        } else {
            for i in 0..CCX_DTVCC_MAX_WINDOWS {
                if windows_bitmap & 1 == 1 {
                    debug!("Deleting [W{i}]");
                    let window = &mut self.windows[i as usize];
                    let window_had_content = is_true(window.is_defined)
                        && is_true(window.visible)
                        && is_false(window.is_empty);
                    if window_had_content {
                        screen_content_changed = true;
                        window.update_time_hide(timing);
                        self.copy_to_screen(&self.windows[i as usize]);
                        if self.current_window == i as i32 {
                            self.screen_print(encoder, timing);
                        }
                    }
                    let window = &mut self.windows[i as usize];
                    window.is_defined = 0;
                    window.visible = 0;
                    window.time_ms_hide = -1;
                    window.time_ms_show = -1;
                    if self.current_window == i as i32 {
                        self.current_window = -1;
                    }
                }
                windows_bitmap >>= 1;
            }
        }
        if screen_content_changed && !self.has_visible_windows() {
            self.screen_print(encoder, timing);
        }
    }

    /// DSW Display Windows
    ///
    /// Display all windows specified by bitmap.
    pub fn handle_display_windows(
        &mut self,
        mut windows_bitmap: u8,
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_DSW_DisplayWindows: windows:");
        if windows_bitmap == 0 {
            debug!("none");
        } else {
            for i in 0..CCX_DTVCC_MAX_WINDOWS {
                if windows_bitmap & 1 == 1 {
                    let window = &mut self.windows[i as usize];
                    debug!("[W{i}]");
                    if is_false(window.is_defined) {
                        error!("Window {i} was not defined");
                        continue;
                    }
                    if is_false(window.visible) {
                        window.visible = 1;
                        window.update_time_show(timing);
                    }
                }
                windows_bitmap >>= 1;
            }
        }
    }

    /// DFx Define Windows
    ///
    /// Define a new window with the provided attributes. New window is now the current window
    pub fn handle_define_windows(
        &mut self,
        window_id: u8,
        block: &[c_uchar],
        timing: &mut ccx_common_timing_ctx,
    ) {
        debug!("dtvcc_handle_DFx_DefineWindow: W[{window_id}], attributes:");
        let window = &mut self.windows[window_id as usize];
        let block = &block[..=5];
        let is_command_repeated = window
            .commands
            .iter()
            .zip(block.iter())
            .all(|(x, y)| x == y);

        if is_true(window.is_defined) && is_command_repeated {
            // When a decoder receives a DefineWindow command for an existing window, the
            // command is to be ignored if the command parameters are unchanged from the
            // previous window definition.
            debug!("dtvcc_handle_DFx_DefineWindow: Repeated window definition, ignored\n");
            return;
        }

        window.number = window_id as i32;
        let priority = (block[0]) & 0x7;
        let col_lock = (block[0] >> 3) & 0x1;
        let row_lock = (block[0] >> 4) & 0x1;
        let visible = (block[0] >> 5) & 0x1;
        let mut anchor_vertical = block[1] & 0x7f;
        let relative_pos = block[1] >> 7;
        let mut anchor_horizontal = block[2];
        let row_count = (block[3] & 0xf) + 1;
        let anchor_point = block[3] >> 4;
        let col_count = (block[4] & 0x3f) + 1;
        let mut pen_style = block[5] & 0x7;
        let mut win_style = (block[5] >> 3) & 0x7;

        let mut do_clear_window = false;

        debug!("Visible: [{}]", if is_true(visible) { "Yes" } else { "No" });
        debug!("Priority: [{priority}]");
        debug!("Row count: [{row_count}]");
        debug!("Column count: [{col_count}]");
        debug!("Anchor point: [{anchor_point}]");
        debug!("Anchor vertical: [{anchor_vertical}]");
        debug!("Anchor horizontal: [{anchor_horizontal}]");
        debug!(
            "Relative pos: [{}]",
            if is_true(relative_pos) { "Yes" } else { "No" }
        );
        debug!(
            "Row lock: [{}]",
            if is_true(row_lock) { "Yes" } else { "No" }
        );
        debug!(
            "Column lock: [{}]",
            if is_true(col_lock) { "Yes" } else { "No" }
        );
        debug!("Pen style: [{pen_style}]");
        debug!("Win style: [{win_style}]");

        // Korean samples have "anchor_vertical" and "anchor_horizontal" mixed up,
        // this seems to be an encoder issue, but we can workaround it
        if anchor_vertical > CCX_DTVCC_SCREENGRID_ROWS - row_count {
            anchor_vertical = CCX_DTVCC_SCREENGRID_ROWS - row_count;
        }
        if anchor_horizontal > CCX_DTVCC_SCREENGRID_COLUMNS - col_count {
            anchor_horizontal = CCX_DTVCC_SCREENGRID_COLUMNS - col_count;
        }

        window.priority = priority as i32;
        window.col_lock = col_lock as i32;
        window.row_lock = row_lock as i32;
        window.visible = visible as i32;
        window.anchor_vertical = anchor_vertical as i32;
        window.relative_pos = relative_pos as i32;
        window.anchor_horizontal = anchor_horizontal as i32;
        window.row_count = row_count as i32;
        window.anchor_point = anchor_point as i32;
        window.col_count = col_count as i32;

        // If changing the style of an existing window delete contents
        if win_style > 0 && is_false(window.is_defined) && window.win_style != win_style as i32 {
            do_clear_window = true;
        }

        /* If the window doesn't exist and win style==0 then default to win_style=1 */
        if win_style == 0 && is_false(window.is_defined) {
            win_style = 1;
        }
        /* If the window doesn't exist and pen style==0 then default to pen_style=1 */
        if pen_style == 0 && is_false(window.is_defined) {
            pen_style = 1;
        }

        if win_style > 0 && win_style < 8 {
            let preset = WindowPreset::get_style(win_style);
            if let Ok(preset) = preset {
                window.set_style(preset);
            }
        }

        if pen_style > 0 {
            let preset = PenPreset::get_style(pen_style);
            if let Ok(preset) = preset {
                window.set_pen_style(preset);
            }
            window.pen_style = pen_style as i32;
        }
        if is_false(window.is_defined) {
            // If the window is being created, all character positions in the window
            // are set to the fill color and the pen location is set to (0,0)
            window.pen_column = 0;
            window.pen_row = 0;
            if is_false(window.memory_reserved) {
                for i in 0..CCX_DTVCC_MAX_ROWS as usize {
                    let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize);
                    if let Err(e) = layout {
                        error!("dtvcc_handle_DFx_DefineWindow: Incorrect Layout, {e}");
                    } else {
                        let ptr = unsafe { alloc(layout.unwrap()) };
                        if ptr.is_null() {
                            error!("dtvcc_handle_DFx_DefineWindow: Not enough memory",);
                        }
                        // Exit here?
                        window.rows[i] = ptr as *mut dtvcc_symbol;
                    }
                }
                window.memory_reserved = 1;
            }
            window.is_defined = 1;
            window.clear_text();
        } else if do_clear_window {
            window.clear_text();
        }

        window
            .commands
            .iter_mut()
            .zip(block.iter())
            .for_each(|(command, val)| *command = *val);

        if is_true(window.visible) {
            window.update_time_show(timing);
        }
        if is_false(window.memory_reserved) {
            for i in 0..CCX_DTVCC_MAX_ROWS as usize {
                let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize);
                if let Err(e) = layout {
                    error!("dtvcc_handle_DFx_DefineWindow: Incorrect Layout, {e}");
                } else {
                    unsafe { dealloc(window.rows[i] as *mut u8, layout.unwrap()) };
                }
            }
        }
        // ...also makes the defined windows the current window
        self.handle_set_current_window(window_id);
    }

    /// SPA Set Pen Attributes
    ///
    /// Change pen attributes
    pub fn handle_set_pen_attributes(&mut self, block: &[c_uchar]) {
        if self.current_window == -1 {
            warn!("dtvcc_handle_SPA_SetPenAttributes: Window has to be defined first");
            return;
        }

        let pen_size = (block[0]) & 0x3;
        let offset = (block[0] >> 2) & 0x3;
        let text_tag = (block[0] >> 4) & 0xf;
        let font_tag = (block[1]) & 0x7;
        let edge_type = (block[1] >> 3) & 0x7;
        let underline = (block[1] >> 6) & 0x1;
        let italic = (block[1] >> 7) & 0x1;
        debug!("dtvcc_handle_SPA_SetPenAttributes: attributes: ");
        debug!(
            "Pen size: [{pen_size}]     Offset: [{offset}]  Text tag: [{text_tag}]   Font tag: [{font_tag}]"
        );
        debug!("Edge type: [{edge_type}]  Underline: [{underline}]    Italic: [{italic}]");

        let window = &mut self.windows[self.current_window as usize];
        if window.pen_row == -1 {
            warn!("dtvcc_handle_SPA_SetPenAttributes: cant't set attributes for undefined row");
            return;
        }

        let pen = &mut window.pen_attribs_pattern;
        pen.pen_size = pen_size as i32;
        pen.offset = offset as i32;
        pen.text_tag = text_tag as i32;
        pen.font_tag = font_tag as i32;
        pen.edge_type = edge_type as i32;
        pen.underline = underline as i32;
        pen.italic = italic as i32;
    }

    /// SPC Set Pen Color
    ///
    /// Change pen color
    pub fn handle_set_pen_color(&mut self, block: &[c_uchar]) {
        if self.current_window == -1 {
            warn!("dtvcc_handle_SPC_SetPenColor: Window has to be defined first");
            return;
        }

        let fg_color = (block[0]) & 0x3f;
        let fg_opacity = (block[0] >> 6) & 0x03;
        let bg_color = (block[1]) & 0x3f;
        let bg_opacity = (block[1] >> 6) & 0x03;
        let edge_color = (block[2]) & 0x3f;
        debug!("dtvcc_handle_SPC_SetPenColor: attributes: ");
        debug!("Foreground color: [{fg_color}]     Foreground opacity: [{fg_opacity}]");
        debug!("Background color: [{bg_color}]     Background opacity: [{bg_opacity}]");
        debug!("Edge color: [{edge_color}]");

        let window = &mut self.windows[self.current_window as usize];
        if window.pen_row == -1 {
            warn!("dtvcc_handle_SPC_SetPenColor: cant't set attributes for undefined row");
            return;
        }

        let color = &mut window.pen_color_pattern;
        color.fg_color = fg_color as i32;
        color.fg_opacity = fg_opacity as i32;
        color.bg_color = bg_color as i32;
        color.bg_opacity = bg_opacity as i32;
        color.edge_color = edge_color as i32;
    }

    /// SPL Set Pen Location
    ///
    /// Change pen location
    pub fn handle_set_pen_location(&mut self, block: &[c_uchar]) {
        if self.current_window == -1 {
            warn!("dtvcc_handle_SPL_SetPenLocation: Window has to be defined first");
            return;
        }

        debug!("dtvcc_handle_SPL_SetPenLocation: attributes: ");
        let row = block[0] & 0x0f;
        let col = block[1] & 0x3f;
        debug!("Row: [{row}]     Column: [{col}]");

        let window = &mut self.windows[self.current_window as usize];
        window.pen_row = row as i32;
        window.pen_column = col as i32;
    }

    /// SWA Set Window Attributes
    ///
    /// Change window attributes
    pub fn handle_set_window_attributes(&mut self, block: &[c_uchar]) {
        if self.current_window == -1 {
            warn!("dtvcc_handle_SWA_SetWindowAttributes: Window has to be defined first");
            return;
        }

        let fill_color = (block[0]) & 0x3f;
        let fill_opacity = (block[0] >> 6) & 0x03;
        let border_color = (block[1]) & 0x3f;
        let border_type01 = (block[1] >> 6) & 0x03;
        let justify = (block[2]) & 0x03;
        let scroll_dir = (block[2] >> 2) & 0x03;
        let print_dir = (block[2] >> 4) & 0x03;
        let word_wrap = (block[2] >> 6) & 0x01;
        let border_type = ((block[2] >> 5) & 0x04) | border_type01;
        let display_eff = (block[3]) & 0x03;
        let effect_dir = (block[3] >> 2) & 0x03;
        let effect_speed = (block[3] >> 4) & 0x0f;
        debug!("dtvcc_handle_SWA_SetWindowAttributes: attributes: ");
        debug!(
            "Fill color: [{fill_color}]     Fill opacity: [{fill_opacity}]    Border color: [{border_color}]  Border type: [{border_type}]"
        );
        debug!(
            "Justify: [{justify}]       Scroll dir: [{scroll_dir}]       Print dir: [{print_dir}]    Word wrap: [{word_wrap}]"
        );
        debug!(
            "Border type: [{border_type}]      Display eff: [{display_eff}]      Effect dir: [{effect_dir}] Effect speed: [{effect_speed}]"
        );

        let window_attribts = &mut self.windows[self.current_window as usize].attribs;
        window_attribts.fill_color = fill_color as i32;
        window_attribts.fill_opacity = fill_opacity as i32;
        window_attribts.border_color = border_color as i32;
        window_attribts.justify = justify as i32;
        window_attribts.scroll_direction = scroll_dir as i32;
        window_attribts.print_direction = print_dir as i32;
        window_attribts.word_wrap = word_wrap as i32;
        window_attribts.border_type = border_type as i32;
        window_attribts.display_effect = display_eff as i32;
        window_attribts.effect_direction = effect_dir as i32;
        window_attribts.effect_speed = effect_speed as i32;
    }

    /// CWx Set Current Window
    ///
    /// Change current window to the window id provided
    pub fn handle_set_current_window(&mut self, window_id: u8) {
        debug!("dtvcc_handle_CWx_SetCurrentWindow: [{window_id}]");
        if is_true(self.windows[window_id as usize].is_defined) {
            self.current_window = window_id as i32;
        } else {
            debug!("dtvcc_handle_CWx_SetCurrentWindow: window [{window_id}] is not defined");
        }
    }

    /// DLY Delay
    pub fn handle_delay(&mut self, tenths_of_sec: u8) {
        debug!("dtvcc_handle_DLY_Delay: dely for {tenths_of_sec} tenths of second");
    }

    /// DLC Delay Cancel
    pub fn handle_delay_cancel(&mut self) {
        debug!("dtvcc_handle_DLC_DelayCancel");
    }

    /// RST Reset
    ///
    /// Clear text from all windows and set all windows to undefined and hidden
    pub fn handle_reset(&mut self) {
        for id in 0..CCX_DTVCC_MAX_WINDOWS as usize {
            let window = &mut self.windows[id];
            window.clear_text();
            window.is_defined = 0;
            window.visible = 0;
            window.commands.fill(0);
        }
        self.current_window = -1;
        unsafe { (*self.tv).clear() };
    }

    /// Print the contents of tv screen to the output file
    pub fn screen_print(&mut self, encoder: &mut encoder_ctx, timing: &mut ccx_common_timing_ctx) {
        debug!("dtvcc_screen_print rust");
        self.cc_count += 1;
        unsafe {
            let tv = &mut (*self.tv);
            tv.cc_count += 1;
            let sn = tv.service_number;
            let writer_ctx = &mut encoder.dtvcc_writers[(sn - 1) as usize];
            tv.update_time_hide(timing.get_visible_end(3));
            let transcript_settings = if !encoder.transcript_settings.is_null() {
                &*encoder.transcript_settings
            } else {
                &ccx_encoders_transcript_format::default()
            };
            let end_frame = &encoder.encoded_end_frame[..encoder.encoded_end_frame_length as usize];
            let mut writer = Writer::new(
                &mut encoder.cea_708_counter,
                encoder.subs_delay,
                encoder.write_format,
                writer_ctx,
                encoder.no_font_color,
                transcript_settings,
                encoder.no_bom,
                end_frame,
            );
            tv.writer_output(&mut writer).unwrap();
            tv.clear();
        }
    }

    /// Copy the contents of window to the TV screen
    pub fn copy_to_screen(&self, window: &dtvcc_window) {
        if self.is_window_overlapping(window) {
            debug!("dtvcc_window_copy_to_screen : window needs to be skipped");
            return;
        } else {
            debug!("dtvcc_window_copy_to_screen : no handling required");
        }
        debug!("dtvcc_window_copy_to_screen: W-{}", window.number);
        let anchor = match dtvcc_pen_anchor_point::new(window.anchor_point) {
            Ok(val) => val,
            Err(e) => {
                warn!("{e}");
                return;
            }
        };
        let (mut top, mut left) = match anchor {
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_LEFT => {
                (window.anchor_vertical, window.anchor_horizontal)
            }
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_CENTER => (
                window.anchor_vertical,
                window.anchor_horizontal - window.col_count / 2,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_RIGHT => (
                window.anchor_vertical,
                window.anchor_horizontal - window.col_count,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_LEFT => (
                window.anchor_vertical - window.row_count / 2,
                window.anchor_horizontal,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_CENTER => (
                window.anchor_vertical - window.row_count / 2,
                window.anchor_horizontal - window.col_count / 2,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_RIGHT => (
                window.anchor_vertical - window.row_count / 2,
                window.anchor_horizontal - window.col_count,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_LEFT => (
                window.anchor_vertical - window.row_count,
                window.anchor_horizontal,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_CENTER => (
                window.anchor_vertical - window.row_count,
                window.anchor_horizontal - window.col_count / 2,
            ),
            dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_RIGHT => (
                window.anchor_vertical - window.row_count,
                window.anchor_horizontal - window.col_count,
            ),
        };
        debug!(
            "For window {}: Anchor point -> {}, size {}:{}, real position {}:{}",
            window.number, window.anchor_point, window.row_count, window.col_count, top, left
        );
        debug!("we have top [{top}] and left [{left}]");
        if top < 0 {
            top = 0
        }
        if left < 0 {
            left = 0
        }
        let copy_rows = if top + window.row_count >= CCX_DTVCC_SCREENGRID_ROWS as i32 {
            CCX_DTVCC_SCREENGRID_ROWS as i32 - top
        } else {
            window.row_count
        };
        let copy_cols = if left + window.col_count >= CCX_DTVCC_SCREENGRID_COLUMNS as i32 {
            CCX_DTVCC_SCREENGRID_COLUMNS as i32 - left
        } else {
            window.col_count
        };
        debug!("{copy_rows}*{copy_cols} will be copied to the TV.");

        unsafe {
            let tv = &mut *self.tv;
            for row in 0..copy_rows as usize {
                for col in 0..CCX_DTVCC_SCREENGRID_COLUMNS as usize {
                    if col < copy_cols as usize {
                        tv.chars[top as usize + row][col] = window.rows[row].add(col).read();
                    }
                    tv.pen_attribs[top as usize + row][col] = window.pen_attribs[row][col];
                    tv.pen_colors[top as usize + row][col] = window.pen_colors[row][col];
                }
            }

            tv.update_time_show(window.time_ms_show);
            tv.update_time_hide(window.time_ms_hide);
        }
    }

    /// Returns `true` if the given window is overlapping other windows
    pub fn is_window_overlapping(&self, window: &dtvcc_window) -> bool {
        let mut flag = 0;
        let (a_x1, a_x2, a_y1, a_y2) = match window.get_dimensions() {
            Ok(val) => val,
            Err(e) => {
                warn!("{e}");
                return false;
            }
        };
        for id in 0..CCX_DTVCC_MAX_WINDOWS as usize {
            let win_compare = &self.windows[id];
            let (b_x1, b_x2, b_y1, b_y2) = match win_compare.get_dimensions() {
                Ok(val) => val,
                Err(e) => {
                    warn!("{e}");
                    return false;
                }
            };
            if a_x1 == b_x1 && a_x2 == b_x2 && a_y1 == b_y1 && a_y2 == b_y2 {
                continue;
            } else if (a_x1 < b_x2)
                && (a_x2 > b_x1)
                && (a_y1 < b_y2)
                && (a_y2 > b_y1)
                && is_true(win_compare.visible)
            {
                if win_compare.priority < window.priority {
                    flag = 1
                } else {
                    flag = 0
                }
            }
        }
        flag == 1
    }

    /// Returns `true` if decoder has any visible window
    pub fn has_visible_windows(&self) -> bool {
        for id in 0..CCX_DTVCC_MAX_WINDOWS {
            if is_true(self.windows[id as usize].visible) {
                return true;
            }
        }
        false
    }

    // -------------------------- G0, G1 and extended Commands-------------------------

    /// G0 - Code Set - ASCII printable characters
    pub fn handle_G0(&mut self, block: &[c_uchar]) -> i32 {
        if self.current_window == -1 {
            warn!("dtvcc_handle_G0: Window has to be defined first");
            return 1;
        }

        let character = block[0];
        debug!("G0: [{:2X}] ({})", character, character as char);
        let sym = if character == 0x7F {
            dtvcc_symbol::new(CCX_DTVCC_MUSICAL_NOTE_CHAR)
        } else {
            dtvcc_symbol::new(character as u16)
        };
        self.process_character(sym);
        1
    }

    /// G1 - Code Set - ISO 8859-1 LATIN-1 Character Set
    pub fn handle_G1(&mut self, block: &[c_uchar]) -> i32 {
        if self.current_window == -1 {
            warn!("dtvcc_handle_G1: Window has to be defined first");
            return 1;
        }

        let character = block[0];
        debug!("G1: [{:2X}] ({})", character, character as char);
        let sym = dtvcc_symbol::new(character as u16);
        self.process_character(sym);
        1
    }

    /// Extended codes (EXT1 + code), from the extended sets
    ///
    /// G2 (20-7F) => Mostly unmapped, except for a few characters.
    ///
    /// G3 (A0-FF) => A0 is the CC symbol, everything else reserved for future expansion in EIA708
    ///
    /// C2 (00-1F) => Reserved for future extended misc. control and captions command codes
    ///
    /// C3 (80-9F) => Reserved for future extended misc. control and captions command codes
    ///
    /// WARN: This code is completely untested due to lack of samples. Just following specs!
    /// Returns number of used bytes, usually 1 (since EXT1 is not counted).
    pub fn handle_extended_char(&mut self, block: &[c_uchar]) -> u8 {
        // Bounds check: need at least 1 byte for the code
        if block.is_empty() {
            warn!("dtvcc_handle_extended_char: empty block");
            return 1;
        }

        let code = block[0];
        debug!(
            "dtvcc_handle_extended_char, first data code: [{}], length: [{}]",
            code as char,
            block.len()
        );

        match code {
            0..=0x1F => commands::handle_C2(code),
            0x20..=0x7F => {
                let val = unsafe { dtvcc_get_internal_from_G2(code) };
                let sym = dtvcc_symbol::new(val as u16);
                self.process_character(sym);
                1
            }
            0x80..=0x9F => {
                // C3 commands need at least 2 bytes
                if block.len() < 2 {
                    warn!(
                        "dtvcc_handle_extended_char: C3 command {:02X} requires 2 bytes but only {} available",
                        code,
                        block.len()
                    );
                    return 1;
                }
                commands::handle_C3(code, block[1])
            }
            _ => {
                let val = unsafe { dtvcc_get_internal_from_G3(code) };
                let sym = dtvcc_symbol::new(val as u16);
                self.process_character(sym);
                1
            }
        }
    }

    /// Process the character and add it to the current window
    pub fn process_character(&mut self, sym: dtvcc_symbol) {
        let window = &mut self.windows[self.current_window as usize];
        let window_state = if is_true(window.is_defined) {
            "OK"
        } else {
            "undefined"
        };
        let (mut row, mut column) = (-1, -1);
        if self.current_window != -1 {
            row = window.pen_row;
            column = window.pen_column
        }
        debug!(
            "dtvcc_process_character: [{:04X}] - Window {} [{}], Pen: {}:{}",
            sym.sym, self.current_window, window_state, row, column
        );

        if self.current_window == -1 || is_false(window.is_defined) {
            return;
        }

        window.is_empty = 0;

        // Add symbol to window
        if window.memory_reserved == 1 {
            unsafe {
                let ptr: *mut dtvcc_symbol =
                    window.rows[window.pen_row as usize].add(window.pen_column as usize);
                *ptr = sym;
            }
        } else {
            return;
        }

        // "Painting" char by pen - attribs
        window.pen_attribs[window.pen_row as usize][window.pen_column as usize] =
            window.pen_attribs_pattern;
        // "Painting" char by pen - colors
        window.pen_colors[window.pen_row as usize][window.pen_column as usize] =
            window.pen_color_pattern;

        let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
            Ok(val) => val,
            Err(e) => {
                warn!("{e}");
                return;
            }
        };
        match pd {
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
                if window.pen_column + 1 < window.col_count {
                    window.pen_column += 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
                if window.pen_column > 0 {
                    window.pen_column -= 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
                if window.pen_row + 1 < window.row_count {
                    window.pen_row += 1;
                }
            }
            dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
                if window.pen_row > 0 {
                    window.pen_row -= 1;
                }
            }
        };
    }
    /// Flush the decoder of any remaining subtitles
    pub fn flush(&self, encoder: &mut encoder_ctx) {
        let transcript_settings = unsafe {
            if !encoder.transcript_settings.is_null() {
                &*encoder.transcript_settings
            } else {
                &ccx_encoders_transcript_format::default()
            }
        };
        unsafe {
            let tv = &mut (*self.tv);
            let sn = tv.service_number;
            let writer_ctx = &mut encoder.dtvcc_writers[(sn - 1) as usize];

            let end_frame = &encoder.encoded_end_frame[..encoder.encoded_end_frame_length as usize];
            let mut writer = Writer::new(
                &mut encoder.cea_708_counter,
                encoder.subs_delay,
                encoder.write_format,
                writer_ctx,
                encoder.no_font_color,
                transcript_settings,
                encoder.no_bom,
                end_frame,
            );
            writer.write_done();
        }
    }
}

/// Flush service decoder
///
/// # Safety
///
/// - `dtvcc` must be a valid pointer to a properly initialized `dtvcc_ctx`
/// - `decoder` must be a valid pointer to a `dtvcc_service_decoder`
/// - `dtvcc.timing` must be a valid pointer if `dtvcc` is non-null
/// - `dtvcc.encoder` must be a valid pointer or null
#[no_mangle]
extern "C" fn ccxr_flush_decoder(dtvcc: *mut dtvcc_ctx, decoder: *mut dtvcc_service_decoder) {
    debug!("dtvcc_decoder_flush: Flushing decoder");
    let timing = unsafe { &mut *((*dtvcc).timing) };
    let mut encoder = Box::into_raw(Box::new(encoder_ctx::default()));
    unsafe {
        if !(*dtvcc).encoder.is_null() {
            encoder = &mut *((*dtvcc).encoder as *mut encoder_ctx);
        }
    }
    let decoder = unsafe { &mut *decoder };

    let mut screen_content_changed = false;
    for i in 0..CCX_DTVCC_MAX_WINDOWS {
        let window = &mut decoder.windows[i as usize];
        if is_true(window.visible) {
            screen_content_changed = true;
            window.update_time_hide(timing);
            decoder.copy_to_screen(&decoder.windows[i as usize]);
            decoder.windows[i as usize].visible = 0
        }
    }
    unsafe {
        let encoder_ctx = &mut *encoder;
        if screen_content_changed {
            decoder.screen_print(encoder_ctx, timing);
        }
        decoder.flush(encoder_ctx);
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::utils::get_zero_allocated_obj;
    use std::alloc::{alloc_zeroed, dealloc, Layout};

    fn setup_test_decoder_with_memory() -> dtvcc_service_decoder {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();

        decoder.current_window = 0;
        decoder.windows[0].is_defined = 1;
        decoder.windows[0].row_count = 4;
        decoder.windows[0].col_count = 4;
        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32;

        let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize).unwrap();
        decoder.windows[0].rows[0] = unsafe { alloc(layout) } as *mut dtvcc_symbol;
        decoder.windows[0].memory_reserved = 1;

        *decoder
    }

    fn cleanup_test_decoder(decoder: &mut dtvcc_service_decoder) {
        let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize).unwrap();
        unsafe {
            dealloc(decoder.windows[0].rows[0] as *mut u8, layout);
        }
    }

    // -------------------------- C0 Commands-------------------------
    #[test]
    fn test_process_cr() {
        let set_tmp_values = |window: &mut dtvcc_window| {
            window.is_defined = 1;
            window.visible = 1;
            window.row_count = 5;
            window.col_count = 10;
            window.pen_row = 2;
            window.pen_column = 5;
            window.is_empty = 0;
        };

        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        decoder.current_window = 0;
        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32;

        let mut encoder = encoder_ctx::default();
        let mut timing = ccx_common_timing_ctx::default();
        let no_rollup = false;

        // Set temp values of window
        set_tmp_values(&mut decoder.windows[0]);

        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32;
        decoder.process_cr(&mut encoder, &mut timing, no_rollup);
        assert_eq!(decoder.windows[0].pen_row, 3);
        assert_eq!(decoder.windows[0].pen_column, 0);

        // Set temp values of window
        set_tmp_values(&mut decoder.windows[0]);

        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT as i32;
        decoder.process_cr(&mut encoder, &mut timing, no_rollup);
        assert_eq!(decoder.windows[0].pen_row, 3);
        assert_eq!(decoder.windows[0].pen_column, 10);

        // Set temp values of window
        set_tmp_values(&mut decoder.windows[0]);

        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM as i32;
        decoder.process_cr(&mut encoder, &mut timing, no_rollup);
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 6);

        // Set temp values of window
        set_tmp_values(&mut decoder.windows[0]);

        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP as i32;
        decoder.process_cr(&mut encoder, &mut timing, no_rollup);
        assert_eq!(decoder.windows[0].pen_row, 5);
        assert_eq!(decoder.windows[0].pen_column, 6);
    }

    #[test]
    fn test_process_hcr() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        decoder.current_window = 1;
        decoder.windows[1].pen_column = 12;
        decoder.windows[1].pen_row = 1;
        let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize).unwrap();
        for i in 0..CCX_DTVCC_MAX_ROWS as usize {
            decoder.windows[1].rows[i] = unsafe { alloc_zeroed(layout) } as *mut dtvcc_symbol;
        }
        decoder.windows[1].memory_reserved = 1;

        unsafe {
            *decoder.windows[1].rows[1] = dtvcc_symbol::new(1);
            *decoder.windows[1].rows[2] = dtvcc_symbol::new(1);
        }

        decoder.process_hcr();

        assert_eq!(decoder.windows[1].pen_column, 0);

        // Ensuring, it erases all text on the row mentioned by `pen_row`
        assert_eq!(
            unsafe { decoder.windows[1].rows[1].as_mut() },
            Some(&mut dtvcc_symbol::default()),
        );
        // Do not clear text for row which is not mentioned by `pen_row`
        assert_eq!(
            unsafe { decoder.windows[1].rows[2].as_mut() },
            Some(&mut dtvcc_symbol { sym: 1, init: 1 }),
        );

        // Cleanup
        for i in 0..CCX_DTVCC_MAX_ROWS as usize {
            unsafe {
                dealloc(decoder.windows[1].rows[i] as *mut u8, layout);
            }
        }
    }

    #[test]
    fn test_process_ff() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        decoder.current_window = 1;
        decoder.windows[1].pen_column = 2;
        decoder.windows[1].pen_row = 1;
        decoder.windows[1].memory_reserved = 1;
        let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize).unwrap();
        for i in 0..CCX_DTVCC_MAX_ROWS as usize {
            decoder.windows[1].rows[i] = unsafe { alloc_zeroed(layout) } as *mut dtvcc_symbol;
        }
        decoder.windows[1].memory_reserved = 1;

        unsafe {
            *decoder.windows[1].rows[1] = dtvcc_symbol::new(1);
            *decoder.windows[1].rows[2] = dtvcc_symbol::new(1);
        }

        decoder.process_ff();

        assert_eq!(decoder.windows[1].pen_column, 0);
        assert_eq!(decoder.windows[1].pen_row, 0);

        // Ensuring, it erases all text on the rows
        // (Doesn't matter for value of pen_column or pen_row..Just delete all text present)
        assert_eq!(
            unsafe { decoder.windows[1].rows[1].as_mut() },
            Some(&mut dtvcc_symbol::default()),
        );
        assert_eq!(
            unsafe { decoder.windows[1].rows[2].as_mut() },
            Some(&mut dtvcc_symbol::default()),
        );

        // Cleanup
        for i in 0..CCX_DTVCC_MAX_ROWS as usize {
            unsafe {
                dealloc(decoder.windows[1].rows[i] as *mut u8, layout);
            }
        }
    }

    #[test]
    fn test_process_bs() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        decoder.current_window = 1;

        // 0 -> dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT
        decoder.windows[1].attribs.print_direction = 0;
        decoder.windows[1].pen_column = 3; // > 0

        decoder.process_bs();
        assert_eq!(decoder.windows[1].pen_column, 2);

        // 1 -> dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT
        decoder.windows[1].attribs.print_direction = 1;
        decoder.windows[1].pen_column = 3;
        decoder.windows[1].col_count = 5;

        decoder.process_bs();
        assert_eq!(decoder.windows[1].pen_column, 4);

        // 2 -> dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM
        decoder.windows[1].attribs.print_direction = 2;
        decoder.windows[1].pen_row = 3;

        decoder.process_bs();
        assert_eq!(decoder.windows[1].pen_row, 2);

        // 3 -> dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP
        decoder.windows[1].attribs.print_direction = 3;
        decoder.windows[1].pen_row = 3;
        decoder.windows[1].row_count = 5;

        decoder.process_bs();
        assert_eq!(decoder.windows[1].pen_column, 4);

        // 4..infinite -> Invalid print direction
        decoder.windows[1].attribs.print_direction = 4;
    }

    #[test]
    fn test_process_p16() {
        let mut decoder = setup_test_decoder_with_memory();
        let block = [b'a', b'b'] as [c_uchar; 2];

        decoder.process_p16(&block);

        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 1);
        unsafe {
            assert_eq!(
                *decoder.windows[0].rows[0].add(0),
                dtvcc_symbol::new_16(block[0], block[1])
            );
        }

        cleanup_test_decoder(&mut decoder);
    }

    // -------------------------- C1 Commands-------------------------
    #[test]
    fn test_handle_display_windows() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let mut timing = ccx_common_timing_ctx::default();

        decoder.windows[0].is_defined = 1;
        decoder.windows[2].is_defined = 1;
        decoder.windows[2].visible = 1; // Window 2 is already visible

        // Test case 1: Display all windows
        let windows_bitmap = 0b00000111;
        decoder.handle_display_windows(windows_bitmap, &mut timing);

        assert_eq!(decoder.windows[0].visible, 1);
        assert_eq!(decoder.windows[1].visible, 0);
        assert_eq!(decoder.windows[2].visible, 1);

        // Test case 2: Do nothing with the windows
        let windows_bitmap = 0b00000000;
        decoder.windows[1].is_defined = 1;
        decoder.handle_display_windows(windows_bitmap, &mut timing);

        assert_eq!(decoder.windows[0].visible, 1);
        // Even after window is defined & `windows_bitmap = 0` => it is not visible
        assert_eq!(decoder.windows[1].visible, 0);
        assert_eq!(decoder.windows[2].visible, 1);
    }

    #[test]
    fn test_handle_define_windows() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let mut timing = ccx_common_timing_ctx::default();
        let window_id = 1;
        let test_block = [
            0b00000111, 0b01000000, 0b00000010, 0b00001010, 0b00111111, 0b00001000,
        ];

        decoder.handle_define_windows(window_id, &test_block, &mut timing);

        let window = &decoder.windows[window_id as usize];

        assert_eq!(window.number, window_id as i32);
        assert_eq!(window.priority, 0x7);
        assert_eq!(window.col_lock, 0x0);
        assert_eq!(window.row_lock, 0x0);
        assert_eq!(window.visible, 0x0);
        assert_eq!(window.anchor_vertical, 64);
        assert_eq!(window.relative_pos, 0x0);
        assert_eq!(window.anchor_horizontal, 0x2);
        assert_eq!(window.row_count, 0xb);
        assert_eq!(window.anchor_point, 0x0);
        assert_eq!(window.col_count, 64);
        assert_eq!(window.pen_style, 0x1);
        assert_eq!(window.win_style, 0x1);
        assert_eq!(window.pen_row, 0x0);
        assert_eq!(window.pen_column, 0x0);
        assert_eq!(window.is_defined, 1);
        assert_eq!(window.memory_reserved, 1);

        // Check `Command` has been set or not
        assert_eq!(window.commands[0], 0b00000111);
        assert_eq!(window.commands[1], 0b01000000);
        assert_eq!(window.commands[2], 0b00000010);
        assert_eq!(window.commands[3], 0b00001010);
        assert_eq!(window.commands[4], 0b00111111);
        assert_eq!(window.commands[5], 0b00001000);
    }

    #[test]
    fn test_handle_set_pen_attributes() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let test_block = [0b00111000, 0b11010001];

        decoder.current_window = 0;
        decoder.windows[0].pen_row = 0;

        decoder.handle_set_pen_attributes(&test_block);

        let pen = &decoder.windows[0].pen_attribs_pattern;
        assert_eq!(pen.pen_size, 0x0);
        assert_eq!(pen.offset, 0x2);
        assert_eq!(pen.text_tag, 0x3);
        assert_eq!(pen.font_tag, 0x1);
        assert_eq!(pen.edge_type, 0x2);
        assert_eq!(pen.underline, 0x1);
        assert_eq!(pen.italic, 0x1);
    }

    #[test]
    fn test_handle_set_pen_color() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let test_block = [0b00111111, 0b00111110, 0b00111100];

        decoder.current_window = 0;
        decoder.windows[0].pen_row = 0;

        decoder.handle_set_pen_color(&test_block);

        let color = &decoder.windows[0].pen_color_pattern;
        assert_eq!(color.fg_color, 0x3f);
        assert_eq!(color.fg_opacity, 0x0);
        assert_eq!(color.bg_color, 0x3e);
        assert_eq!(color.bg_opacity, 0x0);
        assert_eq!(color.edge_color, 0x3c);
    }

    #[test]
    fn test_handle_set_pen_location() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let test_block = [0b00001111, 0b00111111];
        decoder.current_window = 0;

        decoder.handle_set_pen_location(&test_block);

        assert_eq!(decoder.windows[0].pen_row, 0xf);
        assert_eq!(decoder.windows[0].pen_column, 0x3f);
    }

    #[test]
    fn test_handle_set_window_attributes() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        decoder.current_window = 0;
        let test_block = [
            0b00111111, // fill_color, fill_opacity
            0b00111110, // border_color, border_type01
            0b01010101, // justify, scroll_dir, print_dir, word_wrap, border_type
            0b01010101, // display_eff, effect_dir, effect_speed
        ];

        decoder.handle_set_window_attributes(&test_block);

        let window_attribs = &decoder.windows[0].attribs;

        assert_eq!(window_attribs.fill_color, 0x3f);
        assert_eq!(window_attribs.fill_opacity, 0x0);
        assert_eq!(window_attribs.border_color, 0x3e);
        assert_eq!(window_attribs.border_type, 0x0);
        assert_eq!(window_attribs.justify, 0x1);
        assert_eq!(window_attribs.scroll_direction, 0x1);
        assert_eq!(window_attribs.print_direction, 0x1);
        assert_eq!(window_attribs.word_wrap, 0x1);
        assert_eq!(window_attribs.display_effect, 0x1);
        assert_eq!(window_attribs.effect_direction, 0x1);
        assert_eq!(window_attribs.effect_speed, 0x5);
    }

    #[test]
    fn test_handle_set_current_window() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();

        let window_id = 2;
        decoder.windows[window_id].is_defined = 1;
        decoder.current_window = 5;

        decoder.handle_set_current_window(window_id as u8);

        assert_eq!(decoder.current_window, window_id as i32);
    }

    #[test]
    fn test_handle_reset() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();

        // Set random values in decoder
        decoder.current_window = 0;
        decoder.windows.iter_mut().for_each(|window| {
            window.visible = 1;
            window.is_defined = 1;
            window.visible = 1;
            window.commands.fill(1);
        });
        decoder.tv = Box::into_raw(get_zero_allocated_obj::<dtvcc_tv_screen>());

        decoder.handle_reset();

        // Test if reset perfectly works or not?
        assert_eq!(decoder.current_window, -1);
        decoder.windows.iter_mut().for_each(|window| {
            assert_eq!(window.visible, 0);
            assert_eq!(window.is_defined, 0);
            assert!(window.commands.iter().all(|&c| c == 0));
        });
    }

    #[test]
    fn test_is_window_overlapping() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();

        // Non-overlapping windows
        decoder.windows[0].is_defined = 1;
        decoder.windows[0].visible = 1;
        decoder.windows[0].anchor_vertical = 2;
        decoder.windows[0].anchor_horizontal = 2;
        decoder.windows[0].row_count = 5;
        decoder.windows[0].col_count = 10;
        assert!(!decoder.is_window_overlapping(&decoder.windows[0]));

        decoder.windows[1].is_defined = 1;
        decoder.windows[1].visible = 1;
        decoder.windows[1].anchor_vertical = 10;
        decoder.windows[1].anchor_horizontal = 10;
        decoder.windows[1].row_count = 3;
        decoder.windows[1].col_count = 5;
        assert!(!decoder.is_window_overlapping(&decoder.windows[1]));

        // Overlapping windows
        decoder.windows[2].is_defined = 1;
        decoder.windows[2].visible = 1;
        decoder.windows[2].anchor_vertical = 3;
        decoder.windows[2].anchor_horizontal = 3;
        decoder.windows[2].row_count = 3;
        decoder.windows[2].col_count = 5;
        decoder.windows[2].priority = 1;
        assert!(decoder.is_window_overlapping(&decoder.windows[2]));

        decoder.windows[3].is_defined = 1;
        decoder.windows[3].visible = 1;
        decoder.windows[3].anchor_vertical = 4;
        decoder.windows[3].anchor_horizontal = 4;
        decoder.windows[3].row_count = 3;
        decoder.windows[3].col_count = 5;
        decoder.windows[3].priority = 2;
        assert!(decoder.is_window_overlapping(&decoder.windows[3]));
    }

    #[test]
    fn test_has_visible_windows() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();

        // Default case - No windows is visible
        assert!(!decoder.has_visible_windows());

        // Make 1 window visible
        decoder.windows[0].visible = 1;
        assert!(decoder.has_visible_windows());
    }

    // -------------------------- G0, G1 and extended Commands-------------------------
    #[test]
    fn test_handle_G0() {
        let mut decoder = setup_test_decoder_with_memory();

        // Case: block[0] == 0x7F
        let block = [0x7F, 0x61];
        decoder.handle_G0(&block);

        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 1);
        unsafe {
            assert_eq!(
                decoder.windows[0].rows[0].add(0).read().sym,
                CCX_DTVCC_MUSICAL_NOTE_CHAR
            );
        }

        // Case: block[0] != 0x7F
        let block = [0x60, 0x61];
        let return_value = decoder.handle_G0(&block);

        assert_eq!(return_value, 1);
        unsafe {
            assert_eq!(decoder.windows[0].rows[0].add(1).read().sym, 96);
        }

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_handle_G1() {
        let mut decoder = setup_test_decoder_with_memory();

        let block = [0x7F, 0x61];
        let return_value = decoder.handle_G1(&block);

        assert_eq!(return_value, 1);
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 1);
        unsafe {
            assert_eq!(decoder.windows[0].rows[0].add(0).read().sym, 0x7F);
        }

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_handle_extended_char() {
        let mut decoder = setup_test_decoder_with_memory();

        // 0..=0x1F
        let return_value = decoder.handle_extended_char(&[0x1A, 0x61]);
        assert_eq!(return_value, 4);

        // 0x20..=0x7F
        let return_value = decoder.handle_extended_char(&[0x25, 0x61]);
        assert_eq!(return_value, 1);
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 1);
        unsafe {
            assert_eq!(decoder.windows[0].rows[0].add(0).read().sym, 0x5);
        }

        // 0x80..=0x9F
        let return_value = decoder.handle_extended_char(&[0x86, 0x61]);
        assert_eq!(return_value, 5);

        // Anyother value > 0x9F (59)
        let return_value = decoder.handle_extended_char(&[65, 0x61]);
        assert_eq!(return_value, 1);
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 2);
        unsafe {
            assert_eq!(decoder.windows[0].rows[0].add(1).read().sym, 0x20);
        }

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_handle_extended_char_empty_block() {
        // Test that empty block doesn't panic (bounds check fix for issue #1616)
        let mut decoder = setup_test_decoder_with_memory();

        // Empty block should return 1 and not panic
        let return_value = decoder.handle_extended_char(&[]);
        assert_eq!(return_value, 1);

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_handle_extended_char_c3_insufficient_bytes() {
        // Test that C3 command (0x80-0x9F) with only 1 byte doesn't panic (issue #1616)
        let mut decoder = setup_test_decoder_with_memory();

        // C3 commands need 2 bytes, but we only provide 1
        // This should not panic, just return 1 and log a warning
        let return_value = decoder.handle_extended_char(&[0x80]); // C3 code with no second byte
        assert_eq!(return_value, 1);

        let return_value = decoder.handle_extended_char(&[0x90]); // Another C3 code
        assert_eq!(return_value, 1);

        let return_value = decoder.handle_extended_char(&[0x9F]); // Edge of C3 range
        assert_eq!(return_value, 1);

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_process_service_block_ext1_at_end() {
        // Test that EXT1 (0x10) at end of block doesn't panic (issue #1616)
        let mut decoder = setup_test_decoder_with_memory();
        let mut encoder = encoder_ctx::default();
        let mut timing = ccx_common_timing_ctx::default();

        // Block with EXT1 at the end with no following data
        // EXT1 = 0x10 = DTVCC_COMMANDS_C0_CODES_DTVCC_C0_EXT1
        let block = [0x10_u8]; // Just EXT1, no following byte

        // This should not panic, just return early
        decoder.process_service_block(&block, &mut encoder, &mut timing, false);

        // Block with some valid data followed by EXT1 at the end
        let block_with_ext1_at_end = [0x20_u8, 0x10]; // G0 char, then EXT1 with no following

        decoder.process_service_block(&block_with_ext1_at_end, &mut encoder, &mut timing, false);

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_process_service_block_ext1_with_truncated_c3() {
        // Test EXT1 followed by C3 command with insufficient bytes (issue #1616)
        let mut decoder = setup_test_decoder_with_memory();
        let mut encoder = encoder_ctx::default();
        let mut timing = ccx_common_timing_ctx::default();

        // EXT1 (0x10) followed by C3 code (0x80-0x9F) but no second byte for C3
        let block = [0x10_u8, 0x85]; // EXT1 + C3 code, but C3 needs another byte

        // This should not panic
        decoder.process_service_block(&block, &mut encoder, &mut timing, false);

        cleanup_test_decoder(&mut decoder);
    }

    #[test]
    fn test_process_character() {
        let mut decoder = get_zero_allocated_obj::<dtvcc_service_decoder>();
        let sym = dtvcc_symbol::new(0x41);

        // Undefined window case
        decoder.windows[0].is_defined = 0;
        decoder.process_character(sym);

        // No changes occurred
        assert!(decoder.windows[0].rows[0].is_null());
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 0);

        // Valid window case
        decoder.current_window = 0;
        decoder.windows[0].is_defined = 1;
        decoder.windows[0].row_count = 4;
        decoder.windows[0].col_count = 4;
        decoder.windows[0].attribs.print_direction =
            dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32;

        let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize).unwrap();
        decoder.windows[0].rows[0] = unsafe { alloc(layout) } as *mut dtvcc_symbol;
        decoder.windows[0].memory_reserved = 1;

        decoder.process_character(sym);

        // Check changes
        assert_eq!(decoder.windows[0].pen_row, 0);
        assert_eq!(decoder.windows[0].pen_column, 1);
        unsafe {
            assert_eq!(
                decoder.windows[0].rows[0].add(0).read(),
                dtvcc_symbol::new(0x41)
            );
        }
    }
}
