/*
usefull docs:
- https://github.com/FreshRSS/FreshRSS/blob/master/p/api/greader.php
- https://github.com/theoldreader/api
- https://feedhq.readthedocs.io/en/latest/api/index.html
- https://www.inoreader.com/developers/
*/

mod deserialize;
pub mod error;
pub mod models;
#[cfg(test)]
mod tests;

pub use crate::error::ApiError;
use crate::models::GReaderError;
pub use crate::models::{AuthData, GoogleAuth, InoreaderAuth};
use crate::models::{Feeds, ItemRefs, QuickFeed, Stream, StreamType, Taggings, Unread, User};
use std::collections::HashMap;

#[cfg(any(feature = "feedhq", feature = "oldreader", feature = "inoreader"))]
use crate::models::StreamPrefs;

use chrono::{TimeDelta, Utc};
use log::error;
use models::{AuthInput, OAuthResponse, PostToken};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
use reqwest::{Client, StatusCode};
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Mutex;
use url::Url;

#[derive(Clone, Debug)]
pub struct GReaderApi {
    base_uri: Url,
    auth_input: Arc<Mutex<AuthInput>>,
    auth: Arc<Mutex<AuthData>>,
}

impl GReaderApi {
    /// Create a new instance of the GReaderApi
    pub fn new(url: &Url, auth: AuthData) -> Self {
        GReaderApi {
            base_uri: url.clone(),
            auth_input: Arc::new(Mutex::new(AuthInput::Uninitialized)),
            auth: Arc::new(Mutex::new(auth)),
        }
    }

    pub async fn get_auth_data(&self) -> Result<AuthData, ApiError> {
        Ok(self.auth.lock().await.clone())
    }

    pub async fn set_aut_data(&self, auth: AuthData) -> Result<(), ApiError> {
        *(self.auth.lock().await) = auth;
        Ok(())
    }

    async fn get_auth_headers(&self) -> Result<HeaderMap, ApiError> {
        let mut headers = HeaderMap::new();

        let auth_data = self.auth.lock().await.clone();

        match &auth_data {
            AuthData::Uninitialized => return Err(ApiError::Other("api is uninitialized".into())),
            AuthData::Google(auth_data) => {
                if auth_data.auth_token.is_none() {
                    return Err(ApiError::NotLoggedIn)?;
                }

                if let Some(auth_token) = auth_data.auth_token.as_deref() {
                    headers.insert(
                        AUTHORIZATION,
                        HeaderValue::from_str(&format!("GoogleLogin auth={auth_token}"))
                            .expect("parse header value"),
                    );
                }
            }
            AuthData::Inoreader(auth_data) => {
                // check if access_token is still valid
                let expires_in = auth_data.expires_at.signed_duration_since(Utc::now());
                let expired = expires_in.num_seconds() <= 60;

                if expired {
                    return Err(ApiError::TokenExpired)?;
                }

                headers.insert(
                    AUTHORIZATION,
                    HeaderValue::from_str(&format!("Bearer {}", auth_data.access_token))
                        .expect("parse header value"),
                );
                headers.insert(
                    "AppId",
                    HeaderValue::from_str(&auth_data.client_id).expect("parse header value"),
                );
                headers.insert(
                    "AppKey",
                    HeaderValue::from_str(&auth_data.client_secret).expect("parse header value"),
                );
            }
        };

        Ok(headers)
    }

    fn deserialize<T: for<'a> Deserialize<'a>>(json: &str) -> Result<T, ApiError> {
        let result: T = serde_json::from_str(json).map_err(|source| ApiError::Json {
            source,
            json: json.into(),
        })?;
        Ok(result)
    }

