/*-
 * Scram-rs - a SCRAM authentification authorization library
 * 
 * Copyright (C) 2021  Aleksandr Morozov
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

#[cfg(feature = "std")]
use std::str::Split;
#[cfg(not(feature = "std"))]
use core::str::Split;

#[cfg(feature = "std")]
use std::fmt;
#[cfg(not(feature = "std"))]
use core::fmt;

#[cfg(feature = "std")]
use std::str;
#[cfg(not(feature = "std"))]
use core::str;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

#[cfg(not(feature = "std"))]
use alloc::string::String;

#[cfg(not(feature = "std"))]
use crate::alloc::string::ToString;

#[cfg(not(feature = "std"))]
use alloc::format;

#[cfg(feature = "std")]
use std::slice;
#[cfg(not(feature = "std"))]
use core::slice;

use base64::Engine;
use base64::engine::general_purpose;

use crate::ScramCommon;
use crate::ScramRuntimeError;
use crate::ScramServerError;

use super::scram_error::{ScramResult, ScramErrorCode};
use super::{scram_error, scram_error_map};
use super::scram_cb::ChannelBindType;
use super::scram_state::ScramState;

/// A type of the data with which it should comply.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ScramParserItemValidation
{
    /// Unsigned positive
    UnsignedPositive,

    /// %x21-2B / %x2D-7E
    /// Printable ASCII except ",". Note that any "printable" is also a valid "value".
    Printable,

    /// %x01-2B / %x2D-3C / %x3E-7F /
    /// UTF8-2 / UTF8-3 / UTF8-4
    /// UTF8-char except NUL, "=", and ",".
    ValueSafeChar,

    /// 1*(ALPHA / DIGIT / "." / "-")
    /// See RFC 5056, Section 7. E.g., "tls-server-end-point" or "tls-unique".
    CbName, 
}

impl ScramParserItemValidation
{
    fn validate(&self, data: &str) -> Result<(), String>
    {
        match self
        {
            Self::UnsignedPositive =>
                return Self::check_value_positive(data),
            Self::Printable => 
                return Self::check_printable(data),
            Self::ValueSafeChar => 
                return Self::check_value_safe_char(data),
            Self::CbName => 
                return Self::check_cb_name(data),
        }
    }

    fn check_cb_name(a: &str) -> Result<(), String>
    {
        if a.is_empty() == true
        {
            return Err(format!("empty value in SCRAM message"));
        }

        if a.chars().any(|p| p.is_ascii() == false || p.is_ascii_control() && p != '.' && p != '-') == true
        {
            return Err(format!("non cbname valid value '{}' in SCRAM message", ScramCommon::sanitize_str_unicode(a)));
        }

        return Ok(());
    }

    fn check_value_positive(a: &str) -> Result<(), String>
    {
        if a.is_empty() == true
        {
            return Err(format!("empty value in SCRAM message"));
        }

        for (ord, p) in a.chars().enumerate()
        {
            if p.is_ascii_digit() == false
            {
                return Err(format!("non digit character: '{}' in SCRAM message", ScramCommon::sanitize_str_unicode(a)));
            }

            if ord == 0 && p == '0'
            {
                return Err(format!("non valid integer in SCRAM message"));
            }
        }

        return Ok(());
    }

    fn check_printable(a: &str) -> Result<(), String>
    {
        if a.is_empty() == true
        {
            return Err(format!("empty value in SCRAM message"));
        }

        for p in a.chars()
        {
            // p < 0x21 || p > 0x7E
            if p.is_ascii_graphic() == false || p.is_ascii() == false || p == ','
            {
                return Err(format!("non printable characters '{}' in SCRAM message", ScramCommon::sanitize_str_unicode(a)));
            }
        }

        return Ok(());
    }

    /// Checks if any character that can be printed except control chars, whitespace, UTF8-char except NUL, "=", and ","
    /// %x01-2B / %x2D-3C / %x3E-7F /
    /// UTF8-2 / UTF8-3 / UTF8-4
    /// UTF8-char except NUL, "=", and ",".
    fn check_value_safe_char(u: &str) -> Result<(), String>
    {
        if u.is_empty() == true
        {
            return Err(format!("empty value in SCRAM message"));
        }
        
        for p in u.chars()
        {
            // any character that can be printed except control chars
            if p.is_control() == true || p.is_ascii_whitespace() == true || p.is_whitespace() == true || 
                p.is_ascii_control() == true || p == '=' || p == ',' || p == '\0'
            {
                return Err(format!("non safe-value-char characters '{}' in SCRAM message", ScramCommon::sanitize_str_unicode(u)));
            }
        }

        return Ok(());
    }
}

/// Indicates which item should can present, must present or not supported.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ScramParserItemPres
{
    /// Throws error immidiatly
    NotSupported,

    /// Can be skipped if not present, but report the missing if i.e v=1,,e=example
    MayNotPresent,

    /// Must present
    MustPresent,
}

impl fmt::Display for ScramParserItemPres
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        match self
        {
            Self::NotSupported => 
                write!(f, "ExtNotSupported"),
            Self::MayNotPresent => 
                write!(f, "MayNotPresent"),
            Self::MustPresent => 
                write!(f, "MustPresent"),
        }
    }
}

/// Sets if this is a variable or flag.
#[derive(Clone, Debug, PartialEq, Eq)]
enum ScramParserItem
{
    /// A flag which does not have a value after =
    ItemNoVal(&'static str),

    /// A var=val pair.
    ItemVal(&'static str, ScramParserItemValidation)
}

impl fmt::Display for ScramParserItem
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        match self
        {
            Self::ItemNoVal(v) => 
                write!(f, "{}", v),
            Self::ItemVal(v, _) =>
                write!(f, "{}=", v),
        }
    }
}

impl PartialEq<str> for ScramParserItem
{
    fn eq(&self, other: &str) -> bool 
    {
        match self
        {
            Self::ItemNoVal(v) => 
                return *v == other,
            Self::ItemVal(v, _) => 
                return *v == other
        }
    }
}

impl ScramParserItem
{
    fn validate(&self, data: &str) -> Result<(), String>
    {
        match self
        {
            Self::ItemNoVal(_) => 
            {
                if data.is_empty() == false
                {
                    return Err(format!("the value part is not expected"))
                }
            },
            Self::ItemVal(_, scram_parser_item_validation) => 
            {
                return scram_parser_item_validation.validate(data);
            },
        }

        return Ok(());
    }

    fn is_noval(&self) -> bool
    {
        let Self::ItemNoVal(_) = self
            else { return false };

        return true;
    }
}

/// Message item.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ScramParserMsg
{
    /// A static title (for debug purposes)
    title: &'static str,

    /// An identification of the current item.
    msg_type: ScramParserMsgType,

    /// A presence.
    presence: ScramParserItemPres,

    /// A variants (OR list).
    items: &'static [ScramParserItem],
}

impl fmt::Display for ScramParserMsg
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "'{}, {}, declared options: {}'", self.title, self.presence, 
            self.items.iter().map(|v| v.to_string()).collect::<Vec<String>>().join(","))
    }
}

impl ScramParserMsg
{
    fn get_item(&self, var: &str) -> Option<&'static ScramParserItem> 
    {
        for item in self.items.iter()
        {
            if item == var
            {
                return Some(item);
            }
        }

        return None;
    }
}

// https://datatracker.ietf.org/doc/html/rfc5802

/// A description of the rule for specific message.
#[derive(Clone, Debug, PartialEq, Eq)]
struct ScramParserRule
{
    /// A title for debug purposes.
    title: &'static str,

    /// Items in the message.
    items: &'static [ScramParserMsg],
}

impl fmt::Display for ScramParserRule
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "{}", self.title)
    }
}

impl ScramParserRule
{
    const SCRAM_CLIENT_INIT_MSG: usize = 0;
    const SCRAM_SERVER_INIT_MSG: usize = 1;
    const SCRAM_CLIENT_FINAL_MSG: usize = 2;
    const SCRAM_SERVER_FINAL_MSG: usize = 3;
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScramParserMsgType
{
    Gs2CbindFlag,
    Authzid,
    ResercedMext,
    Username, 
    CNonce,
    SNonce,
    IterationCount,
    Error,
    Salt, 
    ChannelBinding,
    Proof,
    Verifier
}

impl fmt::Display for ScramParserMsgType
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        match self
        {
            Self::Gs2CbindFlag => 
                write!(f, "gs2-cbind-flag"),
            Self::Authzid => 
                write!(f, "authzid"),
            Self::ResercedMext => 
                write!(f, "reserved-mext"),
            Self::Username => 
                write!(f, "username"),
            Self::CNonce => 
                write!(f, "c-nonce"),
            Self::SNonce =>
                write!(f, "s-nonce"),
            Self::IterationCount =>
                write!(f, "iteration-count"),
            Self::Error =>
                write!(f, "error"),
            Self::Salt =>
                write!(f, "salt"),
            Self::ChannelBinding => 
                write!(f, "channel-binding"),
            Self::Proof =>
                write!(f, "proof"),
            Self::Verifier =>
                write!(f, "verifier"),
        }
    }
}

const SCRAM_PARSER_RULES: &'static [ScramParserRule] = 
&[
    // n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
    ScramParserRule
    { 
        title: 
            "CLIENT_FIRST_MSG", 
        items: 
            &[
                ScramParserMsg
                { 
                    title: 
                        "gs2-cbind-flag",
                    msg_type:
                        ScramParserMsgType::Gs2CbindFlag,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemNoVal("n"),
                            ScramParserItem::ItemNoVal("y"),
                            ScramParserItem::ItemVal("p", ScramParserItemValidation::CbName),
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "authzid",
                    msg_type:
                        ScramParserMsgType::Authzid,
                    presence:
                        ScramParserItemPres::MayNotPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("a", ScramParserItemValidation::ValueSafeChar)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "reserved-mext",
                    msg_type:
                        ScramParserMsgType::ResercedMext,
                    presence:
                        ScramParserItemPres::NotSupported,
                    items:
                        &[
                            ScramParserItem::ItemVal("m", ScramParserItemValidation::ValueSafeChar)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "username",
                    msg_type:
                        ScramParserMsgType::Username,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("n", ScramParserItemValidation::ValueSafeChar)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "c-nonce",
                    msg_type:
                        ScramParserMsgType::CNonce,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("r", ScramParserItemValidation::Printable)
                        ]
                },
            ] 
    },

    // r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
    ScramParserRule
    {
        title:
            "SERVER_FIRST_MSG",
        items: 
            &[
                // it is not clear if this is part of the RFC but this lib demonstrates such tricks,
                // in case of internal error.
                ScramParserMsg
                {
                    title: 
                        "error",
                    msg_type:
                        ScramParserMsgType::Error,
                    presence:
                        ScramParserItemPres::MayNotPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("e", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "reserved-mext",
                    msg_type:
                        ScramParserMsgType::ResercedMext,
                    presence:
                        ScramParserItemPres::NotSupported,
                    items:
                        &[
                            ScramParserItem::ItemVal("m", ScramParserItemValidation::ValueSafeChar)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "s-nonce",
                    msg_type:
                        ScramParserMsgType::SNonce,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("r", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "salt",
                    msg_type:
                        ScramParserMsgType::Salt,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("s", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "iteration-count",
                    msg_type:
                        ScramParserMsgType::IterationCount,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("i", ScramParserItemValidation::UnsignedPositive)
                        ]
                },
            ]
    },

    // c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=
    ScramParserRule
    {
        title:
            "CLIENT_FINAL_MSG",
        items: 
            &[
                ScramParserMsg
                {
                    title: 
                        "channel-binding",
                    msg_type:
                        ScramParserMsgType::ChannelBinding,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("c", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "c-nonce",
                    msg_type:
                        ScramParserMsgType::CNonce,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("r", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "proof",
                    msg_type:
                        ScramParserMsgType::Proof,
                    presence:
                        ScramParserItemPres::MustPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("p", ScramParserItemValidation::Printable)
                        ]
                },
            ]
    },

    // v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4= / e=invalid-proof
    ScramParserRule
    {
        title:
            "SERVER_FINAL_MSG",
        items: 
            &[
                ScramParserMsg
                {
                    title: 
                        "verifier",
                    msg_type:
                        ScramParserMsgType::Verifier,
                    presence:
                        ScramParserItemPres::MayNotPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("v", ScramParserItemValidation::Printable)
                        ]
                },
                ScramParserMsg
                {
                    title: 
                        "error",
                    msg_type:
                        ScramParserMsgType::Error,
                    presence:
                        ScramParserItemPres::MayNotPresent,
                    items:
                        &[
                            ScramParserItem::ItemVal("e", ScramParserItemValidation::Printable)
                        ]
                },
            ]
    }
];

#[derive(Debug)]
pub(crate) struct ScramParser<'par>
{
    srcmsg: &'par str,
    split: Split<'par, &'static str>,
    parse_rule: &'static ScramParserRule,
    parse_item_iter: slice::Iter<'static, ScramParserMsg>,
}

impl<'par> ScramParser<'par>
{ 
    fn parse(msg: &'par str, scramstate: &'par ScramState) -> ScramResult<ScramParser<'par>>
    {
        if msg.len() <= 1
        {
            scram_error!(ScramErrorCode::MalformedScramMsg, ScramServerError::OtherError,
                "state: '{}', unexpected EOF while parsing", scramstate);
        }

        let parse_rule = 
            match scramstate
            {
                ScramState::WaitForClientInitalMsg =>
                    &SCRAM_PARSER_RULES[ScramParserRule::SCRAM_CLIENT_INIT_MSG],

                ScramState::WaitForServInitMsg =>
                    &SCRAM_PARSER_RULES[ScramParserRule::SCRAM_SERVER_INIT_MSG], //todo

                ScramState::WaitForClientFinalMsg{..} =>
                    &SCRAM_PARSER_RULES[ScramParserRule::SCRAM_CLIENT_FINAL_MSG],

                ScramState::WaitForServFinalMsg{..} =>
                    &SCRAM_PARSER_RULES[ScramParserRule::SCRAM_SERVER_FINAL_MSG],

                _ => 
                    scram_error!(
                        ScramErrorCode::InternalError,
                        ScramServerError::OtherError,
                        "state {} not implemented or does not require handling", scramstate
                    ),
            };


        let inst = 
            Self
            {
                srcmsg: msg,
                split: msg.split(","),
                parse_rule: parse_rule,
                parse_item_iter: parse_rule.items.into_iter()
            };

        return Ok(inst);
    }

    fn iter_cycle(&mut self, item: Option<&'par str>) -> Option<ScramResult<(&'static ScramParserMsg, Option<&'par str>)>>
    {
        let mut parser_item = self.parse_item_iter.next();

        if item.is_some() == true && parser_item.is_none() == true
        {
            return 
                Some(
                    Err(
                        scram_error_map!(
                            ScramErrorCode::InternalError,
                            ScramServerError::OtherError,
                            "unexpected data (probably unsupported extension), item: {}",  
                                ScramCommon::sanitize_str_unicode(item.as_ref().unwrap())
                        )
                    )
                );
        }
        else if item.is_none() == true 
        {   
            if parser_item.is_some() == true
            {
                while parser_item.is_some() == true
                {
                    let pars_msg = parser_item.as_ref().unwrap();

                    if pars_msg.presence == ScramParserItemPres::MustPresent
                    {
                        return 
                            Some(
                                Err(
                                    scram_error_map!(
                                        ScramErrorCode::InternalError,
                                        ScramServerError::OtherError,
                                        "expected {} to present in message, but EOF was reached", pars_msg
                                    )
                                )
                            );
                    }

                    parser_item = self.parse_item_iter.next();
                }
                
                return None;
            }
            else
            {
                // EOF
                return None;
            }
        }

        let parser_item = parser_item.unwrap();
        let item = item.unwrap();

        let (var, val) = 
            item.split_once("=").map_or((item, ""),|(var, val)| (var, val));

      /*  // check var is empty, i.e p=sss,,d=eeee,
        if var.is_empty() == true
        {
            if parser_item.presence == ScramParserItemPres::MayNotPresent
            {
                // skip, assuming that we skip item.
                return self.next();
            }
        }*/    

        // check if var is corresponding to the current parsed item
        if let Some(prs_itm) = parser_item.get_item(var)
        {
            if parser_item.presence == ScramParserItemPres::NotSupported
            {
                return 
                    Some(
                        Err(
                            scram_error_map!(
                                ScramErrorCode::FeatureNotSupported,
                                ScramServerError::ExtensionsNotSupported,
                                "rule: '{}', rule_item: '{}', error: 'extension is not supported'", self.parse_rule, parser_item
                            )
                        )
                    );
            }
            else if let Err(e) = prs_itm.validate(val)
            {
                return 
                    Some(
                        Err(
                            scram_error_map!(
                                ScramErrorCode::MalformedScramMsg,
                                ScramServerError::OtherError,
                                "rule: '{}', rule_item: '{}', error: '{}'", self.parse_rule, parser_item, e
                            )
                        )
                    );
            }

            if prs_itm.is_noval() == false
            {
                return Some(Ok((parser_item, Some(val))));
            }
            else
            {
                return Some(Ok((parser_item, Some(var))));
            }
        }
        
        // check if value is not mandatory and can be skipped
        if parser_item.presence == ScramParserItemPres::MustPresent
        {
            if var.is_empty() == true
            {
                return 
                    Some(
                        Err(
                            scram_error_map!(
                                ScramErrorCode::MalformedScramMsg,
                                ScramServerError::OtherError,
                                "rule: '{}', rule_item: '{}', error: 'should present'", self.parse_rule, parser_item
                            )
                        )
                    );
            }
            else
            {
                return 
                    Some(
                        Err(
                            scram_error_map!(
                                ScramErrorCode::MalformedScramMsg,
                                ScramServerError::OtherError,
                                "rule: '{}', rule_item: '{}', error: 'unknown option: '{}''", self.parse_rule, parser_item,
                                    ScramCommon::sanitize_str_unicode(var)
                            )
                        )
                    );
            }

        }
        else if parser_item.presence == ScramParserItemPres::NotSupported || var.is_empty() == false
        {
            // skip value
            return self.iter_cycle(Some(item));
        }

        return Some(Ok((parser_item, None)));
        
    }

    fn is_eof(&mut self, ignore_exts: bool) -> ScramResult<()>
    {
        if ignore_exts == true
        {
            return Ok(());
        }

        if let Some(res) = self.next()
        {
            res?;
        }

        return Ok(());
    }
}

