use std::fmt::{Display, Formatter, Write};
use std::io::{BufRead, BufReader, Cursor};
use std::iter::Peekable;
use std::vec::IntoIter;

use crate::SePolicy;
use crate::ffi::Xperm;
use base::nix::fcntl::OFlag;
use base::{BufReadExt, LoggedResult, Utf8CStr, error, warn};

pub enum Token<'a> {
    AL,
    DN,
    AA,
    DA,
    AX,
    AY,
    DX,
    PM,
    EF,
    TA,
    TY,
    AT,
    TT,
    TC,
    TM,
    GF,
    LB,
    RB,
    CM,
    ST,
    TL,
    HP,
    HX(u16),
    ID(&'a str),
}

type Tokens<'a> = Peekable<IntoIter<Token<'a>>>;
type ParseResult<'a, T> = Result<T, ParseError<'a>>;

enum ParseError<'a> {
    General,
    AvtabAv(Token<'a>),
    AvtabXperms(Token<'a>),
    AvtabType(Token<'a>),
    TypeState(Token<'a>),
    TypeAttr,
    TypeTrans,
    NewType,
    NewAttr,
    GenfsCon,
    ShowHelp,
    UnknownAction(Token<'a>),
}

macro_rules! throw {
    () => {
        Err(ParseError::General)?
    };
}

fn parse_id<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, &'a str> {
    match tokens.next() {
        Some(Token::ID(name)) => Ok(name),
        _ => throw!(),
    }
}

// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// term ::= ID(n) { vec![n] }
// term ::= LB names(n) RB { n };
fn parse_term<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
    match tokens.next() {
        Some(Token::ID(name)) => Ok(vec![name]),
        Some(Token::LB) => {
            let mut names = Vec::new();
            loop {
                match tokens.next() {
                    Some(Token::ID(name)) => names.push(name),
                    Some(Token::RB) => break,
                    _ => throw!(),
                }
            }
            Ok(names)
        }
        _ => throw!(),
    }
}

// names ::= ST { vec![] }
// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// names ::= names(n) ST { vec![] };
// sterm ::= ST { vec![] }
// sterm ::= ID(n) { vec![n] }
// sterm ::= LB names(n) RB { n };
fn parse_sterm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
    match tokens.next() {
        Some(Token::ID(name)) => Ok(vec![name]),
        Some(Token::ST) => Ok(vec![]),
        Some(Token::LB) => {
            let mut names = Some(Vec::new());
            loop {
                match tokens.next() {
                    Some(Token::ID(name)) => {
                        if let Some(ref mut names) = names {
                            names.push(name)
                        }
                    }
                    Some(Token::ST) => names = None,
                    Some(Token::RB) => break,
                    _ => throw!(),
                }
            }
            Ok(names.unwrap_or(vec![]))
        }
        _ => throw!(),
    }
}

fn parse_xperm_hex(s: &str) -> Option<u16> {
    s.strip_prefix("0x").and_then(|s| u16::from_str_radix(s, 16).ok())
}

fn parse_xperm_range(s: &str) -> Option<(u16, u16)> {
    let (low, high) = s.split_once('-')?;
    Some((parse_xperm_hex(low)?, parse_xperm_hex(high)?))
}

// xperm ::= HX(low) { Xperm{low, high: low, reset: false} };
// xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} };
// xperm ::= ID("0x<low>-0x<high>") { Xperm{low, high, reset: false} };
fn parse_xperm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Xperm> {
    let (low, high) = match tokens.next() {
        Some(Token::HX(low)) => {
            let high = match tokens.peek() {
                Some(Token::HP) => {
                    tokens.next();
                    match tokens.next() {
                        Some(Token::HX(high)) => high,
                        _ => throw!(),
                    }
                }
                _ => low,
            };
            (low, high)
        }
        Some(Token::ID(s)) => {
            if let Some((low, high)) = parse_xperm_range(s) {
                (low, high)
            } else {
                throw!()
            }
        }
        _ => throw!(),
    };
    Ok(Xperm {
        low,
        high,
        reset: false,
    })
}

// xperms ::= HX(low) { if low > 0 { vec![Xperm{low, high: low, reset: false}] } else { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: true}] }};
// xperms ::= ID("0x<low>-0x<high>") { vec![Xperm{low, high, reset: false}] };
// xperms ::= LB xperm_list(l) RB { l };
// xperms ::= TL LB xperm_list(mut l) RB { l.iter_mut().for_each(|x| { x.reset = true; }); l };
// xperms ::= ST { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: false}] };
//
// xperm_list ::= xperm(p) { vec![p] }
// xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l }
fn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<Xperm>> {
    let mut xperms = Vec::new();
    let reset = match tokens.peek() {
        Some(Token::TL) => {
            tokens.next();
            if !matches!(tokens.peek(), Some(Token::LB)) {
                throw!();
            }
            true
        }
        _ => false,
    };
    match tokens.next() {
        Some(Token::LB) => {
            // parse xperm_list
            loop {
                let mut xperm = parse_xperm(tokens)?;
                xperm.reset = reset;
                xperms.push(xperm);
                if matches!(tokens.peek(), Some(Token::RB)) {
                    tokens.next();
                    break;
                }
            }
        }
        Some(Token::ST) => {
            xperms.push(Xperm {
                low: 0x0000,
                high: 0xFFFF,
                reset,
            });
        }
        Some(Token::HX(low)) => {
            if low > 0 {
                xperms.push(Xperm {
                    low,
                    high: low,
                    reset,
                });
            } else {
                xperms.push(Xperm {
                    low: 0x0000,
                    high: 0xFFFF,
                    reset,
                });
            }
        }
        Some(Token::ID(s)) => {
            if let Some((low, high)) = parse_xperm_range(s) {
                xperms.push(Xperm { low, high, reset });
            } else {
                throw!();
            }
        }
        _ => throw!(),
    }
    Ok(xperms)
}

fn match_string<'a>(tokens: &mut Tokens<'a>, pattern: &str) -> ParseResult<'a, ()> {
    if let Some(Token::ID(s)) = tokens.next()
        && s == pattern
    {
        return Ok(());
    }
    Err(ParseError::General)
}

