use crate::BString;

pub trait Stage {
    type Str: std::fmt::Debug + Clone;
}

#[derive(Debug, Clone)]
pub struct PreExpansion;
#[derive(Debug, Clone)]
pub struct PostExpansion;

impl Stage for PreExpansion {
    type Str = ExpString;
}

impl Stage for PostExpansion {
    type Str = BString;
}

type Res<T, E> = std::result::Result<T, E>;

pub trait Expander {
    type Error;
    fn expand_var(&mut self, v: BString) -> Res<BString, Self::Error>;
    fn expand_cmd(&mut self, c: Ast<PostExpansion>) -> Res<BString, Self::Error>;
}

#[derive(Debug, Clone)]
pub enum Ast<T: Stage> {
    AssignVar(AssignVar),
    Pipes(Pipes<T>),
}

impl Ast<PreExpansion> {
    pub fn expand<E: Expander>(self, e: &mut E) -> Res<Ast<PostExpansion>, E::Error> {
        match self {
            Ast::AssignVar(_) => todo!(),
            Ast::Pipes(pipes) => Ok(Ast::Pipes(pipes.expand(e)?)),
        }
    }
}

#[derive(Debug, Clone)]
pub struct AssignVar {
    pub to: String,
    // TODO: body
}

#[derive(Debug, Clone)]
pub struct Pipes<T: Stage> {
    pub cmds: Vec<Command<T>>,
}

impl Pipes<PreExpansion> {
    fn expand<E: Expander>(self, e: &mut E) -> Res<Pipes<PostExpansion>, E::Error> {
        let mut cmds = Vec::with_capacity(self.cmds.len());
        for cmd in self.cmds.into_iter() {
            cmds.push(cmd.expand(e)?);
        }
        Ok(Pipes { cmds })
    }
}

#[derive(Debug, Clone)]
pub enum StringPart {
    Boring(BString),
    Var(VarName),
    Cmd(Ast<PreExpansion>),
}

impl StringPart {
    pub fn is_boring(&self) -> bool {
        matches!(self, StringPart::Boring(..))
    }
    pub fn unwrap_boring(self) -> BString {
        match self {
            StringPart::Boring(items) => items,
            _ => panic!("unwrap on non-boring value"),
        }
    }
}

#[derive(Debug, Clone)]
/// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]`
pub struct ExpString {
    parts: Vec<StringPart>,
}

impl ExpString {
    fn expand<E: Expander>(self, e: &mut E) -> Res<BString, E::Error> {
        let mut out = BString::new();
        for part in self.parts.into_iter() {
            let mut x = match part {
                StringPart::Boring(items) => items,
                StringPart::Var(v) => e.expand_var(v.name)?,
                StringPart::Cmd(ast) => {
                    let exp = ast.expand(e)?;
                    e.expand_cmd(exp)?
                }
            };
            out.append(&mut x);
        }
        Ok(out)
    }
}

fn is_symbol(x: u8) -> bool {
    match x {
        b'|' | b'{' | b'}' | b'$' | b'(' | b')' | b'\'' | b'"' => true,
        _ => false,
    }
}

fn is_var_begin(x: u8) -> bool {
    x.is_ascii_alphabetic()
}
fn is_var_name(x: u8) -> bool {
    x.is_ascii_alphanumeric() || x == b'_'
}

#[derive(Debug, Clone)]
pub struct VarName {
    name: BString,
}

impl Parse for VarName {
    fn parse(b: &mut Cursor<'_>) -> Result<Self> {
        if b.is_empty() {
            return Err(ParseError::Eof);
        }

        if !is_var_begin(b.peek()) {
            return Err(ParseError::ExpectedAlphabetic);
        }

        let mut name = BString::new();
        while b.has() {
            let x = b.peek();
            if is_var_name(x) {
                b.adv();
                name.push(x)
            } else {
                break;
            }
        }

        Ok(Self { name })
    }
}