    async fn get_post_token(&self, client: &Client) -> Result<Option<String>, ApiError> {
        let mut auth_data = self.auth.lock().await.clone();

        let post_token = match &mut auth_data {
            AuthData::Inoreader(_) | AuthData::Uninitialized => Ok(None),
            AuthData::Google(auth_data) => {
                if let Some(post_token) = auth_data.post_token.as_mut() {
                    if !post_token.is_valid() {
                        let mut response = self
                            .get_request("reader/api/0/token", vec![], client)
                            .await?;
                        let _ = response.pop();
                        post_token.update(&response);
                        Ok(Some(response))
                    } else {
                        Ok(Some(post_token.token.clone()))
                    }
                } else {
                    let mut response = self
                        .get_request("reader/api/0/token", vec![], client)
                        .await?;
                    let _ = response.pop();
                    let post_token = PostToken::new(&response);
                    auth_data.post_token.replace(post_token);
                    Ok(Some(response))
                }
            }
        };

        *self.auth.lock().await = auth_data;
        post_token
    }

    async fn get_request(
        &self,
        query: &str,
        mut params: Vec<(&str, String)>,
        client: &Client,
    ) -> Result<String, ApiError> {
        let api_url: Url = self.base_uri.join(query)?;

        let mut query_params = Vec::new();
        query_params.append(&mut params);
        query_params.push(("output", "json".into()));

        let auth_headers = self.get_auth_headers().await?;

        let response = client
            .get(api_url.clone())
            .headers(auth_headers)
            .query(&query_params)
            .send()
            .await?;

        let status = response.status();
        let response = response.text().await?;
        if status != StatusCode::OK {
            if status == StatusCode::UNAUTHORIZED {
                return Err(ApiError::AccessDenied);
            } else if status == StatusCode::TOO_MANY_REQUESTS {
                return Err(ApiError::ApiLimit);
            }

            let error = if let Ok(greader_error) = serde_json::from_str::<GReaderError>(&response) {
                greader_error
            } else {
                GReaderError {
                    errors: vec![response.to_string()],
                }
            };

            error!("GReader API: {}", error.errors.join("; "));
            return Err(ApiError::parse_error(error));
        }
        Ok(response)
    }

    async fn post_request(
        &self,
        query: &str,
        mut params: Vec<(&str, String)>,
        mut form_params: Vec<(&str, String)>,
        body: Option<&str>,
        client: &Client,
    ) -> Result<String, ApiError> {
        let api_url: Url = self.base_uri.join(query)?;
        let mut query_params = Vec::new();
        query_params.append(&mut params);
        query_params.push(("output", "json".into()));

        let mut post_params = Vec::new();
        post_params.append(&mut form_params);
        if let Some(post_token) = self.get_post_token(client).await? {
            post_params.push(("T", post_token));
        }

        let mut headers = self.get_auth_headers().await?;
        headers.append(
            CONTENT_TYPE,
            HeaderValue::from_static("application/x-www-form-urlencoded"),
        );

        let mut request = client
            .post(api_url.clone())
            .headers(headers)
            .query(&query_params)
            .form(&post_params);

        if let Some(body) = body {
            request = request.body(body.to_owned());
        }

        if body.is_none() && post_params.is_empty() {
            request = request.header(CONTENT_LENGTH, 0);
        }

        let response = request.send().await?;

        let status = response.status();
        let response = response.text().await?;
        if status != StatusCode::OK {
            if status == StatusCode::UNAUTHORIZED {
                return Err(ApiError::AccessDenied);
            } else if status == StatusCode::BAD_REQUEST {
                return Err(ApiError::BadRequest);
            } else if status == StatusCode::TOO_MANY_REQUESTS {
                return Err(ApiError::ApiLimit);
            }

            let error = if let Ok(greader_error) = serde_json::from_str::<GReaderError>(&response) {
                greader_error
            } else {
                GReaderError {
                    errors: vec![response.to_string()],
                }
            };

            error!("GReader API: {}", error.errors.join("; "));
            return Err(ApiError::GReader(error));
        }
        Ok(response)
    }

    fn check_ok_response(response: &str) -> Result<(), ApiError> {
        if response == "OK" {
            Ok(())
        } else {
            let error: GReaderError = GReaderError {
                errors: vec![response.to_string()],
            };
            Err(ApiError::GReader(error))
        }
    }