fn extract_token<'a>(s: &'a str, tokens: &mut Vec<Token<'a>>) {
    match s {
        "allow" => tokens.push(Token::AL),
        "deny" => tokens.push(Token::DN),
        "auditallow" => tokens.push(Token::AA),
        "dontaudit" => tokens.push(Token::DA),
        "allowxperm" => tokens.push(Token::AX),
        "auditallowxperm" => tokens.push(Token::AY),
        "dontauditxperm" => tokens.push(Token::DX),
        "permissive" => tokens.push(Token::PM),
        "enforce" => tokens.push(Token::EF),
        "typeattribute" => tokens.push(Token::TA),
        "type" => tokens.push(Token::TY),
        "attribute" => tokens.push(Token::AT),
        "type_transition" => tokens.push(Token::TT),
        "type_change" => tokens.push(Token::TC),
        "type_member" => tokens.push(Token::TM),
        "genfscon" => tokens.push(Token::GF),
        "*" => tokens.push(Token::ST),
        "" => {}
        _ => {
            if let Some(idx) = s.find('{') {
                let (a, b) = s.split_at(idx);
                extract_token(a, tokens);
                tokens.push(Token::LB);
                extract_token(&b[1..], tokens);
            } else if let Some(idx) = s.find('}') {
                let (a, b) = s.split_at(idx);
                extract_token(a, tokens);
                tokens.push(Token::RB);
                extract_token(&b[1..], tokens);
            } else if let Some(idx) = s.find(',') {
                let (a, b) = s.split_at(idx);
                extract_token(a, tokens);
                tokens.push(Token::CM);
                extract_token(&b[1..], tokens);
            } else if let Some(s) = s.strip_prefix('~') {
                tokens.push(Token::TL);
                extract_token(s, tokens);
            } else if let Some(n) = parse_xperm_hex(s) {
                tokens.push(Token::HX(n));
            } else {
                tokens.push(Token::ID(s));
            }
        }
    }
}

