Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Dia-Args

Copyright (C) 2018-2019, 2021-2025  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2018-2019".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Kind

use {
    std::io::{Error, ErrorKind},
    crate::Result,
    super::DASH,
};

/// # Kind
#[derive(Debug, Eq, PartialEq)]
pub (super) enum Kind {
    Command,
    ShortOption,
    ShortOptionWithValue {
        option: String,
        value: String,
    },
    LongOption,
    LongOptionWithValue {
        option: String,
        value: String,
    },
    SubArgsSeparator,
}

impl Kind {

    /// # Parses argument kind
    pub fn parse(s: &str) -> Result<Self> {
        let mut chars = s.chars().peekable();
        if chars.next() == Some(DASH) {
            if chars.next_if_eq(&DASH).is_some() {
                if chars.peek().is_none() {
                    return Ok(Self::SubArgsSeparator);
                }
                return parse_long_options(&mut chars).ok_or_else(|| Error::new(ErrorKind::InvalidData, format!("Invalid long option: {s:?}")));
            }
            return parse_short_options(&mut chars).ok_or_else(|| Error::new(ErrorKind::InvalidData, format!("Invalid short option: {s:?}")));
        }

        Ok(Self::Command)
    }

}

fn is_option_char(c: char) -> bool {
    matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9')
}

/// # Parses long options
///
/// 2 leading characters are expected to be verified by caller.
fn parse_long_options<I>(chars: &mut I) -> Option<Kind> where I: Iterator<Item=char> {
    let mut option = String::with_capacity(chars.size_hint().0);

    match chars.next() {
        Some(c) if is_option_char(c) => option.push(c),
        _ => return None,
    };

    while let Some(c) = chars.next() {
        if is_option_char(c) {
            option.push(c);
            continue;
        }
        match c {
            '-' => {
                option.push(c);
                continue;
            },
            '=' => {
                option.insert(usize::MIN, DASH);
                option.insert(usize::MIN, DASH);
                return Some(Kind::LongOptionWithValue {
                    option,
                    value: chars.collect(),
                });
            },
            _ => return None,
        };
    }

    Some(Kind::LongOption)
}

/// # Parses short options
///
/// The first leading character is expected to be verified by caller.
fn parse_short_options<I>(chars: &mut I) -> Option<Kind> where I: Iterator<Item=char> {
    match chars.next() {
        Some(c) if is_option_char(c) => match chars.next() {
            None => Some(Kind::ShortOption),
            Some('=') => Some(Kind::ShortOptionWithValue {
                option: [DASH, c].into_iter().collect(),
                value: chars.collect(),
            }),
            Some(_) => None,
        },
        _ => None,
    }
}