// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(clippy::unwrap_used)]

extern crate std;

use crate::legacy::{
    actions::*, serialize::ToDataElementBundle, Ciphertext, PacketFlavorEnum, Plaintext,
};
use std::collections;

#[test]
fn set_context_sync_works() {
    let mut actions = ActionBits::<Plaintext>::default();

    actions.set_action(ContextSyncSeqNum::try_from(15).unwrap());
    assert_eq_hex(0xF0000000, actions.bits);

    assert_eq!(actions.bits_for_type(&ActionType::ContextSyncSeqNum), 15);
}

#[test]
fn set_context_sync_doesnt_clobber_neighboring_bit() {
    let mut actions = ActionBits::<Plaintext>::default();

    // set bit just below
    actions.bits |= 0x8000000;

    actions.set_action(ContextSyncSeqNum::try_from(15).unwrap());
    assert_eq_hex(0xF8000000, actions.bits);

    assert_eq!(actions.bits_for_type(&ActionType::ContextSyncSeqNum), 15);
}

#[test]
fn unset_context_sync_works() {
    let mut actions = ActionBits::<Plaintext> {
        // all 1s
        bits: u32::MAX,
        ..Default::default()
    };

    actions.set_action(ContextSyncSeqNum::try_from(0).unwrap());
    assert_eq_hex(0x0FFFFFFF, actions.bits);

    assert_eq!(actions.bits_for_type(&ActionType::ContextSyncSeqNum), 0);
}

#[test]
fn set_ns_works() {
    let mut actions = ActionBits::<Plaintext>::default();

    actions.set_action(NearbyShare::from(true));
    assert_eq_hex(0x00400000, actions.bits);

    assert_eq!(actions.bits_for_type(&ActionType::NearbyShare), 1);
    assert_eq!(actions.bits_for_type(&ActionType::ActiveUnlock), 0);
    assert_eq!(actions.bits_for_type(&ActionType::InstantTethering), 0);
}

#[test]
fn set_ns_doesnt_clobber_others() {
    let mut actions = ActionBits::<Plaintext>::default();

    // set neighboring bits
    actions.bits |= 0x00120000;

    actions.set_action(NearbyShare::from(true));
    assert_eq_hex(0x00520000, actions.bits);

    assert_eq!(actions.bits_for_type(&ActionType::NearbyShare), 1);
    assert_eq!(actions.bits_for_type(&ActionType::PhoneHub), 1);
    assert_eq!(actions.bits_for_type(&ActionType::FastPairSass), 1);
}

#[test]
fn unset_ns_works() {
    let mut actions = ActionBits::<Plaintext> {
        // all 1s
        bits: u32::MAX,
        ..Default::default()
    };

    actions.set_action(NearbyShare::from(false));
    assert_eq_hex(0xFFBFFFFF, actions.bits);
}

#[test]
fn set_last_bit_works() {
    let mut actions = ActionBits::<Plaintext>::default();

    actions.set_action(LastBit::from(true));
    assert_eq_hex(0x0100, actions.bits);
}

#[test]
fn set_last_bit_doesnt_clobber_others() {
    let mut actions = ActionBits::<Plaintext>::default();

    // set neighboring bits
    actions.bits |= 0x200;

    actions.set_action(LastBit::from(true));
    assert_eq_hex(0x300, actions.bits);
}

#[test]
fn unset_last_bit_works() {
    let mut actions = ActionBits::<Plaintext> {
        // all 1s
        bits: u32::MAX,
        ..Default::default()
    };

    actions.set_action(LastBit::from(false));
    assert_eq_hex(0xFFFFFEFF, actions.bits);
}

#[test]
fn bytes_used_works() {
    let mut actions = ActionBits::<Plaintext>::default();

    // Special-case: All-zeroes should lead to a single byte being used.
    assert_eq!(1, actions.bytes_used());

    actions.set_action(ContextSyncSeqNum::try_from(3).unwrap());
    assert_eq!(1, actions.bytes_used());

    actions.set_action(NearbyShare::from(true));
    assert_eq!(2, actions.bytes_used());

    actions.set_action(LastBit::from(true));
    assert_eq!(3, actions.bytes_used());

    actions.set_action(LastBit::from(false));
    assert_eq!(2, actions.bytes_used());
}