    pub async fn login(
        &self,
        auth_input: &AuthInput,
        client: &Client,
    ) -> Result<AuthData, ApiError> {
        *self.auth.lock().await = match auth_input {
            AuthInput::Uninitialized => return Err(ApiError::Input),
            AuthInput::Inoreader(input) => {
                let mut map: HashMap<String, String> = HashMap::new();
                map.insert("code".into(), input.auth_code.clone());
                map.insert("redirect_uri".into(), input.redirect_url.clone());
                map.insert("client_id".into(), input.client_id.clone());
                map.insert("client_secret".into(), input.client_secret.clone());
                map.insert("scope".into(), "".into());
                map.insert("grant_type".into(), "authorization_code".into());

                let response = client
                    .post("https://www.inoreader.com/oauth2/token")
                    .form(&map)
                    .send()
                    .await?;

                let status = response.status();
                if status == StatusCode::UNAUTHORIZED {
                    return Err(ApiError::AccessDenied);
                } else if status == StatusCode::BAD_REQUEST {
                    return Err(ApiError::BadRequest);
                } else if status == StatusCode::TOO_MANY_REQUESTS {
                    return Err(ApiError::ApiLimit);
                }

                let result = response.text().await?;

                let oauth_response = Self::deserialize::<OAuthResponse>(&result)?;

                let now = Utc::now();
                let token_expires =
                    now + TimeDelta::try_seconds(oauth_response.expires_in).unwrap();

                AuthData::Inoreader(InoreaderAuth {
                    client_id: input.client_id.clone(),
                    client_secret: input.client_secret.clone(),
                    access_token: oauth_response.access_token,
                    refresh_token: oauth_response.refresh_token,
                    expires_at: token_expires,
                })
            }
            AuthInput::Google(input) => {
                let api_url: Url = self.base_uri.join("accounts/ClientLogin")?;
                let response = client
                    .post(api_url.clone())
                    .query(&[("Email", &input.username), ("Passwd", &input.password)])
                    .send()
                    .await?;

                let status = response.status();
                let response = response.text().await?;
                if status == StatusCode::UNAUTHORIZED {
                    return Err(ApiError::AccessDenied);
                } else if status == StatusCode::BAD_REQUEST {
                    return Err(ApiError::BadRequest);
                } else if status == StatusCode::TOO_MANY_REQUESTS {
                    return Err(ApiError::ApiLimit);
                } else if status != StatusCode::OK {
                    return Err(ApiError::Other(format!("Status Code {status}")));
                }

                let auth_token = Self::parse_login_response(&response)?;

                AuthData::Google(GoogleAuth {
                    username: input.username.clone(),
                    password: input.password.clone(),
                    auth_token: Some(auth_token),
                    post_token: None,
                })
            }
        };

        let _post_token = self.get_post_token(client).await?;

        *self.auth_input.lock().await = auth_input.clone();

        Ok(self.auth.lock().await.clone())
    }

    fn parse_login_response(response: &str) -> Result<String, ApiError> {
        let mut map = HashMap::new();
        for line in response.lines() {
            if line.trim().is_empty() {
                continue;
            }

            let mut split = line.splitn(2, '=');

            if let (Some(key), Some(value)) = (split.next(), split.next()) {
                map.insert(key, value);
            } else {
                return Err(ApiError::Other(format!(
                    "response doesn't contain '=' in line: {line}"
                )));
            }
        }

        match map.get("Auth") {
            Some(auth) => Ok(auth.to_string()),
            None => Err(ApiError::Other(format!(
                "response doesn't contain auth token in line: {response}"
            ))),
        }
    }

    pub async fn user_info(&self, client: &Client) -> Result<User, ApiError> {
        let response = self
            .get_request("reader/api/0/user-info", vec![], client)
            .await?;
        Self::deserialize(&response)
    }

    pub async fn unread_count(&self, client: &Client) -> Result<Unread, ApiError> {
        let response = self
            .get_request("reader/api/0/unread-count", vec![], client)
            .await?;
        Self::deserialize(&response)
    }

    pub async fn subscription_list(&self, client: &Client) -> Result<Feeds, ApiError> {
        let response = self
            .get_request("reader/api/0/subscription/list", vec![], client)
            .await?;
        Self::deserialize(&response)
    }