impl<'par> Iterator for ScramParser<'par>
{
    type Item = ScramResult<(&'static ScramParserMsg, Option<&'par str>)>;

    fn next(&mut self) -> Option<Self::Item> 
    {
        let item = self.split.next();
        
        return self.iter_cycle(item);
    }
}

/// Parsed data storage with lifetime 'par.
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ScramData<'par>
{
    /// first message from client in the context of a SCRAM
    CmsgInitial
    {
        /// n, or y, or p=<val>
        chan_bind: ChannelBindType,

        /// "a=" saslname
        authzid: Option<&'par str>,

        /// "n=" saslname
        user: &'par str,

        /// r=" c-nonce [s-nonce]
        nonce: &'par str
    },

    /// first server-side message sent to the client in a SCRAM
    SmsgInitial
    {
        /// "r=" c-nonce [s-nonce]
        nonce: &'par str,

        /// "s=" base64
        salt: Vec<u8>,

        /// "i=" posit-number
        itrcnt: u32,
    },

    /// client final message
    CmsgFinalMessage
    {
        /// "c=" base64 [biws == n] [eSws == y]
        chanbinding: &'par str,

        /// "r=" base64 (final nonce)
        finalnonce: &'par str,

        /// "p=" base64 (proof)
        proof: &'par str,

        /// stored client_nonce
        client_nonce: &'par str,
    },

    /// server final message 
    SmsgFinalMessage
    {
        /// "v=" base64 base-64 encoded ServerSignature.
        verifier: Vec<u8>,

        /// stored server verifier
        server_verifier: &'par [u8],
    },
}

