/*
 *
 *    Copyright (c) 2020-2022 Project CHIP Authors
 *
 *    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.
 */

use crate::{
    acl::AclMgr,
    data_model::{
        cluster_basic_information::BasicInfoConfig, core::DataModel,
        sdm::dev_att::DevAttDataFetcher,
    },
    error::*,
    fabric::FabricMgr,
    interaction_model::InteractionModel,
    mdns::Mdns,
    secure_channel::core::SecureChannel,
    transport,
};
use std::sync::Arc;

#[derive(Default)]
/// Device Commissioning Data
pub struct CommissioningData {
    /// The commissioning salt
    pub salt: [u8; 16],
    /// The password for commissioning the device
    // TODO: We should replace this with verifier instead of password
    pub passwd: u32,
    /// The 12-bit discriminator used to differentiate between multiple devices
    pub discriminator: u16,
}

/// The primary Matter Object
pub struct Matter {
    transport_mgr: transport::mgr::Mgr,
    data_model: DataModel,
    fabric_mgr: Arc<FabricMgr>,
}

impl Matter {
    /// Creates a new Matter object
    ///
    /// # Parameters
    /// * dev_att: An object that implements the trait [DevAttDataFetcher]. Any Matter device
    /// requires a set of device attestation certificates and keys. It is the responsibility of
    /// this object to return the device attestation details when queried upon.
    pub fn new(
        dev_det: BasicInfoConfig,
        dev_att: Box<dyn DevAttDataFetcher>,
        dev_comm: CommissioningData,
    ) -> Result<Box<Matter>, Error> {
        let mdns = Mdns::get()?;
        mdns.set_values(dev_det.vid, dev_det.pid, dev_comm.discriminator);

        let fabric_mgr = Arc::new(FabricMgr::new()?);
        let acl_mgr = Arc::new(AclMgr::new()?);
        let open_comm_window = fabric_mgr.is_empty();
        let data_model = DataModel::new(dev_det, dev_att, fabric_mgr.clone(), acl_mgr)?;
        let mut matter = Box::new(Matter {
            transport_mgr: transport::mgr::Mgr::new()?,
            data_model,
            fabric_mgr,
        });
        let interaction_model =
            Box::new(InteractionModel::new(Box::new(matter.data_model.clone())));
        matter.transport_mgr.register_protocol(interaction_model)?;
        let mut secure_channel = Box::new(SecureChannel::new(matter.fabric_mgr.clone()));
        if open_comm_window {
            secure_channel.open_comm_window(&dev_comm.salt, dev_comm.passwd)?;
        }

        matter.transport_mgr.register_protocol(secure_channel)?;
        Ok(matter)
    }

    /// Returns an Arc to [DataModel]
    ///
    /// The Data Model is where you express what is the type of your device. Typically
    /// once you gets this reference, you acquire the write lock and add your device
    /// types, clusters, attributes, commands to the data model.
    pub fn get_data_model(&self) -> DataModel {
        self.data_model.clone()
    }

    /// Starts the Matter daemon
    ///
    /// This call does NOT return
    ///
    /// This call starts the Matter daemon that starts communication with other Matter
    /// devices on the network.
    pub fn start_daemon(&mut self) -> Result<(), Error> {
        self.transport_mgr.start()
    }
}