use crate::{Basic, EncodingFormat, Error, Fd, ObjectPath, Signature};

/// The prefix of ARRAY type signature, as a character. Provided for manual signature creation.
pub const ARRAY_SIGNATURE_CHAR: char = 'a';
/// The prefix of ARRAY type signature, as a string. Provided for manual signature creation.
pub const ARRAY_SIGNATURE_STR: &str = "a";
pub(crate) const ARRAY_ALIGNMENT: usize = 4;
/// The opening character of STRUCT type signature. Provided for manual signature creation.
pub const STRUCT_SIG_START_CHAR: char = '(';
/// The closing character of STRUCT type signature. Provided for manual signature creation.
pub const STRUCT_SIG_END_CHAR: char = ')';
/// The opening character of STRUCT type signature, as a string. Provided for manual signature creation.
pub const STRUCT_SIG_START_STR: &str = "(";
/// The closing character of STRUCT type signature, as a string. Provided for manual signature creation.
pub const STRUCT_SIG_END_STR: &str = ")";
pub(crate) const STRUCT_ALIGNMENT: usize = 8;
/// The opening character of DICT_ENTRY type signature. Provided for manual signature creation.
pub const DICT_ENTRY_SIG_START_CHAR: char = '{';
/// The closing character of DICT_ENTRY type signature. Provided for manual signature creation.
pub const DICT_ENTRY_SIG_END_CHAR: char = '}';
/// The opening character of DICT_ENTRY type signature, as a string. Provided for manual signature creation.
pub const DICT_ENTRY_SIG_START_STR: &str = "{";
/// The closing character of DICT_ENTRY type signature, as a string. Provided for manual signature creation.
pub const DICT_ENTRY_SIG_END_STR: &str = "}";
pub(crate) const DICT_ENTRY_ALIGNMENT: usize = 8;
pub(crate) const VARIANT_SIGNATURE_CHAR: char = 'v';
pub(crate) const VARIANT_SIGNATURE_STR: &str = "v";
pub(crate) const VARIANT_ALIGNMENT: usize = 1;

pub(crate) fn padding_for_n_bytes(value: usize, align: usize) -> usize {
    let len_rounded_up = value.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);

    len_rounded_up.wrapping_sub(value)
}

pub(crate) fn usize_to_u32(value: usize) -> u32 {
    if value > (std::u32::MAX as usize) {
        panic!("{} too large for `u32`", value);
    }

    value as u32
}

pub(crate) fn usize_to_u8(value: usize) -> u8 {
    if value > (std::u8::MAX as usize) {
        panic!("{} too large for `u8`", value);
    }

    value as u8
}

pub(crate) fn f64_to_f32(value: f64) -> f32 {
    if value > (std::f32::MAX as f64) {
        panic!("{} too large for `f32`", value);
    }

    value as f32
}

pub(crate) fn alignment_for_signature_char(signature_char: char, _format: EncodingFormat) -> usize {
    match signature_char {
        u8::SIGNATURE_CHAR => u8::ALIGNMENT,
        bool::SIGNATURE_CHAR => bool::ALIGNMENT,
        i16::SIGNATURE_CHAR => i16::ALIGNMENT,
        u16::SIGNATURE_CHAR => u16::ALIGNMENT,
        i32::SIGNATURE_CHAR => i32::ALIGNMENT,
        u32::SIGNATURE_CHAR | Fd::SIGNATURE_CHAR => u32::ALIGNMENT,
        i64::SIGNATURE_CHAR => i64::ALIGNMENT,
        u64::SIGNATURE_CHAR => u64::ALIGNMENT,
        f64::SIGNATURE_CHAR => f64::ALIGNMENT,
        <&str>::SIGNATURE_CHAR => <&str>::ALIGNMENT,
        ObjectPath::SIGNATURE_CHAR => ObjectPath::ALIGNMENT,
        Signature::SIGNATURE_CHAR => Signature::ALIGNMENT,
        VARIANT_SIGNATURE_CHAR => VARIANT_ALIGNMENT,
        ARRAY_SIGNATURE_CHAR => ARRAY_ALIGNMENT,
        STRUCT_SIG_START_CHAR => STRUCT_ALIGNMENT,
        DICT_ENTRY_SIG_START_CHAR => DICT_ENTRY_ALIGNMENT,
        _ => {
            println!("WARNING: Unsupported signature: {}", signature_char);

            0
        }
    }
}

