mod item_impl;
mod item_struct;

use syn::{GenericArgument, Item, Path};

use crate::{
    util::{create_angle_bracketed_path_arguments, create_type_path},
    wir::{
        IntoSyn, WDescription, WElementaryType, WExpr, WExprCall, WGeneralType, WIdent,
        WItemImplTrait, WPanicResult, WPanicResultType, WPath, WSsaLocal, WStmt, WType, YConverted,
        YStage, ZAssignTypes, ZIfPolarity,
    },
};

use self::{
    item_impl::{preprocess_item_impl, process_item_impl},
    item_struct::process_item_struct,
};

#[derive(Clone, Debug, Hash)]
pub struct YAbstr;

#[derive(Clone, Debug, Hash)]
pub struct ZAbstrIfPolarity(pub bool);

impl IntoSyn<Path> for ZAbstrIfPolarity {
    fn into_syn(self) -> Path {
        if self.0 {
            syn_path::path!(::mck::forward::Test::can_be_true)
        } else {
            syn_path::path!(::mck::forward::Test::can_be_false)
        }
    }
}

impl ZIfPolarity for ZAbstrIfPolarity {}

#[derive(Clone, Debug, Hash)]
pub struct ZAbstr;

impl ZAssignTypes for ZAbstr {
    type Stmt = WStmt<ZAbstr>;
    type FundamentalType = WElementaryType;
    type AssignLeft = WIdent;
    type AssignRight = WExpr<WExprCall>;
    type IfPolarity = ZAbstrIfPolarity;
}

impl YStage for YAbstr {
    type AssignTypes = ZAbstr;
    type InputType = WType<WElementaryType>;
    type OutputType = WPanicResultType<WElementaryType>;
    type FnResult = WPanicResult;
    type Local = WSsaLocal<WGeneralType<WElementaryType>>;
    type ItemImplTrait = WAbstrItemImplTrait;
}

#[derive(Clone, Debug, Hash)]
pub struct WAbstrItemImplTrait {
    pub machine_type: WPath,
    pub trait_: WItemImplTrait,
}

impl IntoSyn<Path> for WAbstrItemImplTrait {
    fn into_syn(self) -> Path {
        let mut trait_path = self.trait_.into_syn();
        trait_path.segments.last_mut().unwrap().arguments = create_angle_bracketed_path_arguments(
            false,
            vec![GenericArgument::Type(create_type_path(
                self.machine_type.clone().into(),
            ))],
            self.machine_type.span(),
        );
        trait_path
    }
}

pub(crate) fn create_abstract_description(
    description: WDescription<YConverted>,
) -> (WDescription<YAbstr>, Vec<Item>) {
    let mut machine_types = Vec::new();
    for item_impl in description.impls.iter() {
        if let Some(ty) = preprocess_item_impl(item_impl) {
            machine_types.push(ty);
        }
    }

    let mut misc_items = Vec::new();

    let mut abstract_description = WDescription::<YAbstr> {
        structs: Vec::new(),
        impls: Vec::new(),
    };

    for item_struct in description.structs {
        let (item_struct, other_impls) = process_item_struct(item_struct);
        abstract_description.structs.push(item_struct);
        misc_items.extend(other_impls.into_iter().map(Item::Impl));
    }

    for item_impl in description.impls {
        let item_impls = process_item_impl(item_impl, &machine_types);
        abstract_description.impls.extend(item_impls);
    }

    (abstract_description, misc_items)
}
