Documentation
//! WIP
use std::fmt;
use std::str::FromStr;

mod error;
mod parse;
mod validate;
pub mod visit;

#[cfg(test)]
mod tests;

pub use error::{ParseError, ParseErrorKind};

pub const KEYWORDS: &[&str] = &[
    "begin", "end", "if", "else", "switch", "case", "for", "in", "and", "or", "not", "function",
];

pub type Pos = u32;

#[derive(Debug, Clone, PartialEq)]
pub struct SourceFile {
    pub stmts: Vec<Stmt>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
    Command(Pos, Words),
    Block(Pos, Vec<Stmt>),
    If(Pos, Box<Stmt>, Box<Stmt>, Option<Box<Stmt>>),
    For(Pos, Word, Words, Box<Stmt>),
    While(Pos, Box<Stmt>, Box<Stmt>),
    Break(Pos),
    Continue(Pos),
    Function(Pos, Words, Box<Stmt>),
    Return(Pos, Option<Word>),
    Switch(Pos, Word, Vec<SwitchCase>),

    Redirect(Pos, Box<Stmt>, Vec<Redirect>),
    Pipe(Pos, Vec<(Stmt, RedirectPort)>, Box<Stmt>),

    Not(Pos, Box<Stmt>),
    UnaryAnd(Pos, Box<Stmt>),
    UnaryOr(Pos, Box<Stmt>),
    BinaryAnd(Pos, Box<Stmt>, Box<Stmt>),
    BinaryOr(Pos, Box<Stmt>, Box<Stmt>),
}

impl Stmt {
    pub fn pos(&self) -> u32 {
        match self {
            Stmt::Command(pos, ..)
            | Stmt::Block(pos, ..)
            | Stmt::If(pos, ..)
            | Stmt::For(pos, ..)
            | Stmt::While(pos, ..)
            | Stmt::Break(pos, ..)
            | Stmt::Continue(pos, ..)
            | Stmt::Function(pos, ..)
            | Stmt::Return(pos, ..)
            | Stmt::Switch(pos, ..)
            | Stmt::Redirect(pos, ..)
            | Stmt::Pipe(pos, ..)
            | Stmt::Not(pos, ..)
            | Stmt::UnaryAnd(pos, ..)
            | Stmt::UnaryOr(pos, ..)
            | Stmt::BinaryAnd(pos, ..)
            | Stmt::BinaryOr(pos, ..) => *pos,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct SwitchCase {
    pub globs: Vec<Word>,
    pub body: Stmt,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Redirect {
    pub port: RedirectPort,
    pub mode: RedirectMode,
    pub dest: Word,
}

impl Redirect {
    pub fn new(port: RedirectPort, mode: RedirectMode, dest: Word) -> Self {
        Self { port, mode, dest }
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct RedirectPort(u16);

impl RedirectPort {
    pub const STDIN: Self = Self(0);
    pub const STDOUT: Self = Self(1);
    pub const STDERR: Self = Self(2);
    pub const STDOUT_STDERR: Self = Self(!0);
}

impl fmt::Debug for RedirectPort {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match *self {
            Self::STDIN => "STDIN",
            Self::STDOUT => "STDOUT",
            Self::STDERR => "STDERR",
            Self::STDOUT_STDERR => "STDOUT_STDERR",
            _ => return f.debug_tuple("RedirectPort").field(&self.0).finish(),
        })
    }
}

impl FromStr for RedirectPort {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let v = s.parse::<u16>().ok().filter(|&p| p != !0).ok_or(())?;
        Ok(Self(v))
    }
}

impl RedirectPort {
    pub fn port(self) -> Option<u16> {
        (self.0 != !0).then_some(self.0)
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RedirectMode {
    Read,
    ReadOrNull,
    ReadFd,

    Write,
    WriteNoClobber,
    Append,
    WriteFd,
}

impl RedirectMode {
    #[must_use]
    pub fn default_port(self) -> RedirectPort {
        match self {
            RedirectMode::Read | RedirectMode::ReadOrNull | RedirectMode::ReadFd => {
                RedirectPort::STDIN
            }
            RedirectMode::Write
            | RedirectMode::WriteNoClobber
            | RedirectMode::Append
            | RedirectMode::WriteFd => RedirectPort::STDOUT,
        }
    }
}

pub type Words = Vec<Word>;

#[derive(Debug, Clone, PartialEq)]
pub enum Word {
    Simple(String),
    Complex(Vec<WordFrag>),
}

impl From<WordFrag> for Word {
    fn from(frag: WordFrag) -> Self {
        match frag {
            WordFrag::Literal(s) => Word::Simple(s),
            frag => Word::Complex(vec![frag]),
        }
    }
}

impl Word {
    fn append(self, frag: WordFrag) -> Self {
        let v = match self {
            Word::Simple(lit) => vec![WordFrag::Literal(lit), frag],
            Word::Complex(mut frags) => {
                frags.push(frag);
                frags
            }
        };
        Self::Complex(v)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum WordFrag {
    Literal(String),
    Variable { name: String, deref: u8 },
    VariableNoSplit { name: String, deref: u8 },
    Command(Stmt),
    CommandNoSplit(Stmt),

    Brace(Vec<Word>),

    Home { slash: bool },
    Wildcard,
    WildcardRecursive,
}

pub fn parse_source(src: &str) -> Result<SourceFile, Vec<ParseError>> {
    let mut file = parse::parse_source(src).map_err(|err| vec![err])?;
    validate::validate_fixup(&mut file)?;
    Ok(file)
}