use std::collections::HashMap;

const INNER_TYPE_SEPARATOR: char = '<';
const INNER_TYPE_END: char = '>';
pub static STATIC_API_SUFFIX: &str = "<StaticApi>";
pub static PLACEHOLDER_INPUT_TYPE_NAME: &str = "PlaceholderInput";

#[derive(Clone, Default, Debug)]
pub struct RustTypeString {
    type_name: String,          // used for return types
    default_value_expr: String, // used for arguments
    contains_custom_types: bool,
}

impl RustTypeString {
    pub fn get_default_value_expr(&self) -> &str {
        &self.default_value_expr
    }
}

lazy_static! {
    static ref ABI_TYPES_TO_RUST_TYPES_MAP: HashMap<&'static str, RustTypeString> =
        init_rust_types_map();
}

fn init_rust_types_map() -> HashMap<&'static str, RustTypeString> {
    let mut m = HashMap::new();

    m.insert(
        "u8",
        RustTypeString {
            type_name: "u8".to_string(),
            default_value_expr: "0u8".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "u16",
        RustTypeString {
            type_name: "u16".to_string(),
            default_value_expr: "0u16".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "u32",
        RustTypeString {
            type_name: "u32".to_string(),
            default_value_expr: "0u32".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "u64",
        RustTypeString {
            type_name: "u64".to_string(),
            default_value_expr: "0u64".to_string(),
            contains_custom_types: false,
        },
    );

    m.insert(
        "Address",
        RustTypeString {
            type_name: "ManagedAddress".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "ManagedAddress::<StaticApi>::zero()".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "BigUint",
        RustTypeString {
            type_name: "BigUint".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "BigUint::<StaticApi>::from(0u128)".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "bytes",
        RustTypeString {
            type_name: "ManagedBuffer".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "ManagedBuffer::new_from_bytes(&b\"\"[..])".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "EsdtTokenIdentifier",
        RustTypeString {
            type_name: "EsdtTokenIdentifier".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "EsdtTokenIdentifier::from_esdt_bytes(&b\"\"[..])".to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "EgldOrEsdtTokenIdentifier",
        RustTypeString {
            type_name: "EgldOrEsdtTokenIdentifier".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "EgldOrEsdtTokenIdentifier::esdt(&b\"\"[..])".to_string(),
            contains_custom_types: false,
        },
    );

    m.insert(
        "EsdtTokenPayment",
        RustTypeString {
            type_name: "EsdtTokenPayment".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "EsdtTokenPayment::new(
            EsdtTokenIdentifier::from_esdt_bytes(&b\"\"[..]),
            0u64,
            BigUint::from(0u128),
        )"
            .to_string(),
            contains_custom_types: false,
        },
    );
    m.insert(
        "EgldOrEsdtTokenPayment",
        RustTypeString {
            type_name: "EgldOrEsdtTokenPayment".to_string() + STATIC_API_SUFFIX,
            default_value_expr: "EgldOrEsdtTokenPayment::new(
            EgldOrEsdtTokenIdentifier::esdt(&b\"\"[..]),
            0u64,
            BigUint::from(0u128),
        )"
            .to_string(),
            contains_custom_types: false,
        },
    );

    m
}

enum AbiType {
    UserDefined(String),
    Basic(RustTypeString),
    Variadic(String),
    Optional(String),
    Multi(String),
    List(String),
    Array(String, String),
    Option(String),
}

fn get_abi_type(abi_type_str: &str) -> AbiType {
    let opt_inner_type_start = abi_type_str.find(INNER_TYPE_SEPARATOR);
    if opt_inner_type_start.is_none() {
        let separated_str: Vec<&str> = abi_type_str.split(INNER_TYPE_END).collect();
        let type_name = separated_str[0];
        let opt_basic_type = ABI_TYPES_TO_RUST_TYPES_MAP.get(type_name);
        return match opt_basic_type {
            Some(basic_type) => AbiType::Basic(basic_type.clone()),
            None => AbiType::UserDefined(type_name.to_string()),
        };
    }

    let inner_type_start = unsafe { opt_inner_type_start.unwrap_unchecked() };
    let (complex_type_name, inner_types) = abi_type_str.split_at(inner_type_start);

    // skip the '<' character
    let inner_type_str = inner_types[1..].to_string();
    if complex_type_name.starts_with("array") {
        let array_type_name_len = "array".len();
        let (_, array_size_str) = complex_type_name.split_at(array_type_name_len);

        return AbiType::Array(array_size_str.to_string(), inner_type_str);
    }

    match complex_type_name {
        "variadic" => AbiType::Variadic(inner_type_str),
        "optional" => AbiType::Optional(inner_type_str),
        "Option" => AbiType::Option(inner_type_str),
        "multi" => AbiType::Multi(inner_type_str),
        "List" => AbiType::List(inner_type_str),
        _ => AbiType::UserDefined(inner_type_str),
    }
}

pub fn handle_abi_type(type_string: &mut RustTypeString, abi_type_str: String) {
    let abi_type = get_abi_type(&abi_type_str);
    match abi_type {
        AbiType::UserDefined(user_type) => {
            // most user-defined types contain managed types
            type_string.type_name += &format!("{}{}", user_type, STATIC_API_SUFFIX);
            type_string.default_value_expr +=
                &format!("{}::{}::default()", user_type, STATIC_API_SUFFIX);
            type_string.contains_custom_types = true;
        }
        AbiType::Basic(basic_type) => {
            type_string.type_name += &basic_type.type_name;
            type_string.default_value_expr += &basic_type.default_value_expr;
        }
        AbiType::Variadic(inner_types) => handle_variadic_type(type_string, inner_types),
        AbiType::Optional(inner_types) => handle_optional_type(type_string, inner_types),
        AbiType::Multi(inner_types) => handle_multi_type(type_string, inner_types),
        AbiType::List(inner_types) => handle_list_type(type_string, inner_types),
        AbiType::Array(array_size, inner_types) => {
            handle_array_type(type_string, array_size, inner_types)
        }
        AbiType::Option(inner_types) => handle_option_type(type_string, inner_types),
    }
}

fn handle_variadic_type(type_string: &mut RustTypeString, inner_types: String) {
    type_string.type_name += "MultiValueVec<";
    let mut inner_type_string = RustTypeString::default();

    handle_abi_type(&mut inner_type_string, inner_types);

    type_string.type_name += &inner_type_string.type_name;
    type_string.type_name += ">";

    if inner_type_string.contains_custom_types {
        type_string.default_value_expr += "MultiValueVec::<";
        type_string.default_value_expr += &inner_type_string.type_name;
        type_string.default_value_expr += ">::new()";
    } else {
        type_string.default_value_expr += "MultiValueVec::from(vec![";
        type_string.default_value_expr += &inner_type_string.default_value_expr;
        type_string.default_value_expr += "])";
    }

    type_string.contains_custom_types |= inner_type_string.contains_custom_types;
}

fn handle_optional_type(type_string: &mut RustTypeString, inner_types: String) {
    type_string.type_name += "OptionalValue<";
    type_string.default_value_expr += "OptionalValue::Some(";

    handle_abi_type(type_string, inner_types);

    type_string.type_name += ">";
    type_string.default_value_expr += ")";
}

fn handle_multi_type(type_string: &mut RustTypeString, inner_types: String) {
    let multi_type_end_index = inner_types.find(INNER_TYPE_END).unwrap();
    let inner_multi_types: Vec<&str> = inner_types[..multi_type_end_index].split(',').collect();
    let inner_multi_types_len = inner_multi_types.len();

    // "MultiValueN::<types>::from((x, y, z))"
    let mut type_name_parts = Vec::new();
    let mut default_value_expr_parts = Vec::new();
    let mut contains_custom_types = false;

    for (i, multi_type) in inner_multi_types.iter().enumerate() {
        let trimmed = multi_type.trim();
        let mut inner_type_string = RustTypeString::default();

        handle_abi_type(&mut inner_type_string, trimmed.to_string());

        type_name_parts.push(inner_type_string.type_name.clone());

        if inner_type_string.contains_custom_types {
            contains_custom_types = true
        }
        default_value_expr_parts.push(inner_type_string.default_value_expr);

        if i < inner_multi_types_len - 1 {
            default_value_expr_parts.push(", ".to_string());
        }
    }

    // construct the type name
    let type_name = format!(
        "MultiValue{}::<{}>",
        inner_multi_types_len,
        type_name_parts.join(", ")
    );

    // construct the default value expression
    let default_value_expr = format!(
        "{}::from(({}))",
        type_name,
        default_value_expr_parts.join("")
    );

    type_string.type_name = type_name;
    type_string.default_value_expr = default_value_expr;
    type_string.contains_custom_types |= contains_custom_types;
}

fn handle_list_type(type_string: &mut RustTypeString, inner_types: String) {
    type_string.type_name += "ManagedVec<StaticApi, ";
    let mut inner_type_string = RustTypeString::default();

    handle_abi_type(&mut inner_type_string, inner_types);

    type_string.type_name += &inner_type_string.type_name;
    type_string.type_name += ">";

    if inner_type_string.contains_custom_types {
        type_string.default_value_expr += "ManagedVec::<StaticApi, ";
        type_string.default_value_expr += &inner_type_string.type_name;
        type_string.default_value_expr += ">::new()";
    } else {
        type_string.default_value_expr += "ManagedVec::from_single_item(";
        type_string.default_value_expr += &inner_type_string.default_value_expr;
        type_string.default_value_expr += ")";
    }

    type_string.contains_custom_types |= inner_type_string.contains_custom_types;
}

fn handle_array_type(type_string: &mut RustTypeString, array_size: String, inner_types: String) {
    type_string.type_name += "[";
    type_string.default_value_expr += "[";

    handle_abi_type(type_string, inner_types);

    type_string.type_name += ";";
    type_string.type_name += &array_size;
    type_string.type_name += "]";

    type_string.default_value_expr += ";";
    type_string.default_value_expr += &array_size;
    type_string.default_value_expr += "]";
}

fn handle_option_type(type_string: &mut RustTypeString, inner_types: String) {
    type_string.type_name += "Option<";
    type_string.default_value_expr += "Option::Some(";

    handle_abi_type(type_string, inner_types);

    type_string.type_name += ">";
    type_string.default_value_expr += ")";
}

pub(crate) fn map_abi_type_to_rust_type(abi_type: String) -> RustTypeString {
    let mut type_string = RustTypeString::default();
    handle_abi_type(&mut type_string, abi_type);
    type_string
}
