dc_array_t* dc_get_contacts(dc_context_t* context, uint32_t listflags, const char* query)
{
	char*         self_addr = NULL;
	char*         self_name = NULL;
	char*         self_name2 = NULL;
	int           add_self = 0;
	dc_array_t*   ret = dc_array_new(context, 100);
	char*         s3strLikeCmd = NULL;
	sqlite3_stmt* stmt = NULL;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		goto cleanup;
	}

	self_addr = dc_sqlite3_get_config(context->sql, "configured_addr", ""); /* we add DC_CONTACT_ID_SELF explicitly; so avoid doubles if the address is present as a normal entry for some case */

	if ((listflags&DC_GCL_VERIFIED_ONLY) || query)
	{
		if ((s3strLikeCmd=sqlite3_mprintf("%%%s%%", query? query : ""))==NULL) {
			goto cleanup;
		}
		// see comments in dc_search_msgs() about the LIKE operator
		stmt = dc_sqlite3_prepare(context->sql,
			"SELECT c.id FROM contacts c"
				" LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
				" WHERE c.addr!=?1 AND c.id>?2 AND c.origin>=?3"
				" AND c.blocked=0 AND (c.name LIKE ?4 OR c.addr LIKE ?5)"
				" AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) "
				" ORDER BY LOWER(c.name||c.addr),c.id;");
		sqlite3_bind_text(stmt, 1, self_addr, -1, SQLITE_STATIC);
		sqlite3_bind_int (stmt, 2, DC_CONTACT_ID_LAST_SPECIAL);
		sqlite3_bind_int (stmt, 3, DC_ORIGIN_MIN_CONTACT_LIST);
		sqlite3_bind_text(stmt, 4, s3strLikeCmd, -1, SQLITE_STATIC);
		sqlite3_bind_text(stmt, 5, s3strLikeCmd, -1, SQLITE_STATIC);
		sqlite3_bind_int (stmt, 6, (listflags&DC_GCL_VERIFIED_ONLY)? 0/*force checking for verified_key*/ : 1/*force statement being always true*/);

		self_name  = dc_sqlite3_get_config(context->sql, "displayname", "");
		self_name2 = dc_stock_str(context, DC_STR_SELF);
		if (query==NULL || dc_str_contains(self_addr, query) || dc_str_contains(self_name, query) || dc_str_contains(self_name2, query)) {
			add_self = 1;
		}
	}
	else
	{
		stmt = dc_sqlite3_prepare(context->sql,
			"SELECT id FROM contacts"
				" WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0"
				" ORDER BY LOWER(name||addr),id;");
		sqlite3_bind_text(stmt, 1, self_addr, -1, SQLITE_STATIC);
		sqlite3_bind_int (stmt, 2, DC_CONTACT_ID_LAST_SPECIAL);
		sqlite3_bind_int (stmt, 3, DC_ORIGIN_MIN_CONTACT_LIST);

		add_self = 1;
	}

	while (sqlite3_step(stmt)==SQLITE_ROW) {
		dc_array_add_id(ret, sqlite3_column_int(stmt, 0));
	}

	/* to the end of the list, add self - this is to be in sync with member lists and to allow the user to start a self talk */
	if ((listflags&DC_GCL_ADD_SELF) && add_self) {
		dc_array_add_id(ret, DC_CONTACT_ID_SELF);
	}

cleanup:
	sqlite3_finalize(stmt);
	sqlite3_free(s3strLikeCmd);
	free(self_addr);
	free(self_name);
	free(self_name2);
	return ret;
}

pub async fn get_all(
        context: &Context,
        listflags: u32,
        query: Option<&str>,
    ) -> Result<Vec<ContactId>> {
        let self_addrs = context.get_all_self_addrs().await?;
        let mut add_self = false;
        let mut ret = Vec::new();
        let flag_verified_only = (listflags & DC_GCL_VERIFIED_ONLY) != 0;
        let flag_add_self = (listflags & DC_GCL_ADD_SELF) != 0;
        let minimal_origin = if context.get_config_bool(Config::Bot).await? {
            Origin::Unknown
        } else {
            Origin::IncomingReplyTo
        };
        if flag_verified_only || query.is_some() {
            let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
            context
                .sql
                .query_map(
                    &format!(
                        "SELECT c.id FROM contacts c \
                 LEFT JOIN acpeerstates ps ON c.addr=ps.addr  \
                 WHERE c.addr NOT IN ({})
                 AND c.id>? \
                 AND c.origin>=? \
                 AND c.blocked=0 \
                 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
                 AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0)  \
                 ORDER BY c.last_seen DESC, c.id DESC;",
                        sql::repeat_vars(self_addrs.len())
                    ),
                    rusqlite::params_from_iter(params_iter(&self_addrs).chain(params_slice![
                        ContactId::LAST_SPECIAL,
                        minimal_origin,
                        s3str_like_cmd,
                        s3str_like_cmd,
                        if flag_verified_only { 0i32 } else { 1i32 }
                    ])),
                    |row| row.get::<_, ContactId>(0),
                    |ids| {
                        for id in ids {
                            ret.push(id?);
                        }
                        Ok(())
                    },
                )
                .await?;

            if let Some(query) = query {
                let self_addr = context
                    .get_config(Config::ConfiguredAddr)
                    .await?
                    .unwrap_or_default();
                let self_name = context
                    .get_config(Config::Displayname)
                    .await?
                    .unwrap_or_default();
                let self_name2 = stock_str::self_msg(context);

                if self_addr.contains(query)
                    || self_name.contains(query)
                    || self_name2.await.contains(query)
                {
                    add_self = true;
                }
            } else {
                add_self = true;
            }
        } else {
            add_self = true;

            context
                .sql
                .query_map(
                    &format!(
                        "SELECT id FROM contacts
                 WHERE addr NOT IN ({})
                 AND id>?
                 AND origin>=?
                 AND blocked=0
                 ORDER BY last_seen DESC, id DESC;",
                        sql::repeat_vars(self_addrs.len())
                    ),
                    rusqlite::params_from_iter(
                        params_iter(&self_addrs)
                            .chain(params_slice![ContactId::LAST_SPECIAL, minimal_origin]),
                    ),
                    |row| row.get::<_, ContactId>(0),
                    |ids| {
                        for id in ids {
                            ret.push(id?);
                        }
                        Ok(())
                    },
                )
                .await?;
        }

        if flag_add_self && add_self {
            ret.push(ContactId::SELF);
        }

        Ok(ret)
    }

pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
        Ok(self.get_config_bool_opt(key).await?.unwrap_or_default())
    }

macro_rules! params_slice {
    ($($param:expr),+) => {
        [$(&$param as &dyn $crate::sql::ToSql),+]
    };
}

pub(crate) fn params_iter(
    iter: &[impl crate::sql::ToSql],
) -> impl Iterator<Item = &dyn crate::sql::ToSql> {
    iter.iter().map(|item| item as &dyn crate::sql::ToSql)
}

pub async fn query_map<T, F, G, H>(
        &self,
        sql: &str,
        params: impl rusqlite::Params + Send,
        f: F,
        mut g: G,
    ) -> Result<H>
    where
        F: Send + FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
        G: Send + FnMut(rusqlite::MappedRows<F>) -> Result<H>,
        H: Send + 'static,
    {
        self.call(move |conn| {
            let mut stmt = conn.prepare(sql)?;
            let res = stmt.query_map(params, f)?;
            g(res)
        })
        .await
    }

pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
        let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
        if let Ok(value) = env::var(env_key) {
            return Ok(Some(value));
        }

        let value = match key {
            Config::Selfavatar => {
                let rel_path = self.sql.get_raw_config(key.as_ref()).await?;
                rel_path.map(|p| {
                    get_abs_path(self, Path::new(&p))
                        .to_string_lossy()
                        .into_owned()
                })
            }
            Config::SysVersion => Some((*DC_VERSION_STR).clone()),
            Config::SysMsgsizeMaxRecommended => Some(format!("{RECOMMENDED_FILE_SIZE}")),
            Config::SysConfigKeys => Some(get_config_keys_string()),
            _ => self.sql.get_raw_config(key.as_ref()).await?,
        };

        if value.is_some() {
            return Ok(value);
        }

        // Default values
        match key {
            Config::ConfiguredInboxFolder => Ok(Some("INBOX".to_owned())),
            _ => Ok(key.get_str("default").map(|s| s.to_string())),
        }
    }

pub(crate) async fn get_all_self_addrs(&self) -> Result<Vec<String>> {
        let primary_addrs = self.get_config(Config::ConfiguredAddr).await?.into_iter();
        let secondary_addrs = self.get_secondary_self_addrs().await?.into_iter();

        Ok(primary_addrs.chain(secondary_addrs).collect())
    }

pub fn repeat_vars(count: usize) -> String {
    let mut s = "?,".repeat(count);
    s.pop(); // Remove trailing comma
    s
}

pub(crate) async fn self_msg(context: &Context) -> String {
    translated(context, StockMessage::SelfMsg).await
}

pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
where
    I: IntoIterator,
    I::Item: ToSql,
{
    ParamsFromIter(iter)
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct InnerContext {
    /// Blob directory path
    pub(crate) blobdir: PathBuf,
    pub(crate) sql: Sql,
    pub(crate) smeared_timestamp: SmearedTimestamp,
    /// The global "ongoing" process state.
    ///
    /// This is a global mutex-like state for operations which should be modal in the
    /// clients.
    running_state: RwLock<RunningState>,
    /// Mutex to avoid generating the key for the user more than once.
    pub(crate) generating_key_mutex: Mutex<()>,
    /// Mutex to enforce only a single running oauth2 is running.
    pub(crate) oauth2_mutex: Mutex<()>,
    /// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
    pub(crate) wrong_pw_warning_mutex: Mutex<()>,
    pub(crate) translated_stockstrings: StockStrings,
    pub(crate) events: Events,

    pub(crate) scheduler: SchedulerState,
    pub(crate) ratelimit: RwLock<Ratelimit>,

    /// Recently loaded quota information, if any.
    /// Set to `None` if quota was never tried to load.
    pub(crate) quota: RwLock<Option<QuotaInfo>>,

    /// IMAP UID resync request.
    pub(crate) resync_request: AtomicBool,

    /// Notify about new messages.
    ///
    /// This causes [`Context::wait_next_msgs`] to wake up.
    pub(crate) new_msgs_notify: Notify,

    /// Server ID response if ID capability is supported
    /// and the server returned non-NIL on the inbox connection.
    /// <https://datatracker.ietf.org/doc/html/rfc2971>
    pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,

    /// IMAP METADATA.
    pub(crate) metadata: RwLock<Option<ServerMetadata>>,

    pub(crate) last_full_folder_scan: Mutex<Option<tools::Time>>,

    /// ID for this `Context` in the current process.
    ///
    /// This allows for multiple `Context`s open in a single process where each context can
    /// be identified by this ID.
    pub(crate) id: u32,

    creation_time: tools::Time,

    /// The text of the last error logged and emitted as an event.
    /// If the ui wants to display an error after a failure,
    /// `last_error` should be used to avoid races with the event thread.
    pub(crate) last_error: std::sync::RwLock<String>,

    /// If debug logging is enabled, this contains all necessary information
    ///
    /// Standard RwLock instead of [`tokio::sync::RwLock`] is used
    /// because the lock is used from synchronous [`Context::emit_event`].
    pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,

    /// Push subscriber to store device token
    /// and register for heartbeat notifications.
    pub(crate) push_subscriber: PushSubscriber,

    /// True if account has subscribed to push notifications via IMAP.
    pub(crate) push_subscribed: AtomicBool,

    /// Iroh for realtime peer channels.
    pub(crate) iroh: OnceCell<Iroh>,
}

pub struct ContactId(u32);

impl ContactId {
    /// The owner of the account.
    ///
    /// The email-address is set by `set_config` using "addr".
    pub const SELF: ContactId = ContactId::new(1);

    /// ID of the contact for device messages.
    pub const DEVICE: ContactId = ContactId::new(5);
    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
}

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}

pub enum Config {
    /// Email address, used in the `From:` field.
    Addr,

    /// IMAP server hostname.
    MailServer,

    /// IMAP server username.
    MailUser,

    /// IMAP server password.
    MailPw,

    /// IMAP server port.
    MailPort,

    /// IMAP server security (e.g. TLS, STARTTLS).
    MailSecurity,

    /// How to check IMAP server TLS certificates.
    ImapCertificateChecks,

    /// SMTP server hostname.
    SendServer,

    /// SMTP server username.
    SendUser,

    /// SMTP server password.
    SendPw,

    /// SMTP server port.
    SendPort,

    /// SMTP server security (e.g. TLS, STARTTLS).
    SendSecurity,

    /// How to check SMTP server TLS certificates.
    SmtpCertificateChecks,

    /// Whether to use OAuth 2.
    ///
    /// Historically contained other bitflags, which are now deprecated.
    /// Should not be extended in the future, create new config keys instead.
    ServerFlags,

    /// True if SOCKS5 is enabled.
    ///
    /// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
    Socks5Enabled,

    /// SOCKS5 proxy server hostname or address.
    Socks5Host,

    /// SOCKS5 proxy server port.
    Socks5Port,

    /// SOCKS5 proxy server username.
    Socks5User,

    /// SOCKS5 proxy server password.
    Socks5Password,

    /// Own name to use in the `From:` field when sending messages.
    Displayname,

    /// Own status to display, sent in message footer.
    Selfstatus,

    /// Own avatar filename.
    Selfavatar,

    /// Send BCC copy to self.
    ///
    /// Should be enabled for multidevice setups.
    #[strum(props(default = "1"))]
    BccSelf,

    /// True if encryption is preferred according to Autocrypt standard.
    #[strum(props(default = "1"))]
    E2eeEnabled,

    /// True if Message Delivery Notifications (read receipts) should
    /// be sent and requested.
    #[strum(props(default = "1"))]
    MdnsEnabled,

    /// True if "Sent" folder should be watched for changes.
    #[strum(props(default = "0"))]
    SentboxWatch,

    /// True if chat messages should be moved to a separate folder.
    #[strum(props(default = "1"))]
    MvboxMove,

    /// Watch for new messages in the "Mvbox" (aka DeltaChat folder) only.
    ///
    /// This will not entirely disable other folders, e.g. the spam folder will also still
    /// be watched for new messages.
    #[strum(props(default = "0"))]
    OnlyFetchMvbox,

    /// Whether to show classic emails or only chat messages.
    #[strum(props(default = "2"))] // also change ShowEmails.default() on changes
    ShowEmails,

    /// Quality of the media files to send.
    #[strum(props(default = "0"))] // also change MediaQuality.default() on changes
    MediaQuality,

    /// If set to "1", on the first time `start_io()` is called after configuring,
    /// the newest existing messages are fetched.
    /// Existing recipients are added to the contact database regardless of this setting.
    #[strum(props(default = "0"))]
    FetchExistingMsgs,

    /// If set to "1", then existing messages are considered to be already fetched.
    /// This flag is reset after successful configuration.
    #[strum(props(default = "1"))]
    FetchedExistingMsgs,

    /// Type of the OpenPGP key to generate.
    #[strum(props(default = "0"))]
    KeyGenType,

    /// Timer in seconds after which the message is deleted from the
    /// server.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    ///
    /// Value 1 is treated as "delete at once": messages are deleted
    /// immediately, without moving to DeltaChat folder.
    #[strum(props(default = "0"))]
    DeleteServerAfter,

    /// Timer in seconds after which the message is deleted from the
    /// device.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    #[strum(props(default = "0"))]
    DeleteDeviceAfter,

    /// Move messages to the Trash folder instead of marking them "\Deleted". Overrides
    /// `ProviderOptions::delete_to_trash`.
    DeleteToTrash,

    /// Save raw MIME messages with headers in the database if true.
    SaveMimeHeaders,

    /// The primary email address. Also see `SecondaryAddrs`.
    ConfiguredAddr,

    /// Configured IMAP server hostname.
    ConfiguredMailServer,

    /// Configured IMAP server username.
    ConfiguredMailUser,

    /// Configured IMAP server password.
    ConfiguredMailPw,

    /// Configured IMAP server port.
    ConfiguredMailPort,

    /// Configured IMAP server security (e.g. TLS, STARTTLS).
    ConfiguredMailSecurity,

    /// How to check IMAP server TLS certificates.
    ConfiguredImapCertificateChecks,

    /// Configured SMTP server hostname.
    ConfiguredSendServer,

    /// Configured SMTP server username.
    ConfiguredSendUser,

    /// Configured SMTP server password.
    ConfiguredSendPw,

    /// Configured SMTP server port.
    ConfiguredSendPort,

    /// How to check SMTP server TLS certificates.
    ConfiguredSmtpCertificateChecks,

    /// Whether OAuth 2 is used with configured provider.
    ConfiguredServerFlags,

    /// Configured SMTP server security (e.g. TLS, STARTTLS).
    ConfiguredSendSecurity,

    /// Configured folder for incoming messages.
    ConfiguredInboxFolder,

    /// Configured folder for chat messages.
    ConfiguredMvboxFolder,

    /// Configured "Sent" folder.
    ConfiguredSentboxFolder,

    /// Configured "Trash" folder.
    ConfiguredTrashFolder,

    /// Unix timestamp of the last successful configuration.
    ConfiguredTimestamp,

    /// ID of the configured provider from the provider database.
    ConfiguredProvider,

    /// True if account is configured.
    Configured,

    /// True if account is a chatmail account.
    IsChatmail,

    /// All secondary self addresses separated by spaces
    /// (`addr1@example.org addr2@example.org addr3@example.org`)
    SecondaryAddrs,

    /// Read-only core version string.
    #[strum(serialize = "sys.version")]
    SysVersion,

    /// Maximal recommended attachment size in bytes.
    #[strum(serialize = "sys.msgsize_max_recommended")]
    SysMsgsizeMaxRecommended,

    /// Space separated list of all config keys available.
    #[strum(serialize = "sys.config_keys")]
    SysConfigKeys,

    /// True if it is a bot account.
    Bot,

    /// True when to skip initial start messages in groups.
    #[strum(props(default = "0"))]
    SkipStartMessages,

    /// Whether we send a warning if the password is wrong (set to false when we send a warning
    /// because we do not want to send a second warning)
    #[strum(props(default = "0"))]
    NotifyAboutWrongPw,

    /// If a warning about exceeding quota was shown recently,
    /// this is the percentage of quota at the time the warning was given.
    /// Unset, when quota falls below minimal warning threshold again.
    QuotaExceeding,

    /// address to webrtc instance to use for videochats
    WebrtcInstance,

    /// Timestamp of the last time housekeeping was run
    LastHousekeeping,

    /// Timestamp of the last `CantDecryptOutgoingMsgs` notification.
    LastCantDecryptOutgoingMsgs,

    /// To how many seconds to debounce scan_all_folders. Used mainly in tests, to disable debouncing completely.
    #[strum(props(default = "60"))]
    ScanAllFoldersDebounceSecs,

    /// Whether to avoid using IMAP IDLE even if the server supports it.
    ///
    /// This is a developer option for testing "fake idle".
    #[strum(props(default = "0"))]
    DisableIdle,

    /// Defines the max. size (in bytes) of messages downloaded automatically.
    /// 0 = no limit.
    #[strum(props(default = "0"))]
    DownloadLimit,

    /// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set.
    #[strum(props(default = "1"))]
    SyncMsgs,

    /// Space-separated list of all the authserv-ids which we believe
    /// may be the one of our email server.
    ///
    /// See `crate::authres::update_authservid_candidates`.
    AuthservIdCandidates,

    /// Make all outgoing messages with Autocrypt header "multipart/signed".
    SignUnencrypted,