impl<'par> fmt::Display for ScramData<'par>
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result 
    {
        match *self 
        {
            Self::CmsgFinalMessage{..} => write!(f, "CmsgFinalMessage"),
            Self::CmsgInitial{..} => write!(f, "CmsgInitial"),
            Self::SmsgFinalMessage{..} => write!(f, "SmsgFinalMessage"),
            Self::SmsgInitial{..} => write!(f, "SmsgInitial"),
        }
    }
}


impl<'par> TryFrom<(&'par str, &'par ScramState, bool)> for ScramData<'par>
{
    type Error = ScramRuntimeError;
    
    fn try_from(value: (&'par str, &'par ScramState, bool)) -> Result<Self, Self::Error> 
    {
        let mut parser = ScramParser::parse(value.0, value.1)?;

        let res = 
            match value.1
            {
                ScramState::WaitForClientInitalMsg =>
                {
                    let cbind: ChannelBindType = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::Gs2CbindFlag
                                )
                            )??
                            .1
                            .unwrap()
                            .try_into()?;

                    let (_, authzid) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::Authzid
                                )
                            )??;

                    let (_, user) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::Username
                                )
                            )??;
            
                    let (_, nonce) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::CNonce
                                )
                            )??;

                    // check the EOF was reached
                    parser.is_eof(value.2).unwrap();
                    
                    ScramData::CmsgInitial{ chan_bind: cbind, authzid: authzid, user: user.unwrap(), nonce: nonce.unwrap() }
                },

                // Parsing the First Reply from Server SMSG_...
                // 
                // The syntax for the server-first-message is: (RFC 5802)
                // server-first-message = [reserved-mext ","] nonce "," salt ","
                //                       iteration-count ["," extensions]
                // nonce                = "r=" c-nonce [s-nonce]
                //                      ;; Second part provided by server.
                // c-nonce              = printable
                // s-nonce              = printable
                // salt                 = "s=" base64
                // iteration-count      = "i=" posit-number
                //                      ;; A positive number.
                // r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096
                ScramState::WaitForServInitMsg =>
                {
                    // it is not clear if this is part of the RFC but this lib demonstrates such tricks,
                    // in case of internal error.
                    let (msg_info, nonce_or_err) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::SNonce
                                )
                            )??;

                    if msg_info.msg_type == ScramParserMsgType::Error
                    {
                        let cerr_type = ScramServerError::from(nonce_or_err.unwrap());

                        scram_error!(ScramErrorCode::ClientSide, cerr_type, "state: '{}', client sent error: {}", 
                            value.1, cerr_type);
                    }

                    let (_, salt) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::SNonce
                                )
                            )??;

                    let (_, iters) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::SNonce
                                )
                            )??;

                    let salt = 
                        general_purpose::STANDARD.decode(salt.unwrap())
                            .map_err(|e| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg, 
                                    ScramServerError::InvalidEncoding,
                                    "parameter s= conversion err, {}", e
                                )
                            )?;

                    let itrcnt = 
                        u32::from_str_radix(iters.unwrap(), 10)
                            .map_err(|e| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg, 
                                    ScramServerError::OtherError,
                                    "parameter i= conversion err, {}", e
                                )
                            )?;

                    // check the EOF was reached
                    parser.is_eof(value.2).unwrap();

                    ScramData::SmsgInitial { nonce: nonce_or_err.unwrap(), salt: salt, itrcnt: itrcnt }
                },

                // c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=
                ScramState::WaitForClientFinalMsg{client_nonce} =>
                {
                    let (_, chanbinding) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::ChannelBinding
                                )
                            )??;

                    let (_, finalnonce) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::CNonce
                                )
                            )??;

                    let (_, proof) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::Proof
                                )
                            )??;

                    // check the EOF was reached
                    parser.is_eof(value.2).unwrap();

                    ScramData::CmsgFinalMessage{ chanbinding: chanbinding.unwrap(), finalnonce: finalnonce.unwrap(), proof: proof.unwrap(), client_nonce: client_nonce }
                },

                // v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4= / e=invalid-proof
                ScramState::WaitForServFinalMsg{server_signature} =>
                {
                    let (msg_info, verifier_or_error) = 
                        parser
                            .next()
                            .ok_or_else(|| 
                                scram_error_map!(
                                    ScramErrorCode::MalformedScramMsg,
                                    ScramServerError::OtherError,
                                    "scram state: '{}' msg unexpected EOF on state: '{}''", value.1, ScramParserMsgType::ChannelBinding
                                )
                            )??;


                    if msg_info.msg_type == ScramParserMsgType::Error
                    {
                        let cerr_type = ScramServerError::from(verifier_or_error.unwrap());

                        scram_error!(ScramErrorCode::ClientSide, cerr_type, "server error: '{}'", cerr_type);
                    }

                    let verifier = 
                        general_purpose::STANDARD.decode(verifier_or_error.unwrap())
                            .map_err(|e| 
                                scram_error_map!(ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                                    "parameter v= conversion err, {}", e)
                            )?;

                    // check the EOF was reached
                    parser.is_eof(value.2).unwrap();

                    ScramData::SmsgFinalMessage{ verifier: verifier, server_verifier: server_signature }
                },

                _ => 
                    scram_error!(
                        ScramErrorCode::InternalError,
                        ScramServerError::OtherError,
                        "state {} not implemented or does not require handling", value.1
                    ),
            };

        return Ok(res);
    }
}

