use ecow::EcoString;
use num_bigint::BigInt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IncompleteType {
    Int,
    Float,
    Bool,
    String,
    Func {
        arguments: Vec<IncompleteType>,
        returns: Box<IncompleteType>,
    },
    Struct {
        elements: Vec<IncompleteType>,
    },
    List(Box<IncompleteType>),
    Generic {
        id: u64,
    },
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CompleteType {
    Int,
    Float,
    Bool,
    String,
    Func {
        arguments: Vec<CompleteType>,
        returns: Box<CompleteType>,
    },
    Struct {
        elements: Vec<CompleteType>,
    },
    List(Box<CompleteType>),
}

#[derive(Debug, PartialEq, Eq, Default)]
pub struct Module<T> {
    pub name: EcoString,
    pub functions: Vec<Function<T>>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Function<T> {
    pub name: EcoString,
    pub return_type: T,
    pub parameters: Vec<FunctionParameter<T>>,
    pub body: Expression<T>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct FunctionParameter<T> {
    pub type_: T,
    pub name: Option<EcoString>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Var<T> {
    pub name: EcoString,
    pub type_: T,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Expression<T> {
    Block(Vec<Expression<T>>),

    FunctionRef {
        module: EcoString,
        name: EcoString,
        arity: usize,
        type_: T,
    },

    Var(Var<T>),

    Int {
        value: BigInt,
    },

    Float {
        value: EcoString,
    },

    Bool {
        value: bool,
    },

    String {
        value: EcoString,
    },

    Equals {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    NotEquals {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntGt {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntGtEq {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntLt {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntLtEq {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntAdd {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntSub {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntMul {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntDiv {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    IntRem {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatGt {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatGtEq {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatLt {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatLtEq {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatAdd {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatSub {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatMul {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    FloatDiv {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    StringConcat {
        lhs: Box<Expression<T>>,
        rhs: Box<Expression<T>>,
    },

    List {
        items: Vec<Expression<T>>,
        tail: Option<Box<Expression<T>>>,
        type_: T,
    },

    Struct {
        tag: Option<u32>,
        items: Vec<Expression<T>>,
        type_: T,
    },

    StructTag {
        value: Box<Expression<T>>,
    },

    StructAccess {
        value: Box<Expression<T>>,
        index: u64,
        type_: T,
    },

    Set {
        name: Var<T>,
        value: Box<Expression<T>>,
    },

    If {
        cond: Box<Expression<T>>,
        then: Box<Expression<T>>,
        else_: Box<Expression<T>>,
    },

    Call {
        target: Box<Expression<T>>,
        args: Vec<Expression<T>>,
        type_: T,
    },
}

impl Expression<IncompleteType> {
    pub fn type_(&self) -> IncompleteType {
        match self {
            Expression::Block(expressions) => expressions
                .last()
                .expect("a block should not be empty")
                .type_(),
            Expression::FunctionRef { type_, .. } => type_.clone(),
            Expression::Var(var) => var.type_.clone(),
            Expression::Int { .. } => IncompleteType::Int,
            Expression::Float { .. } => IncompleteType::Float,
            Expression::Bool { .. } => IncompleteType::Bool,
            Expression::String { .. } => IncompleteType::String,
            Expression::Equals { .. } => IncompleteType::Bool,
            Expression::NotEquals { .. } => IncompleteType::Bool,
            Expression::IntGt { .. } => IncompleteType::Bool,
            Expression::IntGtEq { .. } => IncompleteType::Bool,
            Expression::IntLt { .. } => IncompleteType::Bool,
            Expression::IntLtEq { .. } => IncompleteType::Bool,
            Expression::IntAdd { .. } => IncompleteType::Int,
            Expression::IntSub { .. } => IncompleteType::Int,
            Expression::IntMul { .. } => IncompleteType::Int,
            Expression::IntDiv { .. } => IncompleteType::Int,
            Expression::IntRem { .. } => IncompleteType::Int,
            Expression::FloatGt { .. } => IncompleteType::Bool,
            Expression::FloatGtEq { .. } => IncompleteType::Bool,
            Expression::FloatLt { .. } => IncompleteType::Bool,
            Expression::FloatLtEq { .. } => IncompleteType::Bool,
            Expression::FloatAdd { .. } => IncompleteType::Bool,
            Expression::FloatSub { .. } => IncompleteType::Bool,
            Expression::FloatMul { .. } => IncompleteType::Bool,
            Expression::FloatDiv { .. } => IncompleteType::Bool,
            Expression::StringConcat { .. } => IncompleteType::Bool,
            Expression::List { type_, .. } => type_.clone(),
            Expression::Struct { type_, .. } => type_.clone(),
            Expression::StructTag { .. } => IncompleteType::Int,
            Expression::StructAccess { type_, .. } => type_.clone(),
            Expression::Set { value, .. } => value.type_(),
            Expression::If { then, .. } => then.type_(),
            Expression::Call { type_, .. } => type_.clone(),
        }
    }
}