fn tokenize_statement(statement: &str) -> Vec<Token<'_>> {
    let mut tokens = Vec::new();
    for s in statement.split_whitespace() {
        extract_token(s, &mut tokens);
    }
    tokens
}

impl SePolicy {
    pub fn load_rules(&mut self, rules: &str) {
        let mut cursor = Cursor::new(rules.as_bytes());
        self.load_rules_from_reader(&mut cursor);
    }

    pub fn load_rule_file(&mut self, filename: &Utf8CStr) {
        let result = || -> LoggedResult<()> {
            let file = filename.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
            let mut reader = BufReader::new(file);
            self.load_rules_from_reader(&mut reader);
            Ok(())
        }();
        result.ok();
    }

    fn load_rules_from_reader<T: BufRead>(&mut self, reader: &mut T) {
        reader.for_each_line(|line| {
            self.parse_statement(line);
            true
        });
    }

    fn parse_statement(&mut self, statement: &str) {
        let statement = statement.trim();
        if statement.is_empty() || statement.starts_with('#') {
            return;
        }
        let mut tokens = tokenize_statement(statement).into_iter().peekable();
        let result = self.exec_statement(&mut tokens);
        if let Err(e) = result {
            warn!("Syntax error in: \"{}\"", statement);
            error!("Hint: {}", e);
        }
    }

    // statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.allow(s, t, c, p); };
    // statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.deny(s, t, c, p); };
    // statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.auditallow(s, t, c, p); };
    // statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.dontaudit(s, t, c, p); };
    // statement ::= AX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.allowxperm(s, t, c, p); };
    // statement ::= AY sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.auditallowxperm(s, t, c, p); };
    // statement ::= DX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.dontauditxperm(s, t, c, p); };
    // statement ::= PM sterm(t) { sepolicy.permissive(t); };
    // statement ::= EF sterm(t) { sepolicy.enforce(t); };
    // statement ::= TA term(t) term(a) { sepolicy.typeattribute(t, a); };
    // statement ::= TY ID(t) { sepolicy.type_(t, vec![]);};
    // statement ::= TY ID(t) term(a) { sepolicy.type_(t, a);};
    // statement ::= AT ID(t) { sepolicy.attribute(t); };
    // statement ::= TT ID(s) ID(t) ID(c) ID(d) { sepolicy.type_transition(s, t, c, d, vec![]); };
    // statement ::= TT ID(s) ID(t) ID(c) ID(d) ID(o) { sepolicy.type_transition(s, t, c, d, vec![o]); };
    // statement ::= TC ID(s) ID(t) ID(c) ID(d) { sepolicy.type_change(s, t, c, d); };
    // statement ::= TM ID(s) ID(t) ID(c) ID(d) { sepolicy.type_member(s, t, c, d);};
    // statement ::= GF ID(s) ID(t) ID(c) { sepolicy.genfscon(s, t, c); };
    fn exec_statement<'a>(&mut self, tokens: &mut Tokens<'a>) -> ParseResult<'a, ()> {
        let action = match tokens.next() {
            Some(token) => token,
            _ => Err(ParseError::ShowHelp)?,
        };
        let check_additional_args = |tokens: &mut Tokens<'a>| {
            if tokens.peek().is_none() {
                Ok(())
            } else {
                Err(ParseError::General)
            }
        };
        match action {
            Token::AL | Token::DN | Token::AA | Token::DA => {
                let result = || -> ParseResult<()> {
                    let s = parse_sterm(tokens)?;
                    let t = parse_sterm(tokens)?;
                    let c = parse_sterm(tokens)?;
                    let p = parse_sterm(tokens)?;
                    check_additional_args(tokens)?;
                    match action {
                        Token::AL => self.allow(s, t, c, p),
                        Token::DN => self.deny(s, t, c, p),
                        Token::AA => self.auditallow(s, t, c, p),
                        Token::DA => self.dontaudit(s, t, c, p),
                        _ => unreachable!(),
                    }
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::AvtabAv(action))?
                }
            }
            Token::AX | Token::AY | Token::DX => {
                let result = || -> ParseResult<()> {
                    let s = parse_sterm(tokens)?;
                    let t = parse_sterm(tokens)?;
                    let c = parse_sterm(tokens)?;
                    match_string(tokens, "ioctl")?;
                    let p = parse_xperms(tokens)?;
                    check_additional_args(tokens)?;
                    match action {
                        Token::AX => self.allowxperm(s, t, c, p),
                        Token::AY => self.auditallowxperm(s, t, c, p),
                        Token::DX => self.dontauditxperm(s, t, c, p),
                        _ => unreachable!(),
                    }
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::AvtabXperms(action))?
                }
            }
            Token::PM | Token::EF => {
                let result = || -> ParseResult<()> {
                    let t = parse_sterm(tokens)?;
                    check_additional_args(tokens)?;
                    match action {
                        Token::PM => self.permissive(t),
                        Token::EF => self.enforce(t),
                        _ => unreachable!(),
                    }
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::TypeState(action))?
                }
            }
            Token::TA => {
                let result = || -> ParseResult<()> {
                    let t = parse_term(tokens)?;
                    let a = parse_term(tokens)?;
                    check_additional_args(tokens)?;
                    self.typeattribute(t, a);
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::TypeAttr)?
                }
            }
            Token::TY => {
                let result = || -> ParseResult<()> {
                    let t = parse_id(tokens)?;
                    let a = if tokens.peek().is_none() {
                        vec![]
                    } else {
                        parse_term(tokens)?
                    };
                    check_additional_args(tokens)?;
                    self.type_(t, a);
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::NewType)?
                }
            }
            Token::AT => {
                let result = || -> ParseResult<()> {
                    let t = parse_id(tokens)?;
                    check_additional_args(tokens)?;
                    self.attribute(t);
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::NewAttr)?
                }
            }
            Token::TC | Token::TM => {
                let result = || -> ParseResult<()> {
                    let s = parse_id(tokens)?;
                    let t = parse_id(tokens)?;
                    let c = parse_id(tokens)?;
                    let d = parse_id(tokens)?;
                    check_additional_args(tokens)?;
                    match action {
                        Token::TC => self.type_change(s, t, c, d),
                        Token::TM => self.type_member(s, t, c, d),
                        _ => unreachable!(),
                    }
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::AvtabType(action))?
                }
            }
            Token::TT => {
                let result = || -> ParseResult<()> {
                    let s = parse_id(tokens)?;
                    let t = parse_id(tokens)?;
                    let c = parse_id(tokens)?;
                    let d = parse_id(tokens)?;
                    let o = if tokens.peek().is_none() {
                        ""
                    } else {
                        parse_id(tokens)?
                    };
                    check_additional_args(tokens)?;
                    self.type_transition(s, t, c, d, o);
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::TypeTrans)?
                }
            }
            Token::GF => {
                let result = || -> ParseResult<()> {
                    let s = parse_id(tokens)?;
                    let t = parse_id(tokens)?;
                    let c = parse_id(tokens)?;
                    check_additional_args(tokens)?;
                    self.genfscon(s, t, c);
                    Ok(())
                }();
                if result.is_err() {
                    Err(ParseError::GenfsCon)?
                }
            }
            _ => Err(ParseError::UnknownAction(action))?,
        }
        Ok(())
    }
}

// Token to string
impl Display for Token<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Token::AL => f.write_str("allow"),
            Token::DN => f.write_str("deny"),
            Token::AA => f.write_str("auditallow"),
            Token::DA => f.write_str("dontaudit"),
            Token::AX => f.write_str("allowxperm"),
            Token::AY => f.write_str("auditallowxperm"),
            Token::DX => f.write_str("dontauditxperm"),
            Token::PM => f.write_str("permissive"),
            Token::EF => f.write_str("enforce"),
            Token::TA => f.write_str("typeattribute"),
            Token::TY => f.write_str("type"),
            Token::AT => f.write_str("attribute"),
            Token::TT => f.write_str("type_transition"),
            Token::TC => f.write_str("type_change"),
            Token::TM => f.write_str("type_member"),
            Token::GF => f.write_str("genfscon"),
            Token::LB => f.write_char('{'),
            Token::RB => f.write_char('}'),
            Token::CM => f.write_char(','),
            Token::ST => f.write_char('*'),
            Token::TL => f.write_char('~'),
            Token::HP => f.write_char('-'),
            Token::HX(n) => f.write_fmt(format_args!("{n:06X}")),
            Token::ID(s) => f.write_str(s),
        }
    }
}