pub(crate) fn slice_signature<'a>(signature: &'a Signature<'a>) -> Result<Signature<'a>, Error> {
    match signature
        .chars()
        .next()
        .ok_or_else(|| serde::de::Error::invalid_length(0, &">= 1 character"))?
    {
        u8::SIGNATURE_CHAR
        | bool::SIGNATURE_CHAR
        | i16::SIGNATURE_CHAR
        | u16::SIGNATURE_CHAR
        | i32::SIGNATURE_CHAR
        | u32::SIGNATURE_CHAR
        | i64::SIGNATURE_CHAR
        | u64::SIGNATURE_CHAR
        | f64::SIGNATURE_CHAR
        | <&str>::SIGNATURE_CHAR
        | ObjectPath::SIGNATURE_CHAR
        | Signature::SIGNATURE_CHAR
        | Fd::SIGNATURE_CHAR
        | VARIANT_SIGNATURE_CHAR => Ok(Signature::from_str_unchecked(&signature[0..1])),
        ARRAY_SIGNATURE_CHAR => slice_array_signature(signature),
        STRUCT_SIG_START_CHAR => slice_structure_signature(signature),
        DICT_ENTRY_SIG_START_CHAR => slice_dict_entry_signature(signature),
        c => Err(serde::de::Error::invalid_value(
            serde::de::Unexpected::Char(c),
            &"a valid signature character",
        )),
    }
}

// Given an &str, create an owned (String-based) Signature w/ appropriate capacity
macro_rules! signature_string {
    ($signature:expr) => {{
        let mut s = String::with_capacity(255);
        s.push_str($signature);

        Signature::from_string_unchecked(s)
    }};
}

macro_rules! check_child_value_signature {
    ($expected_signature:expr, $child_signature:expr, $child_name:literal) => {{
        if $child_signature != $expected_signature {
            let unexpected = format!("{} with signature `{}`", $child_name, $child_signature,);
            let expected = format!("{} with signature `{}`", $child_name, $expected_signature);

            return Err(serde::de::Error::invalid_type(
                serde::de::Unexpected::Str(&unexpected),
                &expected.as_str(),
            ));
        }
    }};
}

fn slice_array_signature<'a>(signature: &'a Signature<'a>) -> Result<Signature<'a>, Error> {
    if signature.len() < 2 {
        return Err(serde::de::Error::invalid_length(
            signature.len(),
            &">= 2 characters",
        ));
    }

    // We can't get None here cause we already established there is are least 2 chars above
    let c = signature.chars().next().expect("empty signature");
    if c != ARRAY_SIGNATURE_CHAR {
        return Err(serde::de::Error::invalid_value(
            serde::de::Unexpected::Char(c),
            &crate::ARRAY_SIGNATURE_STR,
        ));
    }

    // There should be a valid complete signature after 'a' but not more than 1
    let slice_len = slice_signature(&Signature::from_str_unchecked(&signature[1..]))?.len();

    Ok(Signature::from_str_unchecked(&signature[0..=slice_len]))
}

fn slice_structure_signature<'a>(signature: &'a Signature<'a>) -> Result<Signature<'a>, Error> {
    if signature.len() < 2 {
        return Err(serde::de::Error::invalid_length(
            signature.len(),
            &">= 2 characters",
        ));
    }

    // We can't get None here cause we already established there are at least 2 chars above
    let c = signature.chars().next().expect("empty signature");
    if c != STRUCT_SIG_START_CHAR {
        return Err(serde::de::Error::invalid_value(
            serde::de::Unexpected::Char(c),
            &crate::STRUCT_SIG_START_STR,
        ));
    }

    let mut open_braces = 1;
    let mut i = 1;
    while i < signature.len() {
        if &signature[i..=i] == STRUCT_SIG_END_STR {
            open_braces -= 1;

            if open_braces == 0 {
                break;
            }
        } else if &signature[i..=i] == STRUCT_SIG_START_STR {
            open_braces += 1;
        }

        i += 1;
    }
    let end = &signature[i..=i];
    if end != STRUCT_SIG_END_STR {
        return Err(serde::de::Error::invalid_value(
            serde::de::Unexpected::Str(end),
            &crate::STRUCT_SIG_END_STR,
        ));
    }

    Ok(Signature::from_str_unchecked(&signature[0..=i]))
}

fn slice_dict_entry_signature<'a>(signature: &'a Signature<'a>) -> Result<Signature<'a>, Error> {
    if signature.len() < 4 {
        return Err(serde::de::Error::invalid_length(
            signature.len(),
            &">= 4 characters",
        ));
    }

    // We can't get None here cause we already established there are at least 4 chars above
    let c = signature.chars().next().expect("empty signature");
    if c != DICT_ENTRY_SIG_START_CHAR {
        return Err(serde::de::Error::invalid_value(
            serde::de::Unexpected::Char(c),
            &crate::DICT_ENTRY_SIG_START_STR,
        ));
    }

    // Key's signature will always be just 1 character so no need to slice for that.
    // There should be one valid complete signature for value.
    let slice_len = slice_signature(&Signature::from_str_unchecked(&signature[2..]))?.len();

    // signature of value + `{` + 1 char of the key signature + `}`
    Ok(Signature::from_str_unchecked(&signature[0..slice_len + 3]))
}
