//! Ditto Builder
//!
//! Provides idiomatic configuration of a Ditto instance using the "Builder
//! Pattern"

use ffi_sdk::Platform;

use super::*;
#[allow(deprecated)]
use crate::identity::Identity;

/// Use [`Ditto::builder()`] to initialize a [`Ditto`] instance.
///
/// [`Ditto::builder()`]: crate::Ditto::builder
#[deprecated(
    note = "Use `DittoConfig` with `Ditto::open(config)` or `Ditto::open_sync(config)` instead. \
            DittoBuilder will be removed in SDK 5.0."
)]
#[allow(deprecated)]
pub struct DittoBuilder {
    ditto_root: Option<Arc<dyn DittoRoot>>,
    identity: Option<Arc<dyn Identity>>,
    minimum_log_level: Option<LogLevel>,
    transport_config: Option<TransportConfig>,
}

#[allow(deprecated)]
impl DittoBuilder {
    /// Create a new, empty builder for a [`Ditto`] instance.
    #[doc(hidden)]
    #[deprecated(note = "Use `Ditto::builder()` instead")]
    #[allow(deprecated)]
    // TODO(V5): remove
    pub fn new() -> DittoBuilder {
        #[allow(deprecated)]
        DittoBuilder {
            ditto_root: None,
            identity: None,
            minimum_log_level: None,
            transport_config: None,
        }
    }