    /// Let the core save all events to the database.
    /// This value is used internally to remember the MsgId of the logging xdc
    #[strum(props(default = "0"))]
    DebugLogging,

    /// Last message processed by the bot.
    LastMsgId,

    /// How often to gossip Autocrypt keys in chats with multiple recipients, in seconds. 2 days by
    /// default.
    ///
    /// This is not supposed to be changed by UIs and only used for testing.
    #[strum(props(default = "172800"))]
    GossipPeriod,

    /// Feature flag for verified 1:1 chats; the UI should set it
    /// to 1 if it supports verified 1:1 chats.
    /// Regardless of this setting, `chat.is_protected()` returns true while the key is verified,
    /// and when the key changes, an info message is posted into the chat.
    /// 0=Nothing else happens when the key changes.
    /// 1=After the key changed, `can_send()` returns false and `is_protection_broken()` returns true
    /// until `chat_id.accept()` is called.
    #[strum(props(default = "0"))]
    VerifiedOneOnOneChats,

    /// Row ID of the key in the `keypairs` table
    /// used for signatures, encryption to self and included in `Autocrypt` header.
    KeyId,

    /// This key is sent to the self_reporting bot so that the bot can recognize the user
    /// without storing the email address
    SelfReportingId,

    /// MsgId of webxdc map integration.
    WebxdcIntegration,

    /// Iroh secret key.
    IrohSecretKey,
}

pub enum Origin {
    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
    /// about origin of the contact.
    #[default]
    Unknown = 0,

    /// The contact is a mailing list address, needed to unblock mailing lists
    MailinglistAddress = 0x2,

    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
    Hidden = 0x8,

    /// From: of incoming messages of unknown sender
    IncomingUnknownFrom = 0x10,

    /// Cc: of incoming messages of unknown sender
    IncomingUnknownCc = 0x20,

    /// To: of incoming messages of unknown sender
    IncomingUnknownTo = 0x40,

    /// address scanned but not verified
    UnhandledQrScan = 0x80,

    /// Reply-To: of incoming message of known sender
    /// Contacts with at least this origin value are shown in the contact list.
    IncomingReplyTo = 0x100,

    /// Cc: of incoming message of known sender
    IncomingCc = 0x200,

    /// additional To:'s of incoming message of known sender
    IncomingTo = 0x400,

    /// a chat was manually created for this user, but no message yet sent
    CreateChat = 0x800,

    /// message sent by us
    OutgoingBcc = 0x1000,

    /// message sent by us
    OutgoingCc = 0x2000,

    /// message sent by us
    OutgoingTo = 0x4000,

    /// internal use
    Internal = 0x40000,

    /// address is in our address book
    AddressBook = 0x80000,

    /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
    SecurejoinInvited = 0x0100_0000,

    /// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
    SecurejoinJoined = 0x0200_0000,

    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
    ManuallyCreated = 0x0400_0000,
}


dc_array_t* dc_get_chat_contacts(dc_context_t* context, uint32_t chat_id)
{
	/* Normal chats do not include SELF.  Group chats do (as it may happen that one is deleted from a
	groupchat but the chats stays visible, moreover, this makes displaying lists easier) */
	dc_array_t*   ret = dc_array_new(context, 100);
	sqlite3_stmt* stmt = NULL;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		goto cleanup;
	}

	stmt = dc_sqlite3_prepare(context->sql,
		"SELECT cc.contact_id FROM chats_contacts cc"
			" LEFT JOIN contacts c ON c.id=cc.contact_id"
			" WHERE cc.chat_id=?"
			" ORDER BY c.id=1, c.last_seen DESC, c.id DESC;");
	sqlite3_bind_int(stmt, 1, chat_id);
	while (sqlite3_step(stmt)==SQLITE_ROW) {
		dc_array_add_id(ret, sqlite3_column_int(stmt, 0));
	}

cleanup:
	sqlite3_finalize(stmt);
	return ret;
}

pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
    // Normal chats do not include SELF.  Group chats do (as it may happen that one is deleted from a
    // groupchat but the chats stays visible, moreover, this makes displaying lists easier)

    let list = context
        .sql
        .query_map(
            "SELECT cc.contact_id
               FROM chats_contacts cc
               LEFT JOIN contacts c
                      ON c.id=cc.contact_id
              WHERE cc.chat_id=?
              ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
            (chat_id,),
            |row| row.get::<_, ContactId>(0),
            |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
        )
        .await?;

    Ok(list)
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct InnerContext {
    /// Blob directory path
    pub(crate) blobdir: PathBuf,
    pub(crate) sql: Sql,
    pub(crate) smeared_timestamp: SmearedTimestamp,
    /// The global "ongoing" process state.
    ///
    /// This is a global mutex-like state for operations which should be modal in the
    /// clients.
    running_state: RwLock<RunningState>,
    /// Mutex to avoid generating the key for the user more than once.
    pub(crate) generating_key_mutex: Mutex<()>,
    /// Mutex to enforce only a single running oauth2 is running.
    pub(crate) oauth2_mutex: Mutex<()>,
    /// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
    pub(crate) wrong_pw_warning_mutex: Mutex<()>,
    pub(crate) translated_stockstrings: StockStrings,
    pub(crate) events: Events,

    pub(crate) scheduler: SchedulerState,
    pub(crate) ratelimit: RwLock<Ratelimit>,

    /// Recently loaded quota information, if any.
    /// Set to `None` if quota was never tried to load.
    pub(crate) quota: RwLock<Option<QuotaInfo>>,

    /// IMAP UID resync request.
    pub(crate) resync_request: AtomicBool,

    /// Notify about new messages.
    ///
    /// This causes [`Context::wait_next_msgs`] to wake up.
    pub(crate) new_msgs_notify: Notify,

    /// Server ID response if ID capability is supported
    /// and the server returned non-NIL on the inbox connection.
    /// <https://datatracker.ietf.org/doc/html/rfc2971>
    pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,

    /// IMAP METADATA.
    pub(crate) metadata: RwLock<Option<ServerMetadata>>,

    pub(crate) last_full_folder_scan: Mutex<Option<tools::Time>>,

    /// ID for this `Context` in the current process.
    ///
    /// This allows for multiple `Context`s open in a single process where each context can
    /// be identified by this ID.
    pub(crate) id: u32,

    creation_time: tools::Time,

    /// The text of the last error logged and emitted as an event.
    /// If the ui wants to display an error after a failure,
    /// `last_error` should be used to avoid races with the event thread.
    pub(crate) last_error: std::sync::RwLock<String>,

    /// If debug logging is enabled, this contains all necessary information
    ///
    /// Standard RwLock instead of [`tokio::sync::RwLock`] is used
    /// because the lock is used from synchronous [`Context::emit_event`].
    pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,

    /// Push subscriber to store device token
    /// and register for heartbeat notifications.
    pub(crate) push_subscriber: PushSubscriber,

    /// True if account has subscribed to push notifications via IMAP.
    pub(crate) push_subscribed: AtomicBool,

    /// Iroh for realtime peer channels.
    pub(crate) iroh: OnceCell<Iroh>,
}

pub async fn query_map<T, F, G, H>(
        &self,
        sql: &str,
        params: impl rusqlite::Params + Send,
        f: F,
        mut g: G,
    ) -> Result<H>
    where
        F: Send + FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
        G: Send + FnMut(rusqlite::MappedRows<F>) -> Result<H>,
        H: Send + 'static,
    {
        self.call(move |conn| {
            let mut stmt = conn.prepare(sql)?;
            let res = stmt.query_map(params, f)?;
            g(res)
        })
        .await
    }

pub struct ChatId(u32);

pub struct ContactId(u32);

use std::cmp;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use anyhow::{anyhow, bail, ensure, Context as _, Result};
use deltachat_contact_tools::{strip_rtlo_characters, ContactAddress};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use tokio::task;
use crate::aheader::EncryptPreference;
use crate::blob::BlobObject;
use crate::chatlist::Chatlist;
use crate::chatlist_events;
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{
    self, Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK,
    DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS,
};
use crate::contact::{self, Contact, ContactId, Origin};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc;
use crate::download::DownloadState;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
use crate::html::new_html_mimepart;
use crate::location;
use crate::log::LogExt;
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::receive_imf::ReceivedMsg;
use crate::securejoin::BobState;
use crate::smtp::send_msg_to_smtp;
use crate::sql;
use crate::stock_str;
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{
    buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
    create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input,
    smeared_time, time, IsNoneOrEmpty, SystemTime,
};
use crate::webxdc::WEBXDC_SUFFIX;
use CantSendReason::*;
use super::*;
use crate::chatlist::get_archived_cnt;
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
use crate::message::delete_msgs;
use crate::receive_imf::receive_imf;
use crate::test_utils::{sync, TestContext, TestContextManager};
use strum::IntoEnumIterator;
use tokio::fs;


char* dc_create_setup_code(dc_context_t* context)
{
	#define         CODE_ELEMS 9
	uint16_t        random_val = 0;
	int             i = 0;
	dc_strbuilder_t ret;
	dc_strbuilder_init(&ret, 0);

	for (i = 0; i < CODE_ELEMS; i++)
	{
		do
		{
			if (!RAND_bytes((unsigned char*)&random_val, sizeof(uint16_t))) {
				dc_log_warning(context, 0, "Falling back to pseudo-number generation for the setup code.");
				RAND_pseudo_bytes((unsigned char*)&random_val, sizeof(uint16_t));
			}
		}
		while (random_val > 60000); /* make sure the modulo below does not reduce entropy (range is 0..65535, a module 10000 would make appearing values <=535 one time more often than other values) */

		random_val = random_val % 10000; /* force all blocks into the range 0..9999 */

		dc_strbuilder_catf(&ret, "%s%04i", i?"-":"", (int)random_val);
	}

	return ret.buf;
}

