use crate::err_str;

// Implementation of the block builtin.
use super::prelude::*;

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum Scope {
    #[default]
    Unset,
    Global,
    Local,
}

#[derive(Debug, Clone, Copy, Default)]
struct Options {
    scope: Scope,
    erase: bool,
    print_help: bool,
}

fn parse_options(
    args: &mut [&wstr],
    parser: &Parser,
    streams: &mut IoStreams,
) -> Result<(Options, usize), ErrorCode> {
    let cmd = args[0];

    const SHORT_OPTS: &wstr = L!("eghl");
    const LONG_OPTS: &[WOption] = &[
        wopt(L!("erase"), ArgType::NoArgument, 'e'),
        wopt(L!("local"), ArgType::NoArgument, 'l'),
        wopt(L!("global"), ArgType::NoArgument, 'g'),
        wopt(L!("help"), ArgType::NoArgument, 'h'),
    ];

    let mut opts = Options::default();

    let mut w = WGetopter::new(SHORT_OPTS, LONG_OPTS, args);
    while let Some(c) = w.next_opt() {
        match c {
            'h' => {
                opts.print_help = true;
            }
            'g' => {
                opts.scope = Scope::Global;
            }
            'l' => {
                opts.scope = Scope::Local;
            }
            'e' => {
                opts.erase = true;
            }
            ':' => {
                builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
                return Err(STATUS_INVALID_ARGS);
            }
            ';' => {
                builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
                return Err(STATUS_INVALID_ARGS);
            }
            '?' => {
                builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
                return Err(STATUS_INVALID_ARGS);
            }
            _ => {
                panic!("unexpected retval from WGetopter");
            }
        }
    }

    Ok((opts, w.wopt_index))
}

/// The block builtin, used for temporarily blocking events.
pub fn block(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
    let cmd = args[0];

    let (opts, _) = parse_options(args, parser, streams)?;

    if opts.print_help {
        builtin_print_help(parser, streams, cmd);
        return Ok(SUCCESS);
    }

    if opts.erase {
        if opts.scope != Scope::Unset {
            err_str!("Can not specify scope when removing block")
                .cmd(cmd)
                .finish(streams);
            return Err(STATUS_INVALID_ARGS);
        }

        if parser.global_event_blocks == 0 {
            err_str!("No blocks defined").cmd(cmd).finish(streams);
            return Err(STATUS_CMD_ERROR);
        }
        parser.global_event_blocks -= 1;
        return Ok(SUCCESS);
    }

    let mut block_idx = 0;
    let have_block = {
        let mut block = parser.block_at_index(block_idx);

        match opts.scope {
            Scope::Local => {
                // If this is the outermost block, then we're global
                if block_idx + 1 >= parser.blocks_size() {
                    block = None;
                }
            }
            Scope::Global => {
                block = None;
            }
            Scope::Unset => {
                loop {
                    block = if let Some(block) = block.as_mut() {
                        if !block.is_function_call() {
                            break;
                        }
                        // Set it in function scope
                        block_idx += 1;
                        parser.block_at_index(block_idx)
                    } else {
                        break;
                    }
                }
            }
        }
        block.is_some()
    };

    if have_block {
        parser.block_at_index_mut(block_idx).unwrap().event_blocks |= true;
    } else {
        parser.global_event_blocks += 1;
    }

    Ok(SUCCESS)
}
