#[cfg(feature = "std")]
use std::{error::Error, fmt};

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

use crate::{MacAddr, MacAddr6, MacAddr8};

/// An error which can be returned when parsing MAC address.
///
/// This error is used as the error type for the `FromStr` implementation
/// for [MacAddr6] and [MacAddr8].
///
/// [MacAddr6]: ./struct.MacAddr6.html
/// [MacAddr8]: ./struct.MacAddr8.html
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
pub enum ParseError {
    /// Provided string can't be parsed into the given type,
    /// because it is either too short or too long.
    ///
    /// For example, any trailing symbols will result in the error,
    /// as in `"12-34-56-78-9A-BC\n"`.
    ///
    /// This enum member will contain the provided string length when returned.
    InvalidLength(usize),

    /// Invalid character occurred in the provided string.
    ///
    /// Allowed characters are `0123456789abcdefABCDEF-:.`.
    ///
    /// This enum member will contain the wrong char and it's position when returned.
    InvalidCharacter(char, usize),
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ParseError::InvalidLength(len) => f.write_fmt(format_args!("Invalid length of {} characters", len,)),
            ParseError::InvalidCharacter(chr, pos) => {
                f.write_fmt(format_args!("Unexpected character '{}' at position {}", chr, pos,))
            }
        }
    }
}

#[cfg(feature = "std")]
impl Error for ParseError {}

#[derive(Debug, Eq, PartialEq)]
enum Delimiter {
    Hyphen,
    Colon,
    Dot,
}

// Heavily based on the Rust' `std/net/parser.rs` sources.
#[derive(Debug)]
pub struct Parser<'a> {
    source: &'a [u8],
    pos: usize,
    delimiter: Option<Delimiter>,
}

impl<'a> Parser<'a> {
    pub fn new(s: &'a str) -> Parser<'a> {
        Parser {
            source: s.as_bytes(),
            pos: 0,
            delimiter: None,
        }
    }

    fn is_eof(&self) -> bool {
        self.pos == self.source.len()
    }

    fn move_next(&mut self) {
        if !self.is_eof() {
            self.pos += 1;
        }
    }

    fn peek_char(&mut self) -> Option<char> {
        if self.is_eof() {
            None
        } else {
            Some(self.source[self.pos] as char)
        }
    }

    fn read_char(&mut self) -> Result<char, ParseError> {
        if self.is_eof() {
            Err(ParseError::InvalidLength(self.pos))
        } else {
            let r = self.source[self.pos] as char;
            self.pos += 1;
            Ok(r)
        }
    }

    fn read_digit(&mut self) -> Result<u8, ParseError> {
        let chr = self.read_char()?;

        match chr as u8 {
            byte @ b'0'..=b'9' => Ok(byte - b'0'),
            byte @ b'a'..=b'f' => Ok(byte - b'a' + 10),
            byte @ b'A'..=b'F' => Ok(byte - b'A' + 10),
            _ => Err(ParseError::InvalidCharacter(chr, self.pos)),
        }
    }

    fn probe_delimiter(&mut self) -> Result<Option<()>, ParseError> {
        match self.peek_char() {
            Some('-') if self.delimiter.is_none() => {
                self.delimiter = Some(Delimiter::Hyphen);
                Ok(Some(()))
            }
            Some('-') if self.delimiter != Some(Delimiter::Hyphen) => Err(ParseError::InvalidCharacter('-', self.pos)),
            Some('-') => Ok(Some(())),

            Some(':') if self.delimiter.is_none() => {
                self.delimiter = Some(Delimiter::Colon);
                Ok(Some(()))
            }
            Some(':') if self.delimiter != Some(Delimiter::Colon) => Err(ParseError::InvalidCharacter(':', self.pos)),
            Some(':') => Ok(Some(())),

            Some('.') if self.delimiter.is_none() => {
                self.delimiter = Some(Delimiter::Dot);
                Ok(Some(()))
            }
            Some('.') if self.delimiter != Some(Delimiter::Dot) => Err(ParseError::InvalidCharacter('.', self.pos)),
            Some('.') => Ok(Some(())),
            _ => Ok(None),
        }
    }

    pub fn read_v6_addr(&mut self) -> Result<MacAddr6, ParseError> {
        let mut bytes = [0; 6];
        let mut i = 0;

        while i < 6 {
            if self.probe_delimiter()?.is_some() {
                self.move_next();
            }

            let mut digit = self.read_digit()? * 16;
            digit += self.read_digit()?;

            bytes[i] = digit;

            i += 1;
        }

        if self.is_eof() {
            Ok(MacAddr6::from(bytes))
        } else {
            Err(ParseError::InvalidLength(self.source.len()))
        }
    }

    pub fn read_v8_addr(&mut self) -> Result<MacAddr8, ParseError> {
        let mut bytes = [0; 8];
        let mut i = 0;

        while i < 8 {
            if self.probe_delimiter()?.is_some() {
                self.move_next();
            }

            let mut digit = self.read_digit()? * 16;
            digit += self.read_digit()?;

            bytes[i] = digit;

            i += 1;
        }

        if self.is_eof() {
            Ok(MacAddr8::from(bytes))
        } else {
            Err(ParseError::InvalidLength(self.source.len()))
        }
    }

    pub fn read_addr(&mut self) -> Result<MacAddr, ParseError> {
        match self.read_v6_addr() {
            Ok(addr) => return Ok(addr.into()),
            Err(err @ ParseError::InvalidCharacter(..)) => return Err(err),
            Err(ParseError::InvalidLength(..)) => {}
        }

        // Rolling back to the start.
        self.pos = 0;

        self.read_v8_addr().map(Into::into)
    }
}

#[cfg(test)]
mod tests;