pub fn create_setup_code(_context: &Context) -> String {
    let mut random_val: u16;
    let mut rng = thread_rng();
    let mut ret = String::new();

    for i in 0..9 {
        loop {
            random_val = rng.gen();
            if random_val as usize <= 60000 {
                break;
            }
        }
        random_val = (random_val as usize % 10000) as u16;
        ret += &format!(
            "{}{:04}",
            if 0 != i { "-" } else { "" },
            random_val as usize
        );
    }

    ret
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

use std::any::Any;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use ::pgp::types::KeyTrait;
use anyhow::{bail, ensure, format_err, Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use futures::StreamExt;
use futures_lite::FutureExt;
use rand::{thread_rng, Rng};
use tokio::fs::{self, File};
use tokio_tar::Archive;
use crate::blob::{BlobDirContents, BlobObject};
use crate::chat::{self, delete_and_reset_all_device_msgs, ChatId};
use crate::config::Config;
use crate::contact::ContactId;
use crate::context::Context;
use crate::e2ee;
use crate::events::EventType;
use crate::key::{
    self, load_self_secret_key, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey,
};
use crate::log::LogExt;
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::pgp;
use crate::sql;
use crate::stock_str;
use crate::tools::{
    create_folder, delete_file, get_filesuffix_lc, open_file_std, read_file, time, write_file,
};
use transfer::{get_backup, BackupProvider};
use std::time::Duration;
use ::pgp::armor::BlockType;
use tokio::task;
use super::*;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::receive_imf::receive_imf;
use crate::stock_str::StockMessage;
use crate::test_utils::{alice_keypair, TestContext, TestContextManager};


char* dc_contact_get_name(const dc_contact_t* contact)
{
	if (contact==NULL || contact->magic!=DC_CONTACT_MAGIC) {
		return dc_strdup(NULL);
	}

	return dc_strdup(contact->name);
}

pub fn get_name(&self) -> &str {
        &self.name
    }

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}

use std::cmp::{min, Reverse};
use std::collections::BinaryHeap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use base64::Engine as _;
use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{
    self as contact_tools, addr_cmp, addr_normalize, sanitize_name_and_addr, strip_rtlo_characters,
    ContactAddress, VcardContact,
};
use deltachat_derive::{FromSql, ToSql};
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
use tokio::task;
use tokio::time::{timeout, Duration};
use crate::aheader::{Aheader, EncryptPreference};
use crate::blob::BlobObject;
use crate::chat::{ChatId, ChatIdBlocked, ProtectionStatus};
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{load_self_public_key, DcKey, SignedPublicKey};
use crate::log::LogExt;
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::mimeparser::AvatarAction;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::sql::{self, params_iter};
use crate::sync::{self, Sync::*};
use crate::tools::{
    duration_to_str, get_abs_path, improve_single_line_input, smeared_time, time, SystemTime,
};
use crate::{chat, chatlist_events, stock_str};
use deltachat_contact_tools::{may_be_valid_addr, normalize_name};
use super::*;
use crate::chat::{get_chat_contacts, send_text_msg, Chat};
use crate::chatlist::Chatlist;
use crate::receive_imf::receive_imf;
use crate::test_utils::{self, TestContext, TestContextManager};


* See also dc_send_msg().
 *
 * @memberof dc_context_t
 * @param context The context object as returned from dc_context_new().
 * @param chat_id Chat ID to send the text message to.
 * @param text_to_send Text to send to the chat defined by the chat ID.
 *     Passing an empty text here causes an empty text to be sent,
 *     it's up to the caller to handle this if undesired.
 *     Passing NULL as the text causes the function to return 0.
 * @return The ID of the message that is about being sent.
 */
uint32_t dc_send_text_msg(dc_context_t* context, uint32_t chat_id, const char* text_to_send)
{
	dc_msg_t* msg = dc_msg_new(context, DC_MSG_TEXT);
	uint32_t  ret = 0;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC || chat_id<=DC_CHAT_ID_LAST_SPECIAL || text_to_send==NULL) {
		goto cleanup;
	}

	msg->text = dc_strdup(text_to_send);

	ret = dc_send_msg(context, chat_id, msg);

cleanup:
	dc_msg_unref(msg);
	return ret;
}

pub async fn send_text_msg(
    context: &Context,
    chat_id: ChatId,
    text_to_send: String,
) -> Result<MsgId> {
    ensure!(
        !chat_id.is_special(),
        "bad chat_id, can not be a special chat: {}",
        chat_id
    );

    let mut msg = Message::new(Viewtype::Text);
    msg.text = text_to_send;
    send_msg(context, chat_id, &mut msg).await
}

pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
    if chat_id.is_unset() {
        let forwards = msg.param.get(Param::PrepForwards);
        if let Some(forwards) = forwards {
            for forward in forwards.split(' ') {
                if let Ok(msg_id) = forward.parse::<u32>().map(MsgId::new) {
                    if let Ok(mut msg) = Message::load_from_db(context, msg_id).await {
                        send_msg_inner(context, chat_id, &mut msg).await?;
                    };
                }
            }
            msg.param.remove(Param::PrepForwards);
            msg.update_param(context).await?;
        }
        return send_msg_inner(context, chat_id, msg).await;
    }

    if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
        msg.param.remove(Param::GuaranteeE2ee);
        msg.param.remove(Param::ForcePlaintext);
        msg.update_param(context).await?;
    }
    send_msg_inner(context, chat_id, msg).await
}

pub fn is_special(self) -> bool {
        (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct MsgId(u32);

pub struct ChatId(u32);

impl Message {
    /// Creates a new message with given view type.
    pub fn new(viewtype: Viewtype) -> Self {
        Message {
            viewtype,
            ..Default::default()
        }
    }
}

pub enum Viewtype {
    /// Unknown message type.
    #[default]
    Unknown = 0,

    /// Text message.
    /// The text of the message is set using dc_msg_set_text() and retrieved with dc_msg_get_text().
    Text = 10,

    /// Image message.
    /// If the image is a GIF and has the appropriate extension, the viewtype is auto-changed to
    /// `Gif` when sending the message.
    /// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
    /// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
    Image = 20,

    /// Animated GIF message.
    /// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
    Gif = 21,

    /// Message containing a sticker, similar to image.
    /// If possible, the ui should display the image without borders in a transparent way.
    /// A click on a sticker will offer to install the sticker set in some future.
    Sticker = 23,

    /// Message containing an Audio file.
    /// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
    Audio = 40,

    /// A voice message that was directly recorded by the user.
    /// For all other audio messages, the type #DC_MSG_AUDIO should be used.
    /// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
    Voice = 41,

    /// Video messages.
    /// File, width, height and durarion
    /// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
    /// and retrieved via
    /// dc_msg_get_file(), dc_msg_get_width(),
    /// dc_msg_get_height(), dc_msg_get_duration().
    Video = 50,

    /// Message containing any file, eg. a PDF.
    /// The file is set via dc_msg_set_file()
    /// and retrieved via dc_msg_get_file().
    File = 60,

    /// Message is an invitation to a videochat.
    VideochatInvitation = 70,

    /// Message is an webxdc instance.
    Webxdc = 80,

    /// Message containing shared contacts represented as a vCard (virtual contact file)
    /// with email addresses and possibly other fields.
    /// Use `parse_vcard()` to retrieve them.
    Vcard = 90,
}


char* dc_contact_get_addr(const dc_contact_t* contact)
{
	if (contact==NULL || contact->magic!=DC_CONTACT_MAGIC) {
		return dc_strdup(NULL);
	}

	return dc_strdup(contact->addr);
}

pub fn get_addr(&self) -> &str {
        &self.addr
    }


*      unless it was edited manually by dc_create_contact() before.
 * @return The number of modified or added contacts.
 */
int dc_add_address_book(dc_context_t* context, const char* adr_book)
{
	carray* lines = NULL;
	size_t  i = 0;
	size_t  iCnt = 0;
	int     sth_modified = 0;
	int     modify_cnt = 0;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC || adr_book==NULL) {
		goto cleanup;
	}

	if ((lines=dc_split_into_lines(adr_book))==NULL) {
		goto cleanup;
	}

	dc_sqlite3_begin_transaction(context->sql);

		iCnt = carray_count(lines);
		for (i = 0; i+1 < iCnt; i += 2) {
			char* name = (char*)carray_get(lines, i);
			char* addr = (char*)carray_get(lines, i+1);
			dc_normalize_name(name);
            dc_normalize_addr(addr);
			dc_add_or_lookup_contact(context, name, addr, DC_ORIGIN_ADRESS_BOOK, &sth_modified);
			if (sth_modified) {
				modify_cnt++;
			}
		}

	dc_sqlite3_commit(context->sql);

	if (modify_cnt) {
		context->cb(context, DC_EVENT_CONTACTS_CHANGED, 0, 0);
	}

cleanup:
	dc_free_splitted_lines(lines);

	return modify_cnt;
}

pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
        let mut modify_cnt = 0;

        for (name, addr) in split_address_book(addr_book) {
            let (name, addr) = sanitize_name_and_addr(name, addr);
            match ContactAddress::new(&addr) {
                Ok(addr) => {
                    match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
                        Ok((_, modified)) => {
                            if modified != Modifier::None {
                                modify_cnt += 1
                            }
                        }
                        Err(err) => {
                            warn!(
                                context,
                                "Failed to add address {} from address book: {}", addr, err
                            );
                        }
                    }
                }
                Err(err) => {
                    warn!(context, "{:#}.", err);
                }
            }
        }
        if modify_cnt > 0 {
            context.emit_event(EventType::ContactsChanged(None));
        }

        Ok(modify_cnt)
    }

fn split_address_book(book: &str) -> Vec<(&str, &str)> {
    book.lines()
        .collect::<Vec<&str>>()
        .chunks(2)
        .filter_map(|chunk| {
            let name = chunk.first()?;
            let addr = chunk.get(1)?;
            Some((*name, *addr))
        })
        .collect()
}

