use core::fmt;

use common::build::BuildError;

#[allow(unused)]
#[derive(Debug, Clone)]
pub enum CompilerError {
    UnexpectedToken(String),
    UnexpectedEndOfInput,
    UnexpectedCharacter(char),
    Undefined(Name),
    InvalidSyntax(String),
    Generic(String),
    UnknownType,
    TypeMismatch(TypeId, TypeId),
    Unimplemented(String),
}

impl From<CompilerError> for BuildError {
    fn from(err: CompilerError) -> Self {
        BuildError::Generic(format!("{:?}", err))
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Name {
    pub name: String,
    pub namespace: Option<String>,
}
impl Name {
    pub fn new(name: impl Into<String>, namespace: Option<String>) -> Self {
        Self {
            name: name.into(),
            namespace,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Program {
    pub declarations: Vec<Declaration>,
}

#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Declaration {
    Function {
        name: String,
        return_type: TypeId,
        params: Vec<Variable>,
        body: Block,
    },
    Variable {
        var: Variable,
        init: Option<ConstExpr>,
        is_const: bool,
    },
    Dependency(Dependency),
    Struct {
        name: Name,
        fields: Vec<Variable>,
    },
}

#[derive(Debug, Clone)]
pub struct Dependency {
    pub name: String,
    pub path: String,
}

#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub enum TypeId {
    U8,
    U16,
    U32,
    I8,
    I16,
    I32,
    Bool,
    Char,
    Void,
    Ptr(Box<TypeId>),
    Ref(Box<TypeId>),
    Tuple(Vec<TypeId>),
    Array {
        r#type: Box<TypeId>,
        size: usize,
    },
    UnknownCustom {
        name: Name,
        generics: Vec<TypeId>,
    },
    Struct {
        name: Name,
        fields: Vec<TypeId>,
        generics: Vec<TypeId>,
    },
}

impl TypeId {
    pub fn size(&self) -> usize {
        match self {
            Self::U8 => 1,
            Self::U16 => 2,
            Self::U32 => 4,
            Self::I8 => 1,
            Self::I16 => 2,
            Self::I32 => 4,
            Self::Bool => 1,
            Self::Char => 1,
            Self::Void => 0,
            Self::Ptr(t) => t.size(),
            Self::Ref(t) => t.size(),
            Self::Tuple(types) => types.iter().map(|t| t.size()).sum(),
            Self::Array { r#type, size } => r#type.size() * size,
            Self::UnknownCustom { .. } => 1, /* TODO: calculate type size during */
            // semantic analysis
            Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(),
        }
    }
}

impl fmt::Display for TypeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::U8 => write!(f, "u8"),
            Self::U16 => write!(f, "u16"),
            Self::U32 => write!(f, "u32"),
            Self::I8 => write!(f, "i8"),
            Self::I16 => write!(f, "i16"),
            Self::I32 => write!(f, "i32"),
            Self::Bool => write!(f, "bool"),
            Self::Char => write!(f, "char"),
            Self::Void => write!(f, "void"),
            Self::Ptr(t) => write!(f, "*{}", t),
            Self::Ref(t) => write!(f, "&{}", t),
            Self::Tuple(elems) => write!(
                f,
                "({})",
                elems
                    .iter()
                    .map(|t| t.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
            Self::Array { r#type, size } => write!(f, "[{}; {}]", r#type, size),
            Self::UnknownCustom { name, generics } => {
                write!(
                    f,
                    "{}<{}>",
                    name,
                    generics
                        .iter()
                        .map(|t| t.to_string())
                        .collect::<Vec<String>>()
                        .join(", ")
                )
            }
            Self::Struct {
                name,
                fields,
                generics,
            } => write!(
                f,
                "struct<{}> {} {{{}}}",
                generics
                    .iter()
                    .map(|t| t.to_string())
                    .collect::<Vec<String>>()
                    .join(", "),
                name,
                fields
                    .iter()
                    .map(|t| t.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
        }
    }
}

pub type Block = Vec<Statement>;

#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub struct Variable {
    pub name: String,
    pub type_id: TypeId,
}

#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Statement {
    Block(Block),
    Declaration {
        var: Variable,
        value: Option<Expression>,
    },
    Assign {
        varname: String,
        operator: AssignmentOperator,
        value: Expression,
    },
    PtrWrite {
        ptr: Expression,
        value: Expression,
    },
    Expression {
        expr: Expression,
    },
    If {
        condition: Expression,
        then_stmt: Block,
        else_stmt: Block,
    },
    While {
        condition: Expression,
        body: Vec<Statement>,
    },
    Loop(Block),
    Defer(Call),
    Break,
    Continue,
    Return(Option<Expression>),
}

#[derive(Debug, Clone)]
pub enum ConstExpr {
    Number(i32),
    String(String),
}

impl fmt::Display for ConstExpr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ConstExpr::Number(n) => write!(f, "{}", n),
            ConstExpr::String(s) => write!(f, "\"{}\"", s),
        }
    }
}

#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Expression {
    Empty,
    Binary {
        op: BinaryOperator,
        left: Box<Expression>,
        right: Box<Expression>,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    Unary {
        op: UnaryOperator,
        operand: Box<Expression>,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    UnaryPostfix {
        op: UnaryOperator,
        operand: Box<Expression>,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    Variable {
        name: Name,
        expr_type: Option<TypeId>,
    },
    TypeCast {
        expr: Box<Expression>,
        target_type: TypeId,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    IndexAccess {
        expr: Box<Expression>,
        index: Box<Expression>,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    MemberAccess {
        expr: Box<Expression>,
        field_name: Name,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    Call {
        func: Call,

        // Post-Semantic Analysis
        type_id: Option<TypeId>,
    },
    Number(Number),
    StringLiteral(String),
    CharLiteral(char),
    ArrayLiteral {
        elements: Vec<Expression>,
        type_id: Option<TypeId>,
    },
    StructLiteral {
        name: Name,
        fields: Vec<(Name, Expression)>,
        type_id: Option<TypeId>,
    },
}

#[derive(Debug, Clone)]
pub enum Number {
    Signed(i32, Option<TypeId>),
    Unsigned(u32, Option<TypeId>),
}

#[derive(Debug, Clone)]
pub struct Call {
    pub name: Name,
    pub args: Vec<Expression>,
}

impl Expression {
    pub fn is_pure(&self) -> bool {
        match self {
            Expression::Number { .. } => true,
            Expression::StringLiteral(_) => true,
            Expression::CharLiteral(_) => true,
            Expression::Call { .. } => false,
            Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
            Expression::Unary { operand, .. } => operand.is_pure(),
            Expression::UnaryPostfix { operand, .. } => operand.is_pure(),
            Expression::Empty => true,
            Expression::Variable { .. } => true,
            Expression::TypeCast { expr, .. } => expr.is_pure(),
            Expression::IndexAccess { expr, index, .. } => {
                expr.is_pure() && index.is_pure()
            }
            Expression::MemberAccess { expr, .. } => expr.is_pure(),
            Expression::ArrayLiteral { elements, .. } => {
                elements.iter().all(|element| element.is_pure())
            }
            Expression::StructLiteral { fields, .. } => {
                fields.iter().all(|(_, expr)| expr.is_pure())
            }
        }
    }

    pub fn type_id(&self) -> Result<TypeId, CompilerError> {
        match self {
            Expression::Number(
                Number::Signed(_, type_id) | Number::Unsigned(_, type_id),
            ) => type_id.clone().ok_or(CompilerError::UnknownType),
            Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))),
            Expression::CharLiteral(_) => Ok(TypeId::Char),
            Expression::Call { type_id, .. } => {
                type_id.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::Binary { type_id, .. } => {
                type_id.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::Unary { type_id, .. } => {
                type_id.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::UnaryPostfix { type_id, .. } => {
                type_id.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::Empty => Ok(TypeId::Void),
            Expression::Variable { expr_type, .. } => {
                expr_type.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::TypeCast { type_id, .. } => {
                type_id.clone().ok_or(CompilerError::UnknownType)
            }
            Expression::IndexAccess { expr, .. } => expr.type_id(),
            Expression::MemberAccess { expr, .. } => expr.type_id(),
            Expression::ArrayLiteral { elements, .. } => {
                let element_type = elements
                    .first()
                    .map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void));
                Ok(TypeId::Array {
                    r#type: Box::new(element_type),
                    size: elements.len(),
                })
            }
            Expression::StructLiteral { name, fields, .. } => {
                let fields = fields
                    .iter()
                    .map(|(_, expr)| expr.type_id())
                    .collect::<Result<Vec<_>, _>>()?;
                Ok(TypeId::Struct {
                    name: name.clone(),
                    fields,
                    generics: Vec::new(),
                })
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum AssignmentOperator {
    Assign,
    AddAssign,
    SubAssign,
    MulAssign,
    DivAssign,
    ModAssign,
    AndAssign,
    OrAssign,
    XorAssign,
    LeftShiftAssign,
    RightShiftAssign,
}

#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
    // arithmetic
    Add,
    Sub,
    Mul,
    Div,
    Mod,

    // comparison
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    LessOrEqual,
    GreaterOrEqual,

    // bitwise
    BitwiseAnd,
    BitwiseOr,
    BitwiseXor,

    // logical
    LogicalAnd,
    LogicalOr,

    // shift
    LeftShift,
    RightShift,
}

impl fmt::Display for BinaryOperator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Add => write!(f, "+"),
            Self::Sub => write!(f, "-"),
            Self::Mul => write!(f, "*"),
            Self::Div => write!(f, "/"),
            Self::Mod => write!(f, "%"),
            Self::Equal => write!(f, "=="),
            Self::NotEqual => write!(f, "!="),
            Self::LessThan => write!(f, "<"),
            Self::GreaterThan => write!(f, ">"),
            Self::LessOrEqual => write!(f, "<="),
            Self::GreaterOrEqual => write!(f, ">="),
            Self::BitwiseAnd => write!(f, "&"),
            Self::BitwiseOr => write!(f, "|"),
            Self::BitwiseXor => write!(f, "^"),
            Self::LogicalAnd => write!(f, "&&"),
            Self::LogicalOr => write!(f, "||"),
            Self::LeftShift => write!(f, "<<"),
            Self::RightShift => write!(f, ">>"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
    Plus,
    Minus,
    AddressOf,
    Dereference,
    BitwiseNot,
    LogicalNot,
    Increment,
    Decrement,
    SizeOf,
}

impl fmt::Display for UnaryOperator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Increment => write!(f, "++"),
            Self::Decrement => write!(f, "--"),
            Self::Plus => write!(f, "+"),
            Self::Minus => write!(f, "-"),
            Self::Dereference => write!(f, "*"),
            Self::AddressOf => write!(f, "&"),
            Self::BitwiseNot => write!(f, "~"),
            Self::LogicalNot => write!(f, "!"),
            Self::SizeOf => write!(f, "sizeof"),
        }
    }
}