impl Parse for ExpString {
    fn parse(b: &mut Cursor<'_>) -> Result<Self> {
        b.spaces();
        if b.is_empty() {
            return Err(ParseError::NotAString);
        }

        let mut delim = b.peek();
        if delim == b'\'' || delim == b'"' {
            b.adv();
        } else if is_symbol(delim) && delim != b'$' {
            return Err(ParseError::NotAString);
        } else {
            delim = b' ';
        }

        let mut parts = Vec::new();
        let p = &mut parts;
        let mut escaping = false;

        let add_char = |p: &mut Vec<StringPart>, x: u8| match p.last_mut() {
            Some(StringPart::Boring(v)) => v.push(x),
            _ => p.push(StringPart::Boring(vec![x])),
        };

        while b.has() {
            let x = b.peek();

            if escaping {
                add_char(p, x);
                escaping = false;
                b.adv();
                continue;
            }

            if x == delim || (b.peek_space() && delim == b' ') {
                if delim != b' ' {
                    b.adv();
                }
                return Ok(Self { parts });
            }

            if delim == b' ' && is_symbol(x) && x != b'$' {
                return Ok(Self { parts });
            }

            b.adv();

            if delim == b'\'' {
                // no fancy stuff here
                add_char(p, x);
                continue;
            }

            if x == b'\\' {
                escaping = true;
                continue;
            }

            if x == b'$' {
                if !b.has() {
                    add_char(p, x);
                    continue;
                }

                let x = b.peek();

                if x == b'?' || x == b'!' {
                    b.adv();
                    p.push(StringPart::Var(VarName { name: vec![x] }))
                } else if is_var_begin(x) {
                    let v = VarName::parse(b)?;
                    p.push(StringPart::Var(v));
                } else if x == b'{' {
                    b.adv();
                    let v = VarName::parse(b)?;

                    if !b.has() {
                        return Err(ParseError::Eof);
                    } else if b.peek() == b':' {
                        todo!(": in var expansion")
                    }

                    if !b.has() {
                        return Err(ParseError::Eof);
                    } else if b.peek() != b'}' {
                        return Err(ParseError::Incomplete);
                    }

                    b.adv();
                    p.push(StringPart::Var(v));
                } else if x == b'(' {
                    b.adv();
                    let cmd = Ast::parse(b)?;
                    b.spaces();
                    if b.is_empty() {
                        return Err(ParseError::Eof);
                    } else if b.peek() == b')' {
                        p.push(StringPart::Cmd(cmd));
                    } else {
                        return Err(ParseError::Expected(')'));
                    }
                } else {
                    // doesn't seem to be a variable or expansion, just add $ back into the string
                    add_char(p, b'$');
                    continue;
                }

                continue;
            }

            add_char(p, x);
        }

        if b.is_completion() || delim == b' ' {
            Ok(Self { parts })
        } else {
            Err(ParseError::Eof)
        }
    }
}

#[derive(Debug, Clone)]
pub struct Command<T: Stage> {
    pub cmd: T::Str,
    pub args: Vec<T::Str>,
}

impl Command<PreExpansion> {
    fn expand<E: Expander>(self, e: &mut E) -> Res<Command<PostExpansion>, E::Error> {
        let cmd = self.cmd.expand(e)?;
        let mut args = Vec::with_capacity(self.args.len());
        for arg in self.args.into_iter() {
            args.push(arg.expand(e)?);
        }
        Ok(Command { cmd, args })
    }
}

#[allow(unused)]
#[derive(Debug)]
pub enum ParseError {
    /// "clean" EOF, i.e. not in the middle of something
    Eof,

    /// "unclean" EOF, i.e. EOF after beginning a quoted string
    Incomplete,

    ExpectedAlphabetic,

    Unknown(u8),

    Expected(char),

    NotAString,
}

type Result<T> = std::result::Result<T, ParseError>;

pub fn do_parse(x: &[u8]) -> Res<Ast<PreExpansion>, (ParseError, &[u8])> {
    let mut c = Cursor::new(x, ParseMode::Command);
    match Ast::parse(&mut c) {
        Ok(ast) => Ok(ast),
        Err(e) => Err((e, c.buf)),
    }
}

pub enum CompletionKind {
    Command,
    Argument,
    None,
}

pub struct CompletionContext {
    pub kind: CompletionKind,
    pub partial: BString,
}

impl CompletionContext {
    pub fn none() -> Self {
        Self {
            kind: CompletionKind::None,
            partial: BString::new(),
        }
    }
}