#[test]
fn write_de_empty_actions() {
    // The special case of no action bits set should still occupy one byte [of all zeroes].
    let de = ActionsDataElement::<Plaintext>::from(ActionBits::default()).to_de_bundle();

    assert_eq!(&[0x00], de.contents_as_slice());
}

#[test]
fn write_de_one_action_byte() {
    let mut action = ActionBits::default();
    action.set_action(ContextSyncSeqNum::try_from(7).unwrap());
    let de = ActionsDataElement::<Plaintext>::from(action).to_de_bundle();

    assert_eq!(&[0x70], de.contents_as_slice());
}

#[test]
fn write_de_three_action_bytes() {
    let mut action = ActionBits::default();
    action.set_action(LastBit::from(true));
    let de = ActionsDataElement::<Plaintext>::from(action).to_de_bundle();

    assert_eq!(&[0, 0, 1], de.contents_as_slice());
}

#[test]
fn write_de_all_plaintext_actions() {
    let mut action = all_plaintext_actions();
    action.set_action(LastBit::from(true));
    let de = ActionsDataElement::<Plaintext>::from(action).to_de_bundle();

    // byte 0: context sync
    // byte 1: nearby share, finder, fp sass
    // byte 2: last bit
    assert_eq!(&[0x90, 0x46, 0x01], de.contents_as_slice());
}

#[test]
fn write_de_all_encrypted_actions() {
    let mut action = all_ciphertext_actions();
    action.set_action(LastBit::from(true));
    let de = ActionsDataElement::<Ciphertext>::from(action).to_de_bundle();

    // byte 1: context sync num = 9, 4 unused bits
    // byte 2: active unlock, nearby share, instant tethering, phone hub,
    // presence manager, last 3 bits unused
    // byte 3: last bit
    assert_eq!(&[0x90, 0xF8, 0x01], de.contents_as_slice());
}

#[test]
fn action_element_nonzero_len() {
    for t in ActionType::iter() {
        assert!(t.bits_len() > 0);
    }
}

#[test]
fn action_element_bits_dont_overlap() {
    let type_to_bits =
        ActionType::iter().map(|t| (t, t.all_bits())).collect::<collections::HashMap<_, _>>();

    for t in ActionType::iter() {
        let bits = type_to_bits.get(&t).unwrap();

        for (_, other_bits) in type_to_bits.iter().filter(|(other_type, _)| t != **other_type) {
            assert_eq!(0, bits & other_bits, "type {t:?}");
        }
    }
}

#[test]
fn action_type_all_bits() {
    assert_eq!(0xF0000000, ActionType::ContextSyncSeqNum.all_bits());
    assert_eq!(0x00800000, ActionType::ActiveUnlock.all_bits());
    assert_eq!(0x00020000, ActionType::FastPairSass.all_bits());
}

#[test]
fn action_type_all_bits_in_per_type_masks() {
    for t in ActionType::iter().filter(|t| t.supports_flavor(PacketFlavorEnum::Plaintext)) {
        assert_eq!(t.all_bits(), t.all_bits() & *ALL_PLAINTEXT_ELEMENT_BITS);
    }
    for t in ActionType::iter().filter(|t| t.supports_flavor(PacketFlavorEnum::Ciphertext)) {
        assert_eq!(t.all_bits(), t.all_bits() & *ALL_CIPHERTEXT_ELEMENT_BITS);
    }
}

#[test]
fn action_bits_try_from_flavor_mismatch_plaintext() {
    assert_eq!(
        FlavorNotSupported { flavor: PacketFlavorEnum::Plaintext },
        ActionBits::<Plaintext>::try_from(ActionType::PresenceManager.all_bits()).unwrap_err()
    );
    assert_eq!(
        0xF0000000,
        ActionBits::<Plaintext>::try_from(ActionType::ContextSyncSeqNum.all_bits()).unwrap().bits
    );
}

#[test]
fn action_bits_try_from_flavor_mismatch_ciphertext() {
    assert_eq!(
        FlavorNotSupported { flavor: PacketFlavorEnum::Ciphertext },
        ActionBits::<Ciphertext>::try_from(ActionType::Finder.all_bits()).unwrap_err()
    );
    assert_eq!(
        0xF0000000,
        ActionBits::<Ciphertext>::try_from(ActionType::ContextSyncSeqNum.all_bits()).unwrap().bits
    );
}