    /// Set the root directory where Ditto will store its data.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn with_root(mut self, ditto_root: Arc<dyn DittoRoot>) -> Self {
        self.ditto_root = Some(ditto_root);
        self
    }

    /// Configure the minimum log level for the [`Ditto`] instance.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn with_minimum_log_level(mut self, log_level: LogLevel) -> Self {
        self.minimum_log_level = Some(log_level);
        self
    }

    /// Build a [`Ditto`] instance with a temporary storage directory which
    /// will be destroyed on exit.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn with_temp_dir(mut self) -> Self {
        let root = TempRoot::new();
        self.ditto_root = Some(Arc::new(root));
        self
    }

    pub(crate) fn platform() -> Platform {
        using!(match () {
            use ffi_sdk::Platform;
            | _case if cfg!(target_os = "windows") => Platform::Windows,
            | _case if cfg!(target_os = "android") => Platform::Android,
            | _case if cfg!(target_os = "macos") => Platform::Mac,
            | _case if cfg!(target_os = "ios") => Platform::Ios,
            | _case if cfg!(target_os = "tvos") => Platform::Tvos,
            | _case if cfg!(target_os = "linux") => Platform::Linux,
            | _default => Platform::Unknown,
        })
    }

    pub(crate) fn sdk_version() -> String {
        let sdk_semver = env!("CARGO_PKG_VERSION");
        sdk_semver.to_string()
    }

    fn init_sdk_version() {
        let platform = Self::platform();
        let sdk_semver = Self::sdk_version();
        let c_version = char_p::new(sdk_semver);
        ffi_sdk::ditto_init_sdk_version(platform, ffi_sdk::Language::Rust, c_version.as_ref());
    }

    fn init_logging(&self) {
        if let Some(level) = self.minimum_log_level {
            ffi_sdk::ditto_logger_minimum_log_level(level);
        }
        ffi_sdk::ditto_logger_init();
        ffi_sdk::ditto_logger_enabled(true);
    }

    /// Provide a factory [`FnOnce`] which will create and configure the
    /// [`Identity`] for the [`Ditto`] instance.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn with_identity<F, I>(mut self, factory: F) -> Result<Self, DittoError>
    where
        F: FnOnce(Arc<dyn DittoRoot>) -> Result<I, DittoError>,
        I: Identity + 'static, // must return something ownable
    {
        match &self.ditto_root {
            Some(root) => {
                let identity = factory(root.retain())?;
                self.identity = Some(Arc::new(identity));
            }
            None => {
                let msg = "A valid DittoRoot directory must be provided before configuring the \
                           Identity"
                    .to_string();
                return Err(DittoError::new(ErrorKind::Config, msg));
            }
        };
        Ok(self)
    }

    /// Provide a factory for the [`TransportConfig`] used by the
    /// [`Ditto`] instance.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn with_transport_config<T>(mut self, factory: T) -> Result<Self, DittoError>
    where
        T: FnOnce(Arc<dyn Identity>) -> TransportConfig,
    {
        match &self.identity {
            Some(id) => {
                let config = factory(id.retain());
                self.transport_config = Some(config)
            }
            None => {
                let msg = "A DittoRoot directory and Identity must first be specified before \
                           providing a custom TransportConfig"
                    .to_string();
                return Err(DittoError::new(ErrorKind::Config, msg));
            }
        }
        Ok(self)
    }

    /// Builds the [`Ditto`] instance, consuming the builder in the process.
    #[deprecated(note = "Use `DittoConfig` with `Ditto::open(config)` or \
                         `Ditto::open_sync(config)` instead. DittoBuilder will be removed in SDK \
                         5.0.")]
    pub fn build(self) -> Result<Ditto, DittoError> {
        // TODO(initialization-revamp): move this function's logic to Ditto::open().
        // The builder should be implemented on top of Ditto::open() and DittoConfig, rather than
        // being responsible for the low-level details.
        self.init_logging();
        Self::init_sdk_version();
        let ditto_root = self.ditto_root.ok_or_else(|| {
            DittoError::new(ErrorKind::Config, "No Ditto Root Directory provided")
        })?;

        crate::fs::drain_ditto_data_dir(&ditto_root);

        let c_root_dir = ditto_root.root_dir_to_c_str()?;
        let identity = self
            .identity
            .ok_or_else(|| DittoError::new(ErrorKind::Config, "No Identity specified"))?;

        // The identity config should only be `None` _after_ this call below (because it ends up
        // being consumed by `ditto_make`)
        let identity_config = identity
            .identity_config()
            .expect("identity config to be Some");

        let boxed_ditto = ffi_sdk::ditto_make(
            c_root_dir.as_ref(),
            identity_config,
            ffi_sdk::HistoryTracking::Disabled,
        );
        let ditto: Arc<BoxedDitto> = Arc::new(boxed_ditto);
        #[allow(deprecated)]
        let site_id: SiteId = ffi_sdk::ditto_auth_client_get_site_id(&ditto);
        let has_auth = identity.involves_authenticator();
        let disk_usage = DiskUsage::new(ditto.retain(), FsComponent::Root);
        let small_peer_info = SmallPeerInfo::new(ditto.retain());
        let fields = Arc::new_cyclic(|weak_fields: &arc::Weak<_>| {
            let store = Store::new(ditto.retain(), weak_fields.clone());
            let sync = crate::sync::Sync::new(weak_fields.clone());
            let presence = Arc::new(Presence::new(weak_fields.clone()));

            DittoFields {
                ditto: ditto.retain(),
                has_auth,
                state: ConfigOrIdentity::Identity(identity.retain()),
                store,
                sync,
                site_id,
                ditto_root,
                presence,
                disk_usage,
                small_peer_info,
            }
        });

        // See inline comments in `Identity` trait about why this is necessary.
        identity.set_login_provider_with_ditto_pointer(&fields);

        #[allow(deprecated)]
        ffi_sdk::dittoffi_ditto_set_cloud_sync_enabled(&ditto, identity.is_cloud_sync_enabled());

        let sdk_ditto = Ditto {
            fields,
            is_shut_down_able: true,
        };
        if let Some(transport_config) = self.transport_config {
            sdk_ditto.set_transport_config(transport_config);
        }
        Ok(sdk_ditto)
    }
}

#[allow(deprecated)]
impl Default for DittoBuilder {
    fn default() -> Self {
        #[allow(deprecated)]
        Self::new()
    }
}