pub(crate) async fn add_or_lookup(
        context: &Context,
        name: &str,
        addr: &ContactAddress,
        mut origin: Origin,
    ) -> Result<(ContactId, Modifier)> {
        let mut sth_modified = Modifier::None;

        ensure!(!addr.is_empty(), "Can not add_or_lookup empty address");
        ensure!(origin != Origin::Unknown, "Missing valid origin");

        if context.is_self_addr(addr).await? {
            return Ok((ContactId::SELF, sth_modified));
        }

        let mut name = strip_rtlo_characters(name);
        #[allow(clippy::collapsible_if)]
        if origin <= Origin::OutgoingTo {
            // The user may accidentally have written to a "noreply" address with another MUA:
            if addr.contains("noreply")
                || addr.contains("no-reply")
                || addr.starts_with("notifications@")
                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
                || (addr.len() > 50 && addr.contains('+'))
            {
                info!(context, "hiding contact {}", addr);
                origin = Origin::Hidden;
                // For these kind of email addresses, sender and address often don't belong together
                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
                // be saved as the displayname for notifications@github.com.
                name = "".to_string();
            }
        }

        // If the origin indicates that user entered the contact manually, from the address book or
        // from the QR-code scan (potentially from the address book of their other phone), then name
        // should go into the "name" column and never into "authname" column, to avoid leaking it
        // into the network.
        let manual = matches!(
            origin,
            Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
        );

        let mut update_addr = false;

        let row_id = context.sql.transaction(|transaction| {
            let row = transaction.query_row(
                "SELECT id, name, addr, origin, authname
                 FROM contacts WHERE addr=? COLLATE NOCASE",
                 [addr.to_string()],
                |row| {
                    let row_id: isize = row.get(0)?;
                    let row_name: String = row.get(1)?;
                    let row_addr: String = row.get(2)?;
                    let row_origin: Origin = row.get(3)?;
                    let row_authname: String = row.get(4)?;

                    Ok((row_id, row_name, row_addr, row_origin, row_authname))
                }).optional()?;

            let row_id;
            if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
                let update_name = manual && name != row_name;
                let update_authname = !manual
                    && name != row_authname
                    && !name.is_empty()
                    && (origin >= row_origin
                        || origin == Origin::IncomingUnknownFrom
                        || row_authname.is_empty());

                row_id = u32::try_from(id)?;
                if origin >= row_origin && addr.as_ref() != row_addr {
                    update_addr = true;
                }
                if update_name || update_authname || update_addr || origin > row_origin {
                    let new_name = if update_name {
                        name.to_string()
                    } else {
                        row_name
                    };

                    transaction
                        .execute(
                            "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
                            (
                                new_name,
                                if update_addr {
                                    addr.to_string()
                                } else {
                                    row_addr
                                },
                                if origin > row_origin {
                                    origin
                                } else {
                                    row_origin
                                },
                                if update_authname {
                                    name.to_string()
                                } else {
                                    row_authname
                                },
                                row_id
                            ),
                        )?;

                    if update_name || update_authname {
                        // Update the contact name also if it is used as a group name.
                        // This is one of the few duplicated data, however, getting the chat list is easier this way.
                        let chat_id: Option<ChatId> = transaction.query_row(
                            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
                            (Chattype::Single, isize::try_from(row_id)?),
                            |row| {
                                let chat_id: ChatId = row.get(0)?;
                                Ok(chat_id)
                            }
                        ).optional()?;

                        if let Some(chat_id) = chat_id {
                            let contact_id = ContactId::new(row_id);
                            let (addr, name, authname) =
                                transaction.query_row(
                                    "SELECT addr, name, authname
                                     FROM contacts
                                     WHERE id=?",
                                     (contact_id,),
                                |row| {
                                    let addr: String = row.get(0)?;
                                    let name: String = row.get(1)?;
                                    let authname: String = row.get(2)?;
                                    Ok((addr, name, authname))
                                })?;

                            let chat_name = if !name.is_empty() {
                                name
                            } else if !authname.is_empty() {
                                authname
                            } else {
                                addr
                            };

                            let count = transaction.execute(
                                    "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
                                    (chat_name, chat_id))?;

                            if count > 0 {
                                // Chat name updated
                                context.emit_event(EventType::ChatModified(chat_id));
                                chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
                            }
                        }
                    }
                    sth_modified = Modifier::Modified;
                }
            } else {
                let update_name = manual;
                let update_authname = !manual;

                transaction
                    .execute(
                        "INSERT INTO contacts (name, addr, origin, authname)
                         VALUES (?, ?, ?, ?);",
                         (
                            if update_name {
                                name.to_string()
                            } else {
                                "".to_string()
                            },
                            &addr,
                            origin,
                            if update_authname {
                                name.to_string()
                            } else {
                                "".to_string()
                            }
                        ),
                    )?;

                sth_modified = Modifier::Created;
                row_id = u32::try_from(transaction.last_insert_rowid())?;
                info!(context, "Added contact id={row_id} addr={addr}.");
            }
            Ok(row_id)
        }).await?;

        let contact_id = ContactId::new(row_id);

        Ok((contact_id, sth_modified))
    }

impl ContactAddress {
    /// Constructs a new contact address from string,
    /// normalizing and validating it.
    pub fn new(s: &str) -> Result<Self> {
        let addr = addr_normalize(s);
        if !may_be_valid_addr(&addr) {
            bail!("invalid address {:?}", s);
        }
        Ok(Self(addr.to_string()))
    }
}

macro_rules! warn {
    ($ctx:expr, $msg:expr) => {
        warn!($ctx, $msg,)
    };
    ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
        let formatted = format!($msg, $($args),*);
        let full = format!("{file}:{line}: {msg}",
                           file = file!(),
                           line = line!(),
                           msg = &formatted);
        $ctx.emit_event($crate::EventType::Warning(full));
    }};
}