    pub async fn subscription_create(
        &self,
        url: &Url,
        name: Option<&str>,
        to_stream: Option<&str>,
        client: &Client,
    ) -> Result<(), ApiError> {
        let mut params = Vec::new();
        params.push(("ac", "subscribe".into()));
        params.push(("s", format!("feed/{}", &url.as_str())));

        if let Some(name) = name {
            params.push(("t", name.into()));
        }
        if let Some(to_stream) = to_stream {
            params.push(("a", to_stream.into()));
        }

        let response = self
            .post_request(
                "reader/api/0/subscription/edit",
                params,
                vec![],
                None,
                client,
            )
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    pub async fn subscription_edit(
        &self,
        item_id: &str,
        name: Option<&str>,
        from_stream: Option<&str>,
        to_stream: Option<&str>,
        client: &Client,
    ) -> Result<(), ApiError> {
        let mut params = Vec::new();
        params.push(("ac", "edit".into()));
        params.push(("s", item_id.into()));

        if let Some(name) = name {
            params.push(("t", name.into()));
        }
        if let Some(from_stream) = from_stream {
            params.push(("r", from_stream.into()));
        }
        if let Some(to_stream) = to_stream {
            params.push(("a", to_stream.into()));
        }

        let response = self
            .post_request(
                "reader/api/0/subscription/edit",
                params,
                vec![],
                None,
                client,
            )
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    pub async fn subscription_delete(
        &self,
        stream_id: &str,
        client: &Client,
    ) -> Result<(), ApiError> {
        let params = vec![("ac", "unsubscribe".into()), ("s", stream_id.into())];

        let response = self
            .post_request(
                "reader/api/0/subscription/edit",
                params,
                vec![],
                None,
                client,
            )
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    pub async fn subscription_quickadd(
        &self,
        url: &Url,
        client: &Client,
    ) -> Result<QuickFeed, ApiError> {
        let params = vec![("quickadd", url.as_str().into())];

        let response = self
            .post_request(
                "reader/api/0/subscription/quickadd",
                params,
                vec![],
                None,
                client,
            )
            .await?;
        let subscriptions: QuickFeed = Self::deserialize(&response)?;
        Ok(subscriptions)
    }

    // untested
    pub async fn import(&self, opml: String, client: &Client) -> Result<u64, ApiError> {
        let response = self
            .post_request(
                "reader/api/0/subscription/import",
                vec![],
                vec![],
                Some(&opml),
                client,
            )
            .await?;

        if response.starts_with("OK: ") {
            let response = response.replace("Ok: ", "");
            let response = response
                .parse::<u64>()
                .map_err(|e| ApiError::Other(format!("failed to parse response u64: {e}")))?;
            Ok(response)
        } else {
            Err(ApiError::GReader(GReaderError {
                errors: vec![response],
            }))
        }
    }

    // untested
    pub async fn export(&self, client: &Client) -> Result<String, ApiError> {
        self.get_request("reader/api/0/subscription/export", vec![], client)
            .await
    }

    // untested
    #[cfg(feature = "feedhq")]
    pub async fn subscribed(&self, stream_id: &str, client: &Client) -> Result<bool, ApiError> {
        let params = vec![("s", stream_id.into())];

        let response = self
            .get_request("reader/api/0/subscribed", params, client)
            .await?;
        match &response[..] {
            "true" => Ok(true),
            "false" => Ok(false),
            _ => Err(ApiError::GReader(GReaderError {
                errors: vec![response.to_string()],
            })),
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn stream_contents(
        &self,
        stream_id: Option<&str>,
        reverse_order: bool,
        amount: Option<u64>,
        continuation: Option<&str>,
        exclude_stream: Option<&str>,
        include_stream: Option<&str>,
        filter_older: Option<i64>,
        filter_newer: Option<i64>,
        client: &Client,
    ) -> Result<Stream, ApiError> {
        let mut params = Vec::new();
        if reverse_order {
            params.push(("r", "o".into()));
        }
        if let Some(n) = amount {
            params.push(("n", n.to_string()));
        }
        if let Some(c) = continuation {
            params.push(("c", c.into()));
        }
        if let Some(s) = exclude_stream {
            params.push(("xt", s.into()));
        }
        if let Some(s) = include_stream {
            params.push(("it", s.into()));
        }
        if let Some(t) = filter_older {
            params.push(("ot", t.to_string()));
        }
        if let Some(t) = filter_newer {
            params.push(("nt", t.to_string()));
        }

        let query = "reader/api/0/stream/contents";
        let query = if let Some(stream_id) = stream_id {
            format!("{}/{}", query, stream_id)
        } else {
            query.into()
        };
        let response = self
            .post_request(&query, params, vec![], None, client)
            .await?;

        Self::deserialize(&response)
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn items_ids(
        &self,
        stream_id: Option<&str>,
        amount: Option<u64>,
        include_all_direct_stream_ids: bool,
        continuation: Option<&str>,
        exclude_stream: Option<&str>,
        include_stream: Option<&str>,
        filter_older: Option<i64>,
        filter_newer: Option<i64>,
        client: &Client,
    ) -> Result<ItemRefs, ApiError> {
        let mut params = Vec::new();

        if let Some(amount) = amount {
            params.push(("n", amount.to_string()));
        }
        if let Some(stream_id) = stream_id {
            params.push(("s", stream_id.into()));
        }
        if let Some(c) = continuation {
            params.push(("c", c.into()));
        }
        if include_all_direct_stream_ids {
            params.push(("includeAllDirectStreamIds", "true".into()));
        }
        if let Some(s) = exclude_stream {
            params.push(("xt", s.into()));
        }
        if let Some(s) = include_stream {
            params.push(("it", s.into()));
        }
        if let Some(t) = filter_older {
            params.push(("ot", t.to_string()));
        }
        if let Some(t) = filter_newer {
            params.push(("nt", t.to_string()));
        }
        let response = self
            .get_request("reader/api/0/stream/items/ids", params, client)
            .await?;

        Self::deserialize(&response)
    }

    #[cfg(feature = "feedhq")]
    pub async fn items_count(
        &self,
        stream_id: &str,
        get_latest_date: bool,
        client: &Client,
    ) -> Result<String, ApiError> {
        let mut params = Vec::new();
        params.push(("s", stream_id.into()));

        if get_latest_date {
            params.push(("a", "true".into()));
        }

        let response = self
            .get_request("reader/api/0/stream/items/count", params, client)
            .await?;
        Ok(response)
    }

    pub async fn items_contents(
        &self,
        item_ids: Vec<String>,
        client: &Client,
    ) -> Result<Stream, ApiError> {
        let params = Vec::new();
        let mut form_params = Vec::new();
        for item_id in item_ids {
            form_params.push(("i", item_id));
        }

        let response = self
            .post_request(
                "reader/api/0/stream/items/contents",
                params,
                form_params,
                None,
                client,
            )
            .await?;

        Self::deserialize(&response)
    }

    pub async fn tag_list(&self, client: &Client) -> Result<Taggings, ApiError> {
        let response = self
            .get_request("reader/api/0/tag/list", vec![], client)
            .await?;
        Self::deserialize(&response)
    }

    pub async fn tag_delete(
        &self,
        stream_type: StreamType,
        id: &str,
        client: &Client,
    ) -> Result<(), ApiError> {
        let form_params = vec![(stream_type.into(), id.into())];

        let response = self
            .post_request(
                "reader/api/0/disable-tag",
                vec![],
                form_params,
                None,
                client,
            )
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    pub async fn tag_rename(
        &self,
        stream_type: StreamType,
        old_name: &str,
        new_name: &str,
        client: &Client,
    ) -> Result<(), ApiError> {
        let form_params = vec![
            (stream_type.into(), old_name.into()),
            ("dest", new_name.into()),
        ];

        let response = self
            .post_request("reader/api/0/rename-tag", vec![], form_params, None, client)
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    // TODO have better parameters, since it is not obvious what is add and remove
    pub async fn tag_edit(
        &self,
        item_ids: &[&str],
        tag_add: Option<&str>,
        tag_remove: Option<&str>,
        client: &Client,
    ) -> Result<(), ApiError> {
        if tag_add.is_none() && tag_remove.is_none() {
            return Err(ApiError::Input);
        }

        let mut form_params = Vec::new();
        for item_id in item_ids {
            form_params.push(("i", item_id.to_string()));
        }
        if let Some(remove) = tag_remove {
            form_params.push(("r", remove.into()));
        }
        if let Some(add) = tag_add {
            form_params.push(("a", add.into()));
        }

        let response = self
            .post_request("reader/api/0/edit-tag", vec![], form_params, None, client)
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    pub async fn mark_all_as_read(
        &self,
        stream_id: &str,
        older_than: Option<u64>,
        client: &Client,
    ) -> Result<(), ApiError> {
        let mut params = Vec::new();
        let form_params = vec![("s", stream_id.into())];

        if let Some(older_than) = older_than {
            params.push(("ts", older_than.to_string()));
        }

        let response = self
            .post_request(
                "reader/api/0/mark-all-as-read",
                params,
                form_params,
                None,
                client,
            )
            .await?;
        GReaderApi::check_ok_response(&response)
    }

    #[allow(unused)]
    #[cfg(any(feature = "feedhq", feature = "oldreader"))]
    pub async fn preference_list(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }

    #[cfg(any(feature = "feedhq", feature = "oldreader", feature = "inoreader"))]
    pub async fn preference_stream_list(&self, client: &Client) -> Result<StreamPrefs, ApiError> {
        let response = self
            .get_request("reader/api/0/preference/stream/list", vec![], client)
            .await?;

        Self::deserialize(&response)
    }

    #[allow(unused)]
    #[cfg(any(feature = "feedhq", feature = "oldreader"))]
    pub async fn friends_list(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }
    #[allow(unused)]
    #[cfg(feature = "oldreader")]
    pub async fn friends_edit(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }

    #[allow(unused)]
    #[cfg(feature = "inoreader")]
    pub async fn inoreader_refresh_token(
        &self,
        client: &Client,
    ) -> Result<InoreaderAuth, ApiError> {
        let auth_data = match &*self.auth.lock().await {
            AuthData::Inoreader(auth_data) => auth_data.clone(),
            _ => return Err(ApiError::Token),
        };

        let client_id = auth_data.client_id.clone();
        let client_secret = auth_data.client_secret.clone();
        let refresh_token = auth_data.refresh_token.clone();

        let mut map: HashMap<String, String> = HashMap::new();
        map.insert("client_id".into(), client_id);
        map.insert("client_secret".into(), client_secret);
        map.insert("grant_type".into(), "refresh_token".into());
        map.insert("refresh_token".into(), refresh_token);

        let response = client
            .post("https://www.inoreader.com/oauth2/token")
            .form(&map)
            .send()
            .await?;

        let status = response.status();
        if status == StatusCode::UNAUTHORIZED {
            return Err(ApiError::AccessDenied);
        } else if status == StatusCode::BAD_REQUEST {
            return Err(ApiError::BadRequest);
        } else if status == StatusCode::TOO_MANY_REQUESTS {
            return Err(ApiError::ApiLimit);
        }

        let result = response.text().await?;

        let oauth_response: OAuthResponse = Self::deserialize(&result)?;

        let now = Utc::now();
        let token_expires = now + TimeDelta::try_seconds(oauth_response.expires_in).unwrap();

        let inoreader_auth = InoreaderAuth {
            client_id: auth_data.client_id.clone(),
            client_secret: auth_data.client_secret.clone(),
            access_token: oauth_response.access_token,
            refresh_token: oauth_response.refresh_token,
            expires_at: token_expires,
        };

        *self.auth.lock().await = AuthData::Inoreader(inoreader_auth.clone());

        Ok(inoreader_auth)
    }

    #[allow(unused)]
    #[cfg(feature = "inoreader")]
    pub async fn create_active_search(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }

    #[allow(unused)]
    #[cfg(feature = "inoreader")]
    pub async fn delete_active_search(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }

    #[allow(unused)]
    #[cfg(feature = "oldreader")]
    pub async fn add_comment(&self, client: &Client) -> Result<(), ApiError> {
        unimplemented!();
    }
}