#[cfg(test)]
mod tests
{
    #[cfg(not(feature = "std"))]
    use alloc::vec::Vec;
    
    #[cfg(not(feature = "std"))]
    use alloc::string::String;

    use base64::{engine::general_purpose, Engine};

    use crate::{scram_parser::ScramParserMsgType, scram_state::ScramState, ChannelBindType};

    use super::{ScramData, ScramParser};


    #[test]
    fn test_parser0()
    {
        let client_init = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
        
        let parser = ScramParser::parse(client_init, &ScramState::WaitForClientInitalMsg).unwrap();

        let mut items = parser.into_iter();

        let (spm, cbind) = items.next().unwrap().unwrap();
        assert_eq!(cbind, Some("n"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Gs2CbindFlag);

        let (spm, authzid) = items.next().unwrap().unwrap();
        assert_eq!(authzid, None);
        assert_eq!(spm.msg_type, ScramParserMsgType::Authzid);

        let (spm, user) = items.next().unwrap().unwrap();
        assert_eq!(user, Some("user"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Username);

        let (spm, nonce) = items.next().unwrap().unwrap();
        assert_eq!(nonce, Some("rOprNGfwEbeRWgbNEkqO"));
        assert_eq!(spm.msg_type, ScramParserMsgType::CNonce);

        items.is_eof(false).unwrap();

    }

    #[test]
    fn test_parser1()
    {
        let client_init = "p=tls-server-end-point,a=authid,n=user,r=rOprNGfwEbeRWgbNEkqO";
   
        let parser = ScramParser::parse(client_init, &ScramState::WaitForClientInitalMsg).unwrap();

        let mut items = parser.into_iter();

        let (spm, cbind) = items.next().unwrap().unwrap();
        assert_eq!(cbind, Some("tls-server-end-point"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Gs2CbindFlag);

        let (spm, authzid) = items.next().unwrap().unwrap();
        assert_eq!(authzid, Some("authid"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Authzid);

        let (spm, user) = items.next().unwrap().unwrap();
        assert_eq!(user, Some("user"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Username);

        let (spm, nonce) = items.next().unwrap().unwrap();
        assert_eq!(nonce, Some("rOprNGfwEbeRWgbNEkqO"));
        assert_eq!(spm.msg_type, ScramParserMsgType::CNonce);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser2()
    {
        let client_init = "p=tls-server-end-point,a=authid,m=test,n=user,r=rOprNGfwEbeRWgbNEkqO";
        
        let parser = ScramParser::parse(client_init, &ScramState::WaitForClientInitalMsg).unwrap();

        let mut items = parser.into_iter();

        let (spm, cbind) = items.next().unwrap().unwrap();
        assert_eq!(cbind, Some("tls-server-end-point"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Gs2CbindFlag);

        let (spm, authzid) = items.next().unwrap().unwrap();
        assert_eq!(authzid, Some("authid"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Authzid);

        let res = items.next().unwrap();
        assert_eq!(res.is_err(), true);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser3()
    {
        let server_init = "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096";
        
        let parser = ScramParser::parse(server_init, &ScramState::WaitForServInitMsg).unwrap();

        let mut items = parser.into_iter();

       /* let (spm, error) = items.next().unwrap().unwrap();
        assert_eq!(error, None);
        assert_eq!(spm.msg_type, ScramParserMsgType::Error);*/

        let (spm, nonce) = items.next().unwrap().unwrap();
        assert_eq!(nonce, Some("fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j"));
        assert_eq!(spm.msg_type, ScramParserMsgType::SNonce);

        let (spm, salt) = items.next().unwrap().unwrap();
        assert_eq!(salt, Some("QSXCR+Q6sek8bf92"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Salt);

        let (spm, iters) = items.next().unwrap().unwrap();
        assert_eq!(iters, Some("4096"));
        assert_eq!(spm.msg_type, ScramParserMsgType::IterationCount);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser4()
    {
        let server_init = "e=error";
        
        let parser = ScramParser::parse(server_init, &ScramState::WaitForServInitMsg).unwrap();

        let mut items = parser.into_iter();

        let (spm, error) = items.next().unwrap().unwrap();
        assert_eq!(error, Some("error"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Error);
    }

    #[test]
    fn test_parser5()
    {
        let client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
        let state = ScramState::WaitForClientFinalMsg { client_nonce: String::new() };

        let parser = ScramParser::parse(client_final, &state).unwrap();

        let mut items = parser.into_iter();

        let (spm, chanbd) = items.next().unwrap().unwrap();
        assert_eq!(chanbd, Some("biws"));
        assert_eq!(spm.msg_type, ScramParserMsgType::ChannelBinding);

        let (spm, cnonce) = items.next().unwrap().unwrap();
        assert_eq!(cnonce, Some("rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"));
        assert_eq!(spm.msg_type, ScramParserMsgType::CNonce);

        let (spm, proof) = items.next().unwrap().unwrap();
        assert_eq!(proof, Some("dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ="));
        assert_eq!(spm.msg_type, ScramParserMsgType::Proof);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser6()
    {
        let client_final = "c=biws,e=test";
        let state = ScramState::WaitForClientFinalMsg { client_nonce: String::new() };

        let parser = ScramParser::parse(client_final, &state).unwrap();

        let mut items = parser.into_iter();

        let (spm, chanbd) = items.next().unwrap().unwrap();
        assert_eq!(chanbd, Some("biws"));
        assert_eq!(spm.msg_type, ScramParserMsgType::ChannelBinding);

        let err = items.next().unwrap();

        assert_eq!(err.is_err(), true);
    }

    
    #[test]
    fn test_parser7()
    {
        let server_final = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
        let state = ScramState::WaitForServFinalMsg { server_signature: Vec::new() };

        let parser = ScramParser::parse(server_final, &state).unwrap();

        let mut items = parser.into_iter();

        let (spm, verif) = items.next().unwrap().unwrap();
        assert_eq!(verif, Some("6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="));
        assert_eq!(spm.msg_type, ScramParserMsgType::Verifier);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser8()
    {
        let server_final = "e=invalid-proof";
        let state = ScramState::WaitForServFinalMsg { server_signature: Vec::new() };

        let parser = ScramParser::parse(server_final, &state).unwrap();

        let mut items = parser.into_iter();

        let (spm, verif) = items.next().unwrap().unwrap();
        assert_eq!(verif, Some("invalid-proof"));
        assert_eq!(spm.msg_type, ScramParserMsgType::Error);

        items.is_eof(false).unwrap();
    }

    #[test]
    fn test_parser9()
    {
        let client_init = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";

        let res = ScramData::try_from((client_init, &ScramState::WaitForClientInitalMsg, false)).unwrap();

        let ctrl = ScramData::CmsgInitial { chan_bind: ChannelBindType::n(), authzid: None, user: "user", nonce: "rOprNGfwEbeRWgbNEkqO" };

        assert_eq!(res, ctrl);
    }

    #[test]
    fn test_parser10()
    {
        let server_init = "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096";

        let res = ScramData::try_from((server_init, &ScramState::WaitForServInitMsg, false)).unwrap();

        let salt_dec = 
            general_purpose::STANDARD.decode("QSXCR+Q6sek8bf92").unwrap();

        let ctrl = ScramData::SmsgInitial { nonce: "fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j", salt: salt_dec, itrcnt: 4096 };

        assert_eq!(res, ctrl);
    }

    #[test]
    fn test_parser11()
    {
        let client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";

        #[cfg(not(feature = "std"))]
        use crate::alloc::string::ToString;

        let client_nonce = "test".to_string();
        let state = ScramState::WaitForClientFinalMsg { client_nonce: client_nonce.clone() };

        let res = ScramData::try_from((client_final, &state, false)).unwrap();

        
        let ctrl = ScramData::CmsgFinalMessage { chanbinding: "biws", finalnonce: "rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0", proof: "dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", client_nonce: &client_nonce };

        assert_eq!(res, ctrl);
    }

    #[test]
    fn test_parser12()
    {
        let server_final = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";

        let server_signature = b"test".to_vec();
        let state = ScramState::WaitForServFinalMsg { server_signature: server_signature.clone() };

        let res = ScramData::try_from((server_final, &state, false)).unwrap();

        let verifier = 
            general_purpose::STANDARD.decode("6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=").unwrap();
        
        let ctrl = ScramData::SmsgFinalMessage { verifier: verifier, server_verifier: &server_signature };

        assert_eq!(res, ctrl);
    }

    #[test]
    fn test_parser13()
    {
        let server_final = "e=invalid-proof";

        let server_signature = b"test".to_vec();
        let state = ScramState::WaitForServFinalMsg { server_signature: server_signature.clone() };

        let res = ScramData::try_from((server_final, &state, false));

        assert_eq!(res.is_err(), true);
    }

    
    #[test]
    fn test_parser14()
    {
        let server_final = "e=invali\0d-proof";

        let server_signature = b"test".to_vec();
        let state = ScramState::WaitForServFinalMsg { server_signature: server_signature.clone() };

        let res = ScramData::try_from((server_final, &state, false));

        assert_eq!(res.is_err(), true);

        #[cfg(feature = "std")]
        println!("{}", res.err().unwrap());
    }
}