pub fn emit_event(&self, event: EventType) {
        {
            let lock = self.debug_logging.read().expect("RwLock is poisoned");
            if let Some(debug_logging) = &*lock {
                debug_logging.log_event(event.clone());
            }
        }
        self.events.emit(Event {
            id: self.id,
            typ: event,
        });
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub enum Origin {
    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
    /// about origin of the contact.
    #[default]
    Unknown = 0,

    /// The contact is a mailing list address, needed to unblock mailing lists
    MailinglistAddress = 0x2,

    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
    Hidden = 0x8,

    /// From: of incoming messages of unknown sender
    IncomingUnknownFrom = 0x10,

    /// Cc: of incoming messages of unknown sender
    IncomingUnknownCc = 0x20,

    /// To: of incoming messages of unknown sender
    IncomingUnknownTo = 0x40,

    /// address scanned but not verified
    UnhandledQrScan = 0x80,

    /// Reply-To: of incoming message of known sender
    /// Contacts with at least this origin value are shown in the contact list.
    IncomingReplyTo = 0x100,

    /// Cc: of incoming message of known sender
    IncomingCc = 0x200,

    /// additional To:'s of incoming message of known sender
    IncomingTo = 0x400,

    /// a chat was manually created for this user, but no message yet sent
    CreateChat = 0x800,

    /// message sent by us
    OutgoingBcc = 0x1000,

    /// message sent by us
    OutgoingCc = 0x2000,

    /// message sent by us
    OutgoingTo = 0x4000,

    /// internal use
    Internal = 0x40000,

    /// address is in our address book
    AddressBook = 0x80000,

    /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
    SecurejoinInvited = 0x0100_0000,

    /// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
    SecurejoinJoined = 0x0200_0000,

    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
    ManuallyCreated = 0x0400_0000,
}

pub enum EventType {
    /// The library-user may write an informational string to the log.
    ///
    /// This event should *not* be reported to the end-user using a popup or something like
    /// that.
    Info(String),

    /// Emitted when SMTP connection is established and login was successful.
    SmtpConnected(String),

    /// Emitted when IMAP connection is established and login was successful.
    ImapConnected(String),

    /// Emitted when a message was successfully sent to the SMTP server.
    SmtpMessageSent(String),

    /// Emitted when an IMAP message has been marked as deleted
    ImapMessageDeleted(String),

    /// Emitted when an IMAP message has been moved
    ImapMessageMoved(String),

    /// Emitted before going into IDLE on the Inbox folder.
    ImapInboxIdle,

    /// Emitted when an new file in the $BLOBDIR was created
    NewBlobFile(String),

    /// Emitted when an file in the $BLOBDIR was deleted
    DeletedBlobFile(String),

    /// The library-user should write a warning string to the log.
    ///
    /// This event should *not* be reported to the end-user using a popup or something like
    /// that.
    Warning(String),

    /// The library-user should report an error to the end-user.
    ///
    /// As most things are asynchronous, things may go wrong at any time and the user
    /// should not be disturbed by a dialog or so.  Instead, use a bubble or so.
    ///
    /// However, for ongoing processes (eg. configure())
    /// or for functions that are expected to fail (eg. dc_continue_key_transfer())
    /// it might be better to delay showing these events until the function has really
    /// failed (returned false). It should be sufficient to report only the *last* error
    /// in a messasge box then.
    Error(String),

    /// An action cannot be performed because the user is not in the group.
    /// Reported eg. after a call to
    /// dc_set_chat_name(), dc_set_chat_profile_image(),
    /// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
    /// dc_send_text_msg() or another sending function.
    ErrorSelfNotInGroup(String),

    /// Messages or chats changed.  One or more messages or chats changed for various
    /// reasons in the database:
    /// - Messages sent, received or removed
    /// - Chats created, deleted or archived
    /// - A draft has been set
    ///
    MsgsChanged {
        /// Set if only a single chat is affected by the changes, otherwise 0.
        chat_id: ChatId,

        /// Set if only a single message is affected by the changes, otherwise 0.
        msg_id: MsgId,
    },

    /// Reactions for the message changed.
    ReactionsChanged {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message for which reactions were changed.
        msg_id: MsgId,

        /// ID of the contact whose reaction set is changed.
        contact_id: ContactId,
    },

    /// There is a fresh message. Typically, the user will show an notification
    /// when receiving this message.
    ///
    /// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
    IncomingMsg {
        /// ID of the chat where the message is assigned.
        chat_id: ChatId,

        /// ID of the message.
        msg_id: MsgId,
    },

    /// Downloading a bunch of messages just finished.
    IncomingMsgBunch,

    /// Messages were seen or noticed.
    /// chat id is always set.
    MsgsNoticed(ChatId),

    /// A single message is sent successfully. State changed from  DC_STATE_OUT_PENDING to
    /// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
    MsgDelivered {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that was successfully sent.
        msg_id: MsgId,
    },

    /// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
    /// DC_STATE_OUT_FAILED, see dc_msg_get_state().
    MsgFailed {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that could not be sent.
        msg_id: MsgId,
    },

    /// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
    /// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
    MsgRead {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that was read.
        msg_id: MsgId,
    },

    /// A single message was deleted.
    ///
    /// This event means that the message will no longer appear in the messagelist.
    /// UI should remove the message from the messagelist
    /// in response to this event if the message is currently displayed.
    ///
    /// The message may have been explicitly deleted by the user or expired.
    /// Internally the message may have been removed from the database,
    /// moved to the trash chat or hidden.
    ///
    /// This event does not indicate the message
    /// deletion from the server.
    MsgDeleted {
        /// ID of the chat where the message was prior to deletion.
        /// Never 0 or trash chat.
        chat_id: ChatId,

        /// ID of the deleted message. Never 0.
        msg_id: MsgId,
    },

    /// Chat changed.  The name or the image of a chat group was changed or members were added or removed.
    /// Or the verify state of a chat has changed.
    /// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
    /// and dc_remove_contact_from_chat().
    ///
    /// This event does not include ephemeral timer modification, which
    /// is a separate event.
    ChatModified(ChatId),

    /// Chat ephemeral timer changed.
    ChatEphemeralTimerModified {
        /// Chat ID.
        chat_id: ChatId,

        /// New ephemeral timer value.
        timer: EphemeralTimer,
    },

    /// Contact(s) created, renamed, blocked, deleted or changed their "recently seen" status.
    ///
    /// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
    ContactsChanged(Option<ContactId>),

    /// Location of one or more contact has changed.
    ///
    /// @param data1 (u32) contact_id of the contact for which the location has changed.
    ///     If the locations of several contacts have been changed,
    ///     eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
    LocationChanged(Option<ContactId>),

    /// Inform about the configuration progress started by configure().
    ConfigureProgress {
        /// Progress.
        ///
        /// 0=error, 1-999=progress in permille, 1000=success and done
        progress: usize,

        /// Progress comment or error, something to display to the user.
        comment: Option<String>,
    },

    /// Inform about the import/export progress started by imex().
    ///
    /// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
    /// @param data2 0
    ImexProgress(usize),

    /// A file has been exported. A file has been written by imex().
    /// This event may be sent multiple times by a single call to imex().
    ///
    /// A typical purpose for a handler of this event may be to make the file public to some system
    /// services.
    ///
    /// @param data2 0
    ImexFileWritten(PathBuf),

    /// Progress information of a secure-join handshake from the view of the inviter
    /// (Alice, the person who shows the QR code).
    ///
    /// These events are typically sent after a joiner has scanned the QR code
    /// generated by dc_get_securejoin_qr().
    SecurejoinInviterProgress {
        /// ID of the contact that wants to join.
        contact_id: ContactId,

        /// Progress as:
        /// 300=vg-/vc-request received, typically shown as "bob@addr joins".
        /// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
        /// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
        /// 1000=Protocol finished for this contact.
        progress: usize,
    },

    /// Progress information of a secure-join handshake from the view of the joiner
    /// (Bob, the person who scans the QR code).
    /// The events are typically sent while dc_join_securejoin(), which
    /// may take some time, is executed.
    SecurejoinJoinerProgress {
        /// ID of the inviting contact.
        contact_id: ContactId,

        /// Progress as:
        /// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
        /// (Bob has verified alice and waits until Alice does the same for him)
        /// 1000=vg-member-added/vc-contact-confirm received
        progress: usize,
    },

    /// The connectivity to the server changed.
    /// This means that you should refresh the connectivity view
    /// and possibly the connectivtiy HTML; see dc_get_connectivity() and
    /// dc_get_connectivity_html() for details.
    ConnectivityChanged,

    /// The user's avatar changed.
    /// Deprecated by `ConfigSynced`.
    SelfavatarChanged,

    /// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
    /// uniformity this is emitted on the source device too. The value isn't here, otherwise it
    /// would be logged which might not be good for privacy.
    ConfigSynced {
        /// Configuration key.
        key: Config,
    },

    /// Webxdc status update received.
    WebxdcStatusUpdate {
        /// Message ID.
        msg_id: MsgId,

        /// Status update ID.
        status_update_serial: StatusUpdateSerial,
    },

    /// Data received over an ephemeral peer channel.
    WebxdcRealtimeData {
        /// Message ID.
        msg_id: MsgId,

        /// Realtime data.
        data: Vec<u8>,
    },

    /// Inform that a message containing a webxdc instance has been deleted.
    WebxdcInstanceDeleted {
        /// ID of the deleted message.
        msg_id: MsgId,
    },

    /// Tells that the Background fetch was completed (or timed out).
    /// This event acts as a marker, when you reach this event you can be sure
    /// that all events emitted during the background fetch were processed.
    ///
    /// This event is only emitted by the account manager
    AccountsBackgroundFetchDone,
    /// Inform that set of chats or the order of the chats in the chatlist has changed.
    ///
    /// Sometimes this is emitted together with `UIChatlistItemChanged`.
    ChatlistChanged,

    /// Inform that a single chat list item changed and needs to be rerendered.
    /// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
    ChatlistItemChanged {
        /// ID of the changed chat
        chat_id: Option<ChatId>,
    },

    /// Event for using in tests, e.g. as a fence between normally generated events.
    #[cfg(test)]
    Test,

    /// Inform than some events have been skipped due to event channel overflow.
    EventChannelOverflow {
        /// Number of events skipped.
        n: u64,
    },
}

pub(crate) enum Modifier {
    None,
    Modified,
    Created,
}

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}


int dc_sqlite3_execute(dc_sqlite3_t* sql, const char* querystr)
{
	int           success = 0;
	sqlite3_stmt* stmt = NULL;
	int           sqlState = 0;

	stmt = dc_sqlite3_prepare(sql, querystr);
	if (stmt==NULL) {
		goto cleanup;
	}

	sqlState = sqlite3_step(stmt);
	if (sqlState != SQLITE_DONE && sqlState != SQLITE_ROW)  {
		dc_sqlite3_log_error(sql, "Cannot execute \"%s\".", querystr);
		goto cleanup;
	}

	success = 1;

cleanup:
	sqlite3_finalize(stmt);
	return success;
}

pub async fn execute(
        &self,
        query: &str,
        params: impl rusqlite::Params + Send,
    ) -> Result<usize> {
        self.call_write(move |conn| {
            let res = conn.execute(query, params)?;
            Ok(res)
        })
        .await
    }

pub async fn call_write<'a, F, R>(&'a self, function: F) -> Result<R>
    where
        F: 'a + FnOnce(&mut Connection) -> Result<R> + Send,
        R: Send + 'static,
    {
        let _lock = self.write_lock().await;
        self.call(function).await
    }

pub struct Params {
    inner: BTreeMap<Param, String>,
}

pub struct Sql {
    /// Database file path
    pub(crate) dbfile: PathBuf,

    /// Write transactions mutex.
    ///
    /// See [`Self::write_lock`].
    write_mtx: Mutex<()>,

    /// SQL connection pool.
    pool: RwLock<Option<Pool>>,

    /// None if the database is not open, true if it is open with passphrase and false if it is
    /// open without a passphrase.
    is_encrypted: RwLock<Option<bool>>,

    /// Cache of `config` table.
    pub(crate) config_cache: RwLock<HashMap<String, Option<String>>>,
}

use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use anyhow::{bail, Context as _, Result};
use rusqlite::{config::DbConfig, types::ValueRef, Connection, OpenFlags, Row};
use tokio::sync::{Mutex, MutexGuard, RwLock};
use crate::blob::BlobObject;
use crate::chat::{self, add_device_msg, update_device_icon, update_saved_messages_icon};
use crate::config::Config;
use crate::constants::DC_CHAT_ID_TRASH;
use crate::context::Context;
use crate::debug_logging::set_debug_logging_xdc;
use crate::ephemeral::start_ephemeral_timers;
use crate::imex::BLOBS_BACKUP_NAME;
use crate::location::delete_orphaned_poi_locations;
use crate::log::LogExt;
use crate::message::{Message, MsgId, Viewtype};
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::stock_str;
use crate::tools::{delete_file, time, SystemTime};
use pool::Pool;
use super::*;
use crate::{test_utils::TestContext, EventType};
use tempfile::tempdir;
use tempfile::tempdir;
use tempfile::tempdir;


* normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
 *
 * To add a number of contacts, see dc_add_address_book() which is much faster for adding
 * a bunch of addresses.
 *
 * May result in a #DC_EVENT_CONTACTS_CHANGED event.
 *
 * @memberof dc_context_t
 * @param context The context object as created by dc_context_new().
 * @param name Name of the contact to add. If you do not know the name belonging
 *     to the address, you can give NULL here.
 * @param addr E-mail-address of the contact to add. If the email address
 *     already exists, the name is updated and the origin is increased to
 *     "manually created".
 * @return Contact ID of the created or reused contact.
 */
uint32_t dc_create_contact(dc_context_t* context, const char* name, const char* addr)
{
	uint32_t contact_id = 0;
	int      sth_modified = 0;
	int      blocked = 0;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC || addr==NULL || addr[0]==0) {
		goto cleanup;
	}

	contact_id = dc_add_or_lookup_contact(context, name, addr, DC_ORIGIN_MANUALLY_CREATED, &sth_modified);

	blocked = dc_is_contact_blocked(context, contact_id);

	context->cb(context, DC_EVENT_CONTACTS_CHANGED, sth_modified==CONTACT_CREATED? contact_id : 0, 0);

	if (blocked) {
		dc_block_contact(context, contact_id, 0);
	}