#[test]
fn actions_de_deser_plaintext_with_ciphertext_action() {
    assert_eq!(
        DataElementDeserializeError::FlavorNotSupported {
            de_type: DataElementType::Actions,
            flavor: PacketFlavorEnum::Plaintext
        },
        <ActionsDataElement<Plaintext> as DataElement>::deserialize::<Plaintext>(&[
            // active unlock bit set
            0x00, 0x80, 0x00,
        ])
        .unwrap_err()
    );
}

#[test]
fn actions_de_deser_ciphertext_with_plaintext_action() {
    assert_eq!(
        DataElementDeserializeError::FlavorNotSupported {
            de_type: DataElementType::Actions,
            flavor: PacketFlavorEnum::Ciphertext
        },
        <ActionsDataElement<Ciphertext> as DataElement>::deserialize::<Ciphertext>(&[
            // Finder bit set
            0x00, 0x00, 0x80,
        ])
        .unwrap_err()
    );
}

#[test]
fn context_sync_seq_num_works() {
    let mut action_bits = ActionBits::<Plaintext>::default();
    action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap());
    let action_de = ActionsDataElement::from(action_bits);
    assert_eq!(15, action_de.action.context_sync_seq_num().as_u8());
}

#[test]
fn context_sync_seq_num_default_zero() {
    let action_bits = ActionBits::<Plaintext>::default();
    let action_de = ActionsDataElement::from(action_bits);
    assert_eq!(0, action_de.action.context_sync_seq_num().as_u8());
}

#[test]
fn has_action_plaintext_works() {
    let mut action_bits = ActionBits::<Plaintext>::default();
    action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap());
    action_bits.set_action(NearbyShare::from(true));
    let action_de = ActionsDataElement::from(action_bits);
    assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true));
    assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(false));
    assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false));
}

#[test]
fn has_action_encrypted_works() {
    let mut action_bits = ActionBits::<Ciphertext>::default();
    action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap());
    action_bits.set_action(NearbyShare::from(true));
    action_bits.set_action(ActiveUnlock::from(true));
    let action_de = ActionsDataElement::from(action_bits);
    assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true));
    assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(true));
    assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false));
    assert_eq!(action_de.action.has_action(&ActionType::ContextSyncSeqNum), None);
}

// hypothetical action using the last bit
#[derive(Debug)]
struct LastBit {
    enabled: bool,
}
impl From<bool> for LastBit {
    fn from(value: bool) -> Self {
        LastBit { enabled: value }
    }
}
impl ActionElement for LastBit {
    const HIGH_BIT_INDEX: u32 = 23;
    const BITS_LEN: u32 = 1;

    // don't want to add a variant for this test only type
    const ACTION_TYPE: ActionType = ActionType::ActiveUnlock;

    fn supports_flavor(_flavor: PacketFlavorEnum) -> bool {
        true
    }
}

macros::boolean_element_to_plaintext_element!(LastBit);
macros::boolean_element_to_encrypted_element!(LastBit);

fn assert_eq_hex(expected: u32, actual: u32) {
    assert_eq!(expected, actual, "{expected:#010X} != {actual:#010X}");
}

pub(crate) fn all_plaintext_actions() -> ActionBits<Plaintext> {
    let mut action = ActionBits::default();
    action.set_action(ContextSyncSeqNum::try_from(9).unwrap());
    action.set_action(NearbyShare::from(true));
    action.set_action(Finder::from(true));
    action.set_action(FastPairSass::from(true));
    action
}

pub(crate) fn all_ciphertext_actions() -> ActionBits<Ciphertext> {
    let mut action = ActionBits::default();
    action.set_action(ContextSyncSeqNum::try_from(9).unwrap());
    action.set_action(ActiveUnlock::from(true));
    action.set_action(NearbyShare::from(true));
    action.set_action(InstantTethering::from(true));
    action.set_action(PhoneHub::from(true));
    action.set_action(PresenceManager::from(true));
    action
}