impl Display for ParseError<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ParseError::General => Ok(()),
            ParseError::ShowHelp => format_statement_help(f),
            ParseError::AvtabAv(action) => {
                write!(f, "{action} *source_type *target_type *class *perm_set")
            }
            ParseError::AvtabXperms(action) => {
                write!(
                    f,
                    "{action} *source_type *target_type *class operation xperm_set"
                )
            }
            ParseError::AvtabType(action) => {
                write!(f, "{action} source_type target_type class default_type")
            }
            ParseError::TypeState(action) => {
                write!(f, "{action} *type")
            }
            ParseError::TypeAttr => f.write_str("typeattribute ^type ^attribute"),
            ParseError::TypeTrans => f.write_str(
                "type_transition source_type target_type class default_type (object_name)",
            ),
            ParseError::NewType => f.write_str("type type_name ^(attribute)"),
            ParseError::NewAttr => f.write_str("attribute attribute_name"),
            ParseError::GenfsCon => f.write_str("genfscon fs_name partial_path fs_context"),
            ParseError::UnknownAction(action) => write!(f, "Unknown action: \"{action}\""),
        }
    }
}

pub(crate) fn format_statement_help(f: &mut dyn Write) -> std::fmt::Result {
    write!(
        f,
        r#"** Policy statements:

One policy statement should be treated as a single parameter;
this means each policy statement should be enclosed in quotes.
Multiple policy statements can be provided in a single command.

Statements has a format of "<rule_name> [args...]".
Arguments labeled with (^) can accept one or more entries.
Multiple entries consist of a space separated list enclosed in braces ({{}}).
Arguments labeled with (*) are the same as (^), but additionally
support the match-all operator (*).

Example: "allow {{ s1 s2 }} {{ t1 t2 }} class *"
Will be expanded to:

allow s1 t1 class {{ all-permissions-of-class }}
allow s1 t2 class {{ all-permissions-of-class }}
allow s2 t1 class {{ all-permissions-of-class }}
allow s2 t2 class {{ all-permissions-of-class }}

** Extended permissions:

The only supported operation for extended permissions right now is 'ioctl'.
xperm_set is one or multiple hexadecimal numeric values ranging from 0x0000 to 0xFFFF.
Multiple values consist of a space separated list enclosed in braces ({{}}).
Use the complement operator (~) to specify all permissions except those explicitly listed.
Use the range operator (-) to specify all permissions within the low – high range.
Use the match all operator (*) to match all ioctl commands (0x0000-0xFFFF).
The special value 0 is used to clear all rules.

Some examples:
allowxperm source target class ioctl 0x8910
allowxperm source target class ioctl {{ 0x8910-0x8926 0x892A-0x8935 }}
allowxperm source target class ioctl ~{{ 0x8910 0x892A }}
allowxperm source target class ioctl *

** Supported policy statements:

{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
"#,
        ParseError::AvtabAv(Token::AL),
        ParseError::AvtabAv(Token::DN),
        ParseError::AvtabAv(Token::AA),
        ParseError::AvtabAv(Token::DA),
        ParseError::AvtabXperms(Token::AX),
        ParseError::AvtabXperms(Token::AY),
        ParseError::AvtabXperms(Token::DX),
        ParseError::TypeState(Token::PM),
        ParseError::TypeState(Token::EF),
        ParseError::TypeAttr,
        ParseError::NewType,
        ParseError::NewAttr,
        ParseError::TypeTrans,
        ParseError::AvtabType(Token::TC),
        ParseError::AvtabType(Token::TM),
        ParseError::GenfsCon
    )
}