cleanup:
	return contact_id;
}

pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
        Self::create_ex(context, Sync, name, addr).await
    }

pub(crate) async fn create_ex(
        context: &Context,
        sync: sync::Sync,
        name: &str,
        addr: &str,
    ) -> Result<ContactId> {
        let name = improve_single_line_input(name);

        let (name, addr) = sanitize_name_and_addr(&name, addr);
        let addr = ContactAddress::new(&addr)?;

        let (contact_id, sth_modified) =
            Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
                .await
                .context("add_or_lookup")?;
        let blocked = Contact::is_blocked_load(context, contact_id).await?;
        match sth_modified {
            Modifier::None => {}
            Modifier::Modified | Modifier::Created => {
                context.emit_event(EventType::ContactsChanged(Some(contact_id)))
            }
        }
        if blocked {
            set_blocked(context, Nosync, contact_id, false).await?;
        }

        if sync.into() {
            chat::sync(
                context,
                chat::SyncId::ContactAddr(addr.to_string()),
                chat::SyncAction::Rename(name.to_string()),
            )
            .await
            .log_err(context)
            .ok();
        }
        Ok(contact_id)
    }

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct ContactId(u32);


int dc_msg_get_state(const dc_msg_t* msg)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC) {
		return DC_STATE_UNDEFINED;
	}
	return msg->state;
}

pub fn get_state(&self) -> MessageState {
        self.state
    }

pub struct MsgId(u32);

pub struct Message {
    /// Message ID.
    pub(crate) id: MsgId,

    /// `From:` contact ID.
    pub(crate) from_id: ContactId,

    /// ID of the first contact in the `To:` header.
    pub(crate) to_id: ContactId,

    /// ID of the chat message belongs to.
    pub(crate) chat_id: ChatId,

    /// Type of the message.
    pub(crate) viewtype: Viewtype,

    /// State of the message.
    pub(crate) state: MessageState,
    pub(crate) download_state: DownloadState,

    /// Whether the message is hidden.
    pub(crate) hidden: bool,
    pub(crate) timestamp_sort: i64,
    pub(crate) timestamp_sent: i64,
    pub(crate) timestamp_rcvd: i64,
    pub(crate) ephemeral_timer: EphemeralTimer,
    pub(crate) ephemeral_timestamp: i64,
    pub(crate) text: String,

    /// Message subject.
    ///
    /// If empty, a default subject will be generated when sending.
    pub(crate) subject: String,

    /// `Message-ID` header value.
    pub(crate) rfc724_mid: String,

    /// `In-Reply-To` header value.
    pub(crate) in_reply_to: Option<String>,
    pub(crate) is_dc_message: MessengerMessage,
    pub(crate) mime_modified: bool,
    pub(crate) chat_blocked: Blocked,
    pub(crate) location_id: u32,
    pub(crate) error: Option<String>,
    pub(crate) param: Params,
}

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use anyhow::{ensure, format_err, Context as _, Result};
use deltachat_contact_tools::{parse_vcard, VcardContact};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use tokio::{fs, io};
use crate::blob::BlobObject;
use crate::chat::{Chat, ChatId, ChatIdBlocked};
use crate::chatlist_events;
use crate::config::Config;
use crate::constants::{
    Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
};
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::debug_logging::set_debug_logging_xdc;
use crate::download::DownloadState;
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::imap::markseen_on_imap_table;
use crate::location::delete_poi_location;
use crate::mimeparser::{parse_message_id, SystemMessage};
use crate::param::{Param, Params};
use crate::pgp::split_armored_data;
use crate::reaction::get_msg_reactions;
use crate::sql;
use crate::summary::Summary;
use crate::tools::{
    buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file, time,
    timestamp_to_str, truncate,
};
use MessageState::*;
use MessageState::*;
use num_traits::FromPrimitive;
use super::*;
use crate::chat::{
        self, add_contact_to_chat, marknoticed_chat, send_text_msg, ChatItem, ProtectionStatus,
    };
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::reaction::send_reaction;
use crate::receive_imf::receive_imf;
use crate::test_utils as test;
use crate::test_utils::{TestContext, TestContextManager};


*     0 and dc_chatlist_get_cnt()-1.
 */
uint32_t dc_chatlist_get_chat_id(const dc_chatlist_t* chatlist, size_t index)
{
	if (chatlist==NULL || chatlist->magic!=DC_CHATLIST_MAGIC || chatlist->chatNlastmsg_ids==NULL || index>=chatlist->cnt) {
		return 0;
	}

	return dc_array_get_id(chatlist->chatNlastmsg_ids, index*DC_CHATLIST_IDS_PER_RESULT);
}

pub fn get_chat_id(&self, index: usize) -> Result<ChatId> {
        let (chat_id, _msg_id) = self
            .ids
            .get(index)
            .context("chatlist index is out of range")?;
        Ok(*chat_id)
    }

pub struct Chatlist {
    /// Stores pairs of `chat_id, message_id`
    ids: Vec<(ChatId, Option<MsgId>)>,
}

use anyhow::{ensure, Context as _, Result};
use once_cell::sync::Lazy;
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
use crate::constants::{
    Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_GCL_ADD_ALLDONE_HINT,
    DC_GCL_ARCHIVED_ONLY, DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS,
};
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::message::{Message, MessageState, MsgId};
use crate::param::{Param, Params};
use crate::stock_str;
use crate::summary::Summary;
use crate::tools::IsNoneOrEmpty;
use super::*;
use crate::chat::{
        add_contact_to_chat, create_group_chat, get_chat_contacts, remove_contact_from_chat,
        send_text_msg, ProtectionStatus,
    };
use crate::message::Viewtype;
use crate::receive_imf::receive_imf;
use crate::stock_str::StockMessage;
use crate::test_utils::TestContext;


int dc_msg_has_location(const dc_msg_t* msg)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC) {
		return 0;
	}

	return (msg->location_id!=0);
}

pub fn has_location(&self) -> bool {
        self.location_id != 0
    }

pub struct Message {
    /// Message ID.
    pub(crate) id: MsgId,

    /// `From:` contact ID.
    pub(crate) from_id: ContactId,

    /// ID of the first contact in the `To:` header.
    pub(crate) to_id: ContactId,

    /// ID of the chat message belongs to.
    pub(crate) chat_id: ChatId,

    /// Type of the message.
    pub(crate) viewtype: Viewtype,

    /// State of the message.
    pub(crate) state: MessageState,
    pub(crate) download_state: DownloadState,

    /// Whether the message is hidden.
    pub(crate) hidden: bool,
    pub(crate) timestamp_sort: i64,
    pub(crate) timestamp_sent: i64,
    pub(crate) timestamp_rcvd: i64,
    pub(crate) ephemeral_timer: EphemeralTimer,
    pub(crate) ephemeral_timestamp: i64,
    pub(crate) text: String,

    /// Message subject.
    ///
    /// If empty, a default subject will be generated when sending.
    pub(crate) subject: String,

    /// `Message-ID` header value.
    pub(crate) rfc724_mid: String,

    /// `In-Reply-To` header value.
    pub(crate) in_reply_to: Option<String>,
    pub(crate) is_dc_message: MessengerMessage,
    pub(crate) mime_modified: bool,
    pub(crate) chat_blocked: Blocked,
    pub(crate) location_id: u32,
    pub(crate) error: Option<String>,
    pub(crate) param: Params,
}


static int is_file_in_use(dc_hash_t* files_in_use, const char* namespc, const char* name)
{
	char* name_to_check = dc_strdup(name);

	if (namespc) {
		int name_len = strlen(name);
		int namespc_len = strlen(namespc);
		if (name_len<=namespc_len
		 || strcmp(&name[name_len-namespc_len], namespc)!=0) {
			return 0;
		}
		name_to_check[name_len-namespc_len] = 0;
	}

	int ret = (dc_hash_find_str(files_in_use, name_to_check)!=NULL);

	free(name_to_check);
	return ret;
}

fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
    let name_to_check = if let Some(namespc) = namespc_opt {
        let name_len = name.len();
        let namespc_len = namespc.len();
        if name_len <= namespc_len || !name.ends_with(namespc) {
            return false;
        }
        &name[..name_len - namespc_len]
    } else {
        name
    };
    files_in_use.contains(name_to_check)
}


* After the creation with dc_create_group_chat() the chat is usually unpromoted
 * until the first call to dc_send_text_msg() or another sending function.
 *
 * With unpromoted chats, members can be added
 * and settings can be modified without the need of special status messages being sent.
 *
 * While the core takes care of the unpromoted state on its own,
 * checking the state from the UI side may be useful to decide whether a hint as
 * "Send the first message to allow others to reply within the group"
 * should be shown to the user or not.
 *
 * @memberof dc_chat_t
 * @param chat The chat object.
 * @return 1=chat is still unpromoted, no message was ever send to the chat,
 *     0=chat is not unpromoted, messages were send and/or received
 *     or the chat is not group chat.
 */
int dc_chat_is_unpromoted(const dc_chat_t* chat)
{
	if (chat==NULL || chat->magic!=DC_CHAT_MAGIC) {
		return 0;
	}
	return dc_param_get_int(chat->param, DC_PARAM_UNPROMOTED, 0);
}

pub fn is_unpromoted(&self) -> bool {
        self.param.get_bool(Param::Unpromoted).unwrap_or_default()
    }

pub fn get_bool(&self, key: Param) -> Option<bool> {
        self.get_int(key).map(|v| v != 0)
    }

pub struct Chat {
    /// Database ID.
    pub id: ChatId,

    /// Chat type, e.g. 1:1 chat, group chat, mailing list.
    pub typ: Chattype,