fn expstr_cc(s: &ExpString, kind: CompletionKind) -> CompletionContext {
    if s.parts.len() > 1 || !s.parts[0].is_boring() {
        CompletionContext::none()
    } else {
        CompletionContext {
            kind,
            partial: s.parts[0].clone().unwrap_boring().clone(),
        }
    }
}

pub fn completion_context<'a>(x: &'a [u8]) -> CompletionContext {
    let mut cursor = Cursor::new(x, ParseMode::Completion);
    let ast = Ast::parse(&mut cursor);
    match ast {
        Ok(Ast::Pipes(pipes)) if cursor.spaced == false => {
            if let Some(cmd) = pipes.cmds.last() {
                if cmd.args.is_empty() {
                    expstr_cc(&cmd.cmd, CompletionKind::Command)
                } else {
                    expstr_cc(&cmd.args[cmd.args.len() - 1], CompletionKind::Argument)
                }
            } else {
                CompletionContext::none()
            }
        }
        _ => CompletionContext::none(),
    }
}

trait Parse: Sized {
    fn parse(b: &mut Cursor<'_>) -> Result<Self>;
}

enum ParseMode {
    Command,
    Completion,
}

struct Cursor<'a> {
    buf: &'a [u8],
    mode: ParseMode,

    /// if the last byte that was consumed was whitespace or part of a word
    spaced: bool,

    backtrace: bool,
}

impl<'a> Cursor<'a> {
    fn new(buf: &'a [u8], mode: ParseMode) -> Self {
        Self {
            buf,
            mode,
            spaced: false,
            backtrace: false,
        }
    }

    // non empty
    fn has(&self) -> bool {
        !self.buf.is_empty()
    }

    fn is_empty(&self) -> bool {
        self.buf.is_empty()
    }

    fn bt(&self, word: &str) {
        if self.backtrace {
            let bt = std::backtrace::Backtrace::capture();
            let bt = format!("{bt}");
            println!("{word} {}\r", self.buf[0] as char);
            for l in bt.lines().skip(4).take(2) {
                println!("{l}\r");
            }
            println!("\r");
        }
    }

    fn peek(&self) -> u8 {
        self.bt("peek");
        self.buf[0]
    }

    fn adv(&mut self) -> u8 {
        self.bt("adv");
        let out = self.buf[0];
        self.buf = &self.buf[1..];
        self.spaced = false;
        out
    }

    fn peek_space(&self) -> bool {
        if self.buf.is_empty() {
            return false;
        }
        matches!(self.buf[0], b' ' | b'\t' | b'\n' | b'\r')
    }

    fn spaces(&mut self) {
        while self.peek_space() {
            self.adv();
            self.spaced = true;
        }
    }

    fn is_completion(&self) -> bool {
        match self.mode {
            ParseMode::Completion => true,
            _ => false,
        }
    }

    fn parse<T: Parse>(&mut self) -> Result<T> {
        T::parse(self)
    }
}

impl Parse for Ast<PreExpansion> {
    fn parse(b: &mut Cursor<'_>) -> Result<Self> {
        Ok(Self::Pipes(b.parse()?))
    }
}

impl Parse for Command<PreExpansion> {
    fn parse(b: &mut Cursor<'_>) -> Result<Self> {
        let path: ExpString = b.parse()?;
        let mut args = Vec::new();
        loop {
            match ExpString::parse(b) {
                Ok(arg) => args.push(arg),
                Err(ParseError::NotAString) => break,
                Err(e) => Err(e)?,
            }
        }
        let x = Ok(Self { cmd: path, args });
        x
    }
}

impl Parse for Pipes<PreExpansion> {
    fn parse(b: &mut Cursor<'_>) -> Result<Self> {
        let mut cmds: Vec<Command<PreExpansion>> = vec![b.parse()?];

        loop {
            b.spaces();
            if b.is_empty() {
                return Ok(Pipes { cmds });
            }

            let c = b.peek();
            if c == b'|' {
                b.adv();
                cmds.push(b.parse()?);
            } else if is_symbol(c) {
                return Ok(Pipes { cmds });
            } else {
                Err(ParseError::Unknown(c))?;
            }
        }
    }
}