    /// Chat name.
    pub name: String,

    /// Whether the chat is archived or pinned.
    pub visibility: ChatVisibility,

    /// Group ID. For [`Chattype::Mailinglist`] -- mailing list address. Empty for 1:1 chats and
    /// ad-hoc groups.
    pub grpid: String,

    /// Whether the chat is blocked, unblocked or a contact request.
    pub blocked: Blocked,

    /// Additional chat parameters stored in the database.
    pub param: Params,

    /// If location streaming is enabled in the chat.
    is_sending_locations: bool,

    /// Duration of the chat being muted.
    pub mute_duration: MuteDuration,

    /// If the chat is protected (verified).
    pub(crate) protected: ProtectionStatus,
}

pub struct ChatId(u32);

pub enum Param {
    /// For messages
    File = b'f',

    /// For messages: original filename (as shown in chat)
    Filename = b'v',

    /// For messages: This name should be shown instead of contact.get_display_name()
    /// (used if this is a mailinglist
    /// or explicitly set using set_override_sender_name(), eg. by bots)
    OverrideSenderDisplayname = b'O',

    /// For Messages
    Width = b'w',

    /// For Messages
    Height = b'h',

    /// For Messages
    Duration = b'd',

    /// For Messages
    MimeType = b'm',

    /// For Messages: HTML to be written to the database and to be send.
    /// `SendHtml` param is not used for received messages.
    /// Use `MsgId::get_html()` to get HTML of received messages.
    SendHtml = b'T',

    /// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
    GuaranteeE2ee = b'c',

    /// For Messages: quoted message is encrypted.
    ///
    /// If this message is sent unencrypted, quote text should be replaced.
    ProtectQuote = b'0',

    /// For Messages: decrypted with validation errors or without mutual set, if neither
    /// 'c' nor 'e' are preset, the messages is only transport encrypted.
    ErroneousE2ee = b'e',

    /// For Messages: force unencrypted message, a value from `ForcePlaintext` enum.
    ForcePlaintext = b'u',

    /// For Messages: do not include Autocrypt header.
    SkipAutocrypt = b'o',

    /// For Messages
    WantsMdn = b'r',

    /// For Messages: the message is a reaction.
    Reaction = b'x',

    /// For Chats: the timestamp of the last reaction.
    LastReactionTimestamp = b'y',

    /// For Chats: Message ID of the last reaction.
    LastReactionMsgId = b'Y',

    /// For Chats: Contact ID of the last reaction.
    LastReactionContactId = b'1',

    /// For Messages: a message with "Auto-Submitted: auto-generated" header ("bot").
    Bot = b'b',

    /// For Messages: unset or 0=not forwarded,
    /// 1=forwarded from unknown msg_id, >9 forwarded from msg_id
    Forwarded = b'a',

    /// For Messages: quoted text.
    Quote = b'q',

    /// For Messages
    Cmd = b'S',

    /// For Messages
    Arg = b'E',

    /// For Messages
    Arg2 = b'F',

    /// `Secure-Join-Fingerprint` header for `{vc,vg}-request-with-auth` messages.
    Arg3 = b'G',

    /// Deprecated `Secure-Join-Group` header for messages.
    Arg4 = b'H',

    /// For Messages
    AttachGroupImage = b'A',

    /// For Messages
    WebrtcRoom = b'V',

    /// For Messages: space-separated list of messaged IDs of forwarded copies.
    ///
    /// This is used when a [crate::message::Message] is in the
    /// [crate::message::MessageState::OutPending] state but is already forwarded.
    /// In this case the forwarded messages are written to the
    /// database and their message IDs are added to this parameter of
    /// the original message, which is also saved in the database.
    /// When the original message is then finally sent this parameter
    /// is used to also send all the forwarded messages.
    PrepForwards = b'P',

    /// For Messages
    SetLatitude = b'l',

    /// For Messages
    SetLongitude = b'n',

    /// For Groups
    ///
    /// An unpromoted group has not had any messages sent to it and thus only exists on the
    /// creator's device.  Any changes made to an unpromoted group do not need to send
    /// system messages to the group members to update them of the changes.  Once a message
    /// has been sent to a group it is promoted and group changes require sending system
    /// messages to all members.
    Unpromoted = b'U',

    /// For Groups and Contacts
    ProfileImage = b'i',

    /// For Chats
    /// Signals whether the chat is the `saved messages` chat
    Selftalk = b'K',

    /// For Chats: On sending a new message we set the subject to `Re: <last subject>`.
    /// Usually we just use the subject of the parent message, but if the parent message
    /// is deleted, we use the LastSubject of the chat.
    LastSubject = b't',

    /// For Chats
    Devicetalk = b'D',

    /// For Chats: If this is a mailing list chat, contains the List-Post address.
    /// None if there simply is no `List-Post` header in the mailing list.
    /// Some("") if the mailing list is using multiple different List-Post headers.
    ///
    /// The List-Post address is the email address where the user can write to in order to
    /// post something to the mailing list.
    ListPost = b'p',

    /// For Contacts: If this is the List-Post address of a mailing list, contains
    /// the List-Id of the mailing list (which is also used as the group id of the chat).
    ListId = b's',

    /// For Contacts: timestamp of status (aka signature or footer) update.
    StatusTimestamp = b'j',

    /// For Contacts and Chats: timestamp of avatar update.
    AvatarTimestamp = b'J',

    /// For Chats: timestamp of status/signature/footer update.
    EphemeralSettingsTimestamp = b'B',

    /// For Chats: timestamp of subject update.
    SubjectTimestamp = b'C',

    /// For Chats: timestamp of group name update.
    GroupNameTimestamp = b'g',

    /// For Chats: timestamp of member list update.
    MemberListTimestamp = b'k',

    /// For Webxdc Message Instances: Current document name
    WebxdcDocument = b'R',

    /// For Webxdc Message Instances: timestamp of document name update.
    WebxdcDocumentTimestamp = b'W',

    /// For Webxdc Message Instances: Current summary
    WebxdcSummary = b'N',

    /// For Webxdc Message Instances: timestamp of summary update.
    WebxdcSummaryTimestamp = b'Q',

    /// For Webxdc Message Instances: Webxdc is an integration, see init_webxdc_integration()
    WebxdcIntegration = b'3',

    /// For Webxdc Message Instances: Chat to integrate the Webxdc for.
    WebxdcIntegrateFor = b'2',

    /// For messages: Whether [crate::message::Viewtype::Sticker] should be forced.
    ForceSticker = b'X',
    // 'L' was defined as ProtectionSettingsTimestamp for Chats, however, never used in production.
}


char* dc_imex_has_backup(dc_context_t* context, const char* dir_name)
{
	char*          ret = NULL;
	DIR*           dir_handle = NULL;
	struct dirent* dir_entry = NULL;
	int            prefix_len = strlen(DC_BAK_PREFIX);
	int            suffix_len = strlen(DC_BAK_SUFFIX);
	char*          curr_pathNfilename = NULL;
	dc_sqlite3_t*  test_sql = NULL;
    char *newest_backup_name = NULL;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		return NULL;
	}

	if ((dir_handle=opendir(dir_name))==NULL) {
		dc_log_info(context, 0, "Backup check: Cannot open directory \"%s\".", dir_name); /* this is not an error - eg. the directory may not exist or the user has not given us access to read data from the storage */
		goto cleanup;
	}

	while ((dir_entry=readdir(dir_handle))!=NULL) {
		const char* name = dir_entry->d_name; /* name without path; may also be `.` or `..` */
		int name_len = strlen(name);
        free(curr_pathNfilename);
        curr_pathNfilename = dc_mprintf("%s/%s", dir_name, name);

		if (name_len > prefix_len && strncmp(name, DC_BAK_PREFIX, prefix_len)==0
		 && name_len > suffix_len && strncmp(&name[name_len-suffix_len-1], "." DC_BAK_SUFFIX, suffix_len)==0)
		 && (newest_backup_name == NULL || strcmp(name, newest_backup_name) > 0)
        {
			free(newest_backup_name);
            newest_backup_name = strdup(name);

            free(ret);
            ret = curr_pathNfilename;
            curr_pathNfilename = NULL;
		}
	}

cleanup:
	if (dir_handle) { closedir(dir_handle); }
	free(curr_pathNfilename);
	return ret;
}

pub async fn has_backup(_context: &Context, dir_name: &Path) -> Result<String> {
    let mut dir_iter = tokio::fs::read_dir(dir_name).await?;
    let mut newest_backup_name = "".to_string();
    let mut newest_backup_path: Option<PathBuf> = None;

    while let Ok(Some(dirent)) = dir_iter.next_entry().await {
        let path = dirent.path();
        let name = dirent.file_name();
        let name: String = name.to_string_lossy().into();
        if name.starts_with("delta-chat")
            && name.ends_with(".tar")
            && (newest_backup_name.is_empty() || name > newest_backup_name)
        {
            // We just use string comparison to determine which backup is newer.
            // This works fine because the filenames have the form `delta-chat-backup-2023-10-18-00-foo@example.com.tar`
            newest_backup_path = Some(path);
            newest_backup_name = name;
        }
    }

    match newest_backup_path {
        Some(path) => Ok(path.to_string_lossy().into_owned()),
        None => bail!("no backup found in {}", dir_name.display()),
    }
}

pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}


int dc_param_exists(dc_param_t* param, int key)
{
	char *p2 = NULL;

	if (param==NULL || key==0) {
		return 0;
	}

	return find_param(param->packed, key, &p2)? 1 : 0;
}

pub fn exists(&self, key: Param) -> bool {
        self.inner.contains_key(&key)
    }

pub struct Params {
    inner: BTreeMap<Param, String>,
}

use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::str;
use anyhow::{bail, Error, Result};
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
use crate::blob::BlobObject;
use crate::context::Context;
use crate::mimeparser::SystemMessage;
use std::path::Path;
use std::str::FromStr;
use tokio::fs;
use super::*;
use crate::test_utils::TestContext;