* using dc_set_config(context, "selfavatar", image).
 *
 * @memberof dc_contact_t
 * @param contact The contact object.
 * @return Path and file if the profile image, if any.
 *     NULL otherwise.
 *     Must be free()'d after usage.
 */
char* dc_contact_get_profile_image(const dc_contact_t* contact)
{
	char* selfavatar = NULL;
	char* image_abs = NULL;

	if (contact==NULL || contact->magic!=DC_CONTACT_MAGIC) {
		goto cleanup;
	}

	if (contact->id==DC_CONTACT_ID_SELF) {
		selfavatar = dc_get_config(contact->context, "selfavatar");
		if (selfavatar && selfavatar[0]) {
			image_abs = dc_strdup(selfavatar);
		}
	}
	else {
		image_rel = dc_param_get(chat->param, DC_PARAM_PROFILE_IMAGE, NULL);
		if (image_rel && image_rel[0]) {
			image_abs = dc_get_abs_path(chat->context, image_rel);
		}
	}

cleanup:
	free(selfavatar);
	return image_abs;
}

pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
        if self.id == ContactId::SELF {
            if let Some(p) = context.get_config(Config::Selfavatar).await? {
                return Ok(Some(PathBuf::from(p)));
            }
        } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
            if !image_rel.is_empty() {
                return Ok(Some(get_abs_path(context, Path::new(image_rel))));
            }
        }
        Ok(None)
    }

pub fn get(&self, key: Param) -> Option<&str> {
        self.inner.get(&key).map(|s| s.as_str())
    }

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 fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

pub(crate) fn get_abs_path(context: &Context, path: &Path) -> PathBuf {
    if let Ok(p) = path.strip_prefix("$BLOBDIR") {
        context.get_blobdir().join(p)
    } else {
        path.into()
    }
}

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

pub struct ContactId(u32);

impl ContactId {
    /// Undefined contact. Used as a placeholder for trashed messages.
    pub const UNDEFINED: ContactId = ContactId::new(0);

    /// 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 info messages.
    pub const INFO: ContactId = ContactId::new(2);

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

    /// Address to go with [`ContactId::DEVICE`].
    ///
    /// This is used by APIs which need to return an email address for this contact.
    pub const DEVICE_ADDR: &'static str = "device@localhost";
}

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 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.
}

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_msg_is_increation(const dc_msg_t* msg)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC || msg->context==NULL) {
		return 0;
	}

	return DC_MSG_NEEDS_ATTACHMENT(msg->type) && msg->state==DC_STATE_OUT_PREPARING;
}

pub fn is_increation(&self) -> bool {
        self.viewtype.has_file() && self.state == MessageState::OutPreparing
    }

pub fn has_file(&self) -> bool {
        match self {
            Viewtype::Unknown => false,
            Viewtype::Text => false,
            Viewtype::Image => true,
            Viewtype::Gif => true,
            Viewtype::Sticker => true,
            Viewtype::Audio => true,
            Viewtype::Voice => true,
            Viewtype::Video => true,
            Viewtype::File => true,
            Viewtype::VideochatInvitation => false,
            Viewtype::Webxdc => true,
            Viewtype::Vcard => true,
        }
    }

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,
}

pub enum MessageState {
    /// Undefined message state.
    #[default]
    Undefined = 0,

    /// Incoming *fresh* message. Fresh messages are neither noticed
    /// nor seen and are typically shown in notifications.
    InFresh = 10,

    /// Incoming *noticed* message. E.g. chat opened but message not
    /// yet read - noticed messages are not counted as unread but did
    /// not marked as read nor resulted in MDNs.
    InNoticed = 13,

    /// Incoming message, really *seen* by the user. Marked as read on
    /// IMAP and MDN may be sent.
    InSeen = 16,

    /// For files which need time to be prepared before they can be
    /// sent, the message enters this state before
    /// OutPending.
    OutPreparing = 18,

    /// Message saved as draft.
    OutDraft = 19,

    /// The user has pressed the "send" button but the message is not
    /// yet sent and is pending in some way. Maybe we're offline (no
    /// checkmark).
    OutPending = 20,

    /// *Unrecoverable* error (*recoverable* errors result in pending
    /// messages).
    OutFailed = 24,

    /// Outgoing message successfully delivered to server (one
    /// checkmark). Note, that already delivered messages may get into
    /// the OutFailed state if we get such a hint from the server.
    OutDelivered = 26,

    /// Outgoing message read by the recipient (two checkmarks; this
    /// requires goodwill on the receiver's side)
    OutMdnRcvd = 28,
}

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};


char* dc_get_message_kml(dc_context_t* context, time_t timestamp, double latitude, double longitude)
{
	char*  timestamp_str = NULL;
	char*  latitude_str = NULL;
	char*  longitude_str = NULL;
	char*  ret = NULL;

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

	timestamp_str = get_kml_timestamp(timestamp);
	latitude_str  = dc_ftoa(latitude);
	longitude_str = dc_ftoa(longitude);

	ret = dc_mprintf(
		"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
		"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
			"<Document>\n"
				"<Placemark>"
					"<Timestamp><when>%s</when></Timestamp>"
					"<Point><coordinates>%s,%s</coordinates></Point>"
				"</Placemark>\n"
			"</Document>\n"
		"</kml>",
		timestamp_str,
		longitude_str, // reverse order!
		latitude_str);

cleanup:
	free(latitude_str);
	free(longitude_str);
	free(timestamp_str);
	return ret;
}

pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
    format!(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
         <kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
         <Document>\n\
         <Placemark>\
         <Timestamp><when>{}</when></Timestamp>\
         <Point><coordinates>{},{}</coordinates></Point>\
         </Placemark>\n\
         </Document>\n\
         </kml>",
        get_kml_timestamp(timestamp),
        longitude,
        latitude,
    )
}

use std::time::Duration;
use anyhow::{ensure, Context as _, Result};
use async_channel::Receiver;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use tokio::time::timeout;
use crate::chat::{self, ChatId};
use crate::constants::DC_CHAT_ID_TRASH;
use crate::contact::ContactId;
use crate::context::Context;
use crate::events::EventType;
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::tools::{duration_to_str, time};
use crate::{chatlist_events, stock_str};
use super::*;
use crate::config::Config;
use crate::message::MessageState;
use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::SystemTime;


void dc_send_locations_to_chat(dc_context_t* context, uint32_t chat_id,
                               int seconds)
{
	sqlite3_stmt* stmt = NULL;
	time_t        now = time(NULL);
	dc_msg_t*     msg = NULL;
	char*         stock_str = NULL;
	int           is_sending_locations_before = 0;

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

	is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);

	stmt = dc_sqlite3_prepare(context->sql,
		"UPDATE chats "
		"   SET locations_send_begin=?, "
		"       locations_send_until=? "
		" WHERE id=?");
	sqlite3_bind_int64(stmt, 1, seconds? now : 0);
	sqlite3_bind_int64(stmt, 2, seconds? now+seconds : 0);
	sqlite3_bind_int  (stmt, 3, chat_id);
	sqlite3_step(stmt);

	// add/sent status message.
	// as disable also happens after a timeout, this is not sent explicitly.
	if (seconds && !is_sending_locations_before) {
		msg = dc_msg_new(context, DC_MSG_TEXT);
		msg->text = dc_stock_system_msg(context, DC_STR_MSGLOCATIONENABLED, NULL, NULL, 0);
		dc_param_set_int(msg->param, DC_PARAM_CMD, DC_CMD_LOCATION_STREAMING_ENABLED);
		dc_send_msg(context, chat_id, msg);
	}
	else if(!seconds && is_sending_locations_before) {
		stock_str = dc_stock_system_msg(context, DC_STR_MSGLOCATIONDISABLED, NULL, NULL, 0);
		dc_add_device_msg(context, chat_id, stock_str);
	}

	// update eg. the "location-sending"-icon
	context->cb(context, DC_EVENT_CHAT_MODIFIED, chat_id, 0);

	if (seconds) {
		schedule_MAYBE_SEND_LOCATIONS(context, 0);
		dc_job_add(context, DC_JOB_MAYBE_SEND_LOC_ENDED, chat_id, NULL, seconds+1);
	}

cleanup:
	free(stock_str);
	dc_msg_unref(msg);
	sqlite3_finalize(stmt);
}

pub async fn send_locations_to_chat(
    context: &Context,
    chat_id: ChatId,
    seconds: i64,
) -> Result<()> {
    ensure!(seconds >= 0);
    ensure!(!chat_id.is_special());
    let now = time();
    let is_sending_locations_before = is_sending_locations_to_chat(context, Some(chat_id)).await?;
    context
        .sql
        .execute(
            "UPDATE chats    \
         SET locations_send_begin=?,        \
         locations_send_until=?  \
         WHERE id=?",
            (
                if 0 != seconds { now } else { 0 },
                if 0 != seconds { now + seconds } else { 0 },
                chat_id,
            ),
        )
        .await?;
    if 0 != seconds && !is_sending_locations_before {
        let mut msg = Message::new(Viewtype::Text);
        msg.text = stock_str::msg_location_enabled(context).await;
        msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
        chat::send_msg(context, chat_id, &mut msg)
            .await
            .unwrap_or_default();
    } else if 0 == seconds && is_sending_locations_before {
        let stock_str = stock_str::msg_location_disabled(context).await;
        chat::add_info_msg(context, chat_id, &stock_str, now).await?;
    }
    context.emit_event(EventType::ChatModified(chat_id));
    chatlist_events::emit_chatlist_item_changed(context, chat_id);
    if 0 != seconds {
        context.scheduler.interrupt_location().await;
    }
    Ok(())
}

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 fn is_special(self) -> bool {
        (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
    }

pub async fn is_sending_locations_to_chat(
    context: &Context,
    chat_id: Option<ChatId>,
) -> Result<bool> {
    let exists = match chat_id {
        Some(chat_id) => {
            context
                .sql
                .exists(
                    "SELECT COUNT(id) FROM chats  WHERE id=?  AND locations_send_until>?;",
                    (chat_id, time()),
                )
                .await?
        }
        None => {
            context
                .sql
                .exists(
                    "SELECT COUNT(id) FROM chats  WHERE locations_send_until>?;",
                    (time(),),
                )
                .await?
        }
    };
    Ok(exists)
}

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 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(crate) fn time() -> i64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64
}

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

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,
}

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

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) fn emit_chatlist_item_changed(context: &Context, chat_id: ChatId) {
    context.emit_event(EventType::ChatlistItemChanged {
        chat_id: Some(chat_id),
    });
}

pub(crate) async fn interrupt_location(&self) {
        let inner = self.inner.read().await;
        if let InnerSchedulerState::Started(ref scheduler) = *inner {
            scheduler.interrupt_location();
        }
    }

pub(crate) async fn add_info_msg(
    context: &Context,
    chat_id: ChatId,
    text: &str,
    timestamp: i64,
) -> Result<MsgId> {
    add_info_msg_with_cmd(
        context,
        chat_id,
        text,
        SystemMessage::Unknown,
        timestamp,
        None,
        None,
        None,
    )
    .await
}

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>,
}


dc_array_t* dc_get_locations(dc_context_t* context,
                             uint32_t chat_id, uint32_t  contact_id,
                             time_t timestamp_from, time_t timestamp_to)
{
	dc_array_t*   ret = dc_array_new_typed(context, DC_ARRAY_LOCATIONS, 500);
	sqlite3_stmt* stmt = NULL;

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

	if (timestamp_to==0) {
		timestamp_to = time(NULL) + 10/*messages may be inserted by another thread just now*/;
	}

	stmt = dc_sqlite3_prepare(context->sql,
			"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, "
			"       m.id, l.from_id, l.chat_id, m.txt "
			" FROM locations l "
			" LEFT JOIN msgs m ON l.id=m.location_id "
			" WHERE (? OR l.chat_id=?) "
			"   AND (? OR l.from_id=?) "
			"   AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) "
			" ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;");
	sqlite3_bind_int(stmt, 1, chat_id==0? 1 : 0);
	sqlite3_bind_int(stmt, 2, chat_id);
	sqlite3_bind_int(stmt, 3, contact_id==0? 1 : 0);
	sqlite3_bind_int(stmt, 4, contact_id);
	sqlite3_bind_int(stmt, 5, timestamp_from);
	sqlite3_bind_int(stmt, 6, timestamp_to);

	while (sqlite3_step(stmt)==SQLITE_ROW) {
        struct _dc_location* loc = calloc(1, sizeof(struct _dc_location));
        if (loc==NULL) {
			goto cleanup;
        }

		loc->location_id = sqlite3_column_double(stmt, 0);
		loc->latitude    = sqlite3_column_double(stmt, 1);
		loc->longitude   = sqlite3_column_double(stmt, 2);
		loc->accuracy    = sqlite3_column_double(stmt, 3);
		loc->timestamp   = sqlite3_column_int64 (stmt, 4);
		loc->independent = sqlite3_column_int   (stmt, 5);
		loc->msg_id      = sqlite3_column_int   (stmt, 6);
		loc->contact_id  = sqlite3_column_int   (stmt, 7);
		loc->chat_id     = sqlite3_column_int   (stmt, 8);
		if (loc->msg_id) {
			const char* txt = (const char*)sqlite3_column_text(stmt, 9);
			if (is_marker(txt)) {
				loc->marker = strdup(txt);
			}
		}

		dc_array_add_ptr(ret, loc);
	}

cleanup:
	sqlite3_finalize(stmt);
	return ret;
}

pub async fn get_range(
    context: &Context,
    chat_id: Option<ChatId>,
    contact_id: Option<u32>,
    timestamp_from: i64,
    mut timestamp_to: i64,
) -> Result<Vec<Location>> {
    if timestamp_to == 0 {
        timestamp_to = time() + 10;
    }

    let (disable_chat_id, chat_id) = match chat_id {
        Some(chat_id) => (0, chat_id),
        None => (1, ChatId::new(0)), // this ChatId is unused
    };
    let (disable_contact_id, contact_id) = match contact_id {
        Some(contact_id) => (0, contact_id),
        None => (1, 0), // this contact_id is unused
    };
    let list = context
        .sql
        .query_map(
            "SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
             COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt \
             FROM locations l  LEFT JOIN msgs m ON l.id=m.location_id  WHERE (? OR l.chat_id=?) \
             AND (? OR l.from_id=?) \
             AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
             ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
            (
                disable_chat_id,
                chat_id,
                disable_contact_id,
                contact_id as i32,
                timestamp_from,
                timestamp_to,
            ),
            |row| {
                let msg_id = row.get(6)?;
                let txt: String = row.get(9)?;
                let marker = if msg_id != 0 && is_marker(&txt) {
                    Some(txt)
                } else {
                    None
                };
                let loc = Location {
                    location_id: row.get(0)?,
                    latitude: row.get(1)?,
                    longitude: row.get(2)?,
                    accuracy: row.get(3)?,
                    timestamp: row.get(4)?,
                    independent: row.get(5)?,
                    msg_id,
                    contact_id: row.get(7)?,
                    chat_id: row.get(8)?,
                    marker,
                };
                Ok(loc)
            },
            |locations| {
                let mut ret = Vec::new();

                for location in locations {
                    ret.push(location?);
                }
                Ok(ret)
            },
        )
        .await?;
    Ok(list)
}

fn is_marker(txt: &str) -> bool {
    let mut chars = txt.chars();
    if let Some(c) = chars.next() {
        !c.is_whitespace() && chars.next().is_none()
    } else {
        false
    }
}

pub(crate) fn time() -> i64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64
}

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

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 const fn new(id: u32) -> ChatId {
        ChatId(id)
    }

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 Location {
    /// Row ID of the location.
    pub location_id: u32,

    /// Location latitude.
    pub latitude: f64,

    /// Location longitude.
    pub longitude: f64,

    /// Nonstandard `accuracy` attribute of the `coordinates` tag.
    pub accuracy: f64,

    /// Location timestamp in seconds.
    pub timestamp: i64,

    /// Contact ID.
    pub contact_id: ContactId,

    /// Message ID.
    pub msg_id: u32,

    /// Chat ID.
    pub chat_id: ChatId,

    /// A marker string, such as an emoji, to be displayed on top of the location.
    pub marker: Option<String>,

    /// Whether location is independent, i.e. not part of the path.
    pub independent: u32,
}


static int is_marker(const char* txt)
{
	if (txt) {
		int len = dc_utf8_strlen(txt);
		if (len==1 && !isspace(txt[0])) {
			return 1;
		}
	}
	return 0;
}

fn is_marker(txt: &str) -> bool {
    let mut chars = txt.chars();
    if let Some(c) = chars.next() {
        !c.is_whitespace() && chars.next().is_none()
    } else {
        false
    }
}


* function not under the control of the core (eg. #DC_EVENT_HTTP_GET). Another
 * reason for dc_stop_ongoing_process() not to wait is that otherwise it
 * would be GUI-blocking and should be started in another thread then; this
 * would make things even more complicated.
 *
 * Typical ongoing processes are started by dc_configure(),
 * dc_initiate_key_transfer() or dc_imex(). As there is always at most only
 * one onging process at the same time, there is no need to define _which_ process to exit.
 *
 * @memberof dc_context_t
 * @param context The context object.
 * @return None.
 */
void dc_stop_ongoing_process(dc_context_t* context)
{
	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		return;
	}

	if (context->ongoing_running && context->shall_stop_ongoing==0)
	{
		dc_log_info(context, 0, "Signaling the ongoing process to stop ASAP.");
		context->shall_stop_ongoing = 1;
	}
	else
	{
		dc_log_info(context, 0, "No ongoing process to stop.");
	}
}

pub async fn stop_ongoing(&self) {
        let mut s = self.running_state.write().await;
        match &*s {
            RunningState::Running { cancel_sender } => {
                if let Err(err) = cancel_sender.send(()).await {
                    warn!(self, "could not cancel ongoing: {:#}", err);
                }
                info!(self, "Signaling the ongoing process to stop ASAP.",);
                *s = RunningState::ShallStop {
                    request: tools::Time::now(),
                };
            }
            RunningState::ShallStop { .. } | RunningState::Stopped => {
                info!(self, "No ongoing process to stop.",);
            }
        }
    }

macro_rules! info {
    ($ctx:expr,  $msg:expr) => {
        info!($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::Info(full));
    }};
}

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 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>,
}

enum RunningState {
    /// Ongoing process is allocated.
    Running { cancel_sender: Sender<()> },

    /// Cancel signal has been sent, waiting for ongoing process to be freed.
    ShallStop { request: tools::Time },

    /// There is no ongoing process, a new one can be allocated.
    Stopped,
}

use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use pgp::SignedPublicKey;
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, Notify, OnceCell, RwLock};
use crate::aheader::EncryptPreference;
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
use crate::chatlist_events;
use crate::config::Config;
use crate::constants::{
    self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
};
use crate::contact::{Contact, ContactId};
use crate::debug_logging::DebugLogging;
use crate::download::DownloadState;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _};
use crate::login_param::LoginParam;
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
use crate::param::{Param, Params};
use crate::peer_channels::Iroh;
use crate::peerstate::Peerstate;
use crate::push::PushSubscriber;
use crate::quota::QuotaInfo;
use crate::scheduler::{convert_folder_meaning, SchedulerState};
use crate::sql::Sql;
use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
use crate::tools::{self, create_id, duration_to_str, time, time_elapsed};
use anyhow::Context as _;
use strum::IntoEnumIterator;
use tempfile::tempdir;
use super::*;
use crate::chat::{get_chat_contacts, get_chat_msgs, send_msg, set_muted, Chat, MuteDuration};
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
use crate::test_utils::{get_chat_msg, TestContext};
use crate::tools::{create_outgoing_rfc724_mid, SystemTime};


void dc_msg_set_location(dc_msg_t* msg, double latitude, double longitude)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC || (latitude==0.0 && longitude==0.0)) {
		return;
	}

	dc_param_set_float(msg->param, DC_PARAM_SET_LATITUDE,  latitude);
	dc_param_set_float(msg->param, DC_PARAM_SET_LONGITUDE, longitude);
}

pub fn set_location(&mut self, latitude: f64, longitude: f64) {
        if latitude == 0.0 && longitude == 0.0 {
            return;
        }

        self.param.set_float(Param::SetLatitude, latitude);
        self.param.set_float(Param::SetLongitude, longitude);
    }

pub fn set_float(&mut self, key: Param, value: f64) -> &mut Self {
        self.set(key, format!("{value}"));
        self
    }

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,
}

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.
}


void dc_block_contact(dc_context_t* context, uint32_t contact_id, int new_blocking)
{
	int           send_event = 0;
	dc_contact_t* contact = dc_contact_new(context);
	sqlite3_stmt* stmt = NULL;

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

		if (dc_contact_load_from_db(contact, context->sql, contact_id)
		 && contact->blocked!=new_blocking)
		{
			stmt = dc_sqlite3_prepare(context->sql,
				"UPDATE contacts SET blocked=? WHERE id=?;");
			sqlite3_bind_int(stmt, 1, new_blocking);
			sqlite3_bind_int(stmt, 2, contact_id);
			if (sqlite3_step(stmt)!=SQLITE_DONE) {
				goto cleanup;
			}
			sqlite3_finalize(stmt);
			stmt = NULL;

			/* also (un)block all chats with _only_ this contact - we do not delete them to allow a non-destructive blocking->unblocking.
			(Maybe, beside normal chats (type=100) we should also block group chats with only this user.
			However, I'm not sure about this point; it may be confusing if the user wants to add other people;
			this would result in recreating the same group...) */
			stmt = dc_sqlite3_prepare(context->sql,
				"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);");
			sqlite3_bind_int(stmt, 1, new_blocking);
			sqlite3_bind_int(stmt, 2, DC_CHAT_TYPE_SINGLE);
			sqlite3_bind_int(stmt, 3, contact_id);
			if (sqlite3_step(stmt)!=SQLITE_DONE) {
				goto cleanup;
			}

			/* mark all messages from the blocked contact as being noticed (this is to remove the deaddrop popup) */
			dc_marknoticed_contact(context, contact_id);

			send_event = 1;
		}

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

cleanup:
	sqlite3_finalize(stmt);
	dc_contact_unref(contact);
}

pub async fn block(context: &Context, id: ContactId) -> Result<()> {
        set_blocked(context, Sync, id, true).await
    }

pub(crate) async fn set_blocked(
    context: &Context,
    sync: sync::Sync,
    contact_id: ContactId,
    new_blocking: bool,
) -> Result<()> {
    ensure!(
        !contact_id.is_special(),
        "Can't block special contact {}",
        contact_id
    );
    let contact = Contact::get_by_id(context, contact_id).await?;

    if contact.blocked != new_blocking {
        context
            .sql
            .execute(
                "UPDATE contacts SET blocked=? WHERE id=?;",
                (i32::from(new_blocking), contact_id),
            )
            .await?;

        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
        // non-destructive blocking->unblocking.
        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
        // this would result in recreating the same group...)
        if context
            .sql
            .execute(
                r#"
UPDATE chats
SET blocked=?
WHERE type=? AND id IN (
  SELECT chat_id FROM chats_contacts WHERE contact_id=?
);
"#,
                (new_blocking, Chattype::Single, contact_id),
            )
            .await
            .is_ok()
        {
            Contact::mark_noticed(context, contact_id).await?;
            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
        }

        // also unblock mailinglist
        // if the contact is a mailinglist address explicitly created to allow unblocking
        if !new_blocking && contact.origin == Origin::MailinglistAddress {
            if let Some((chat_id, _, _)) =
                chat::get_chat_id_by_grpid(context, &contact.addr).await?
            {
                chat_id.unblock_ex(context, Nosync).await?;
            }
        }

        if sync.into() {
            let action = match new_blocking {
                true => chat::SyncAction::Block,
                false => chat::SyncAction::Unblock,
            };
            chat::sync(
                context,
                chat::SyncId::ContactAddr(contact.addr.clone()),
                action,
            )
            .await
            .log_err(context)
            .ok();
        }
    }

    chatlist_events::emit_chatlist_changed(context);
    Ok(())
}

pub struct ContactId(u32);

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

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};


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

	if (dc_param_get_int(msg->param, DC_PARAM_GUARANTEE_E2EE, 0)!=0) {
		return 1;
	}

	return 0;
}

pub fn get_showpadlock(&self) -> bool {
        self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
    }

pub fn get_int(&self, key: Param) -> Option<i32> {
        self.get(key).and_then(|s| s.parse().ok())
    }

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,
}

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.
}


int dc_set_location(dc_context_t* context,
                    double latitude, double longitude, double accuracy)
{
	sqlite3_stmt* stmt_chats = NULL;
	sqlite3_stmt* stmt_insert = NULL;
	int           continue_streaming = 0;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC
	 || (latitude==0.0 && longitude==0.0)) {
		continue_streaming = 1;
		goto cleanup;
	}

	stmt_chats = dc_sqlite3_prepare(context->sql,
		"SELECT id FROM chats WHERE locations_send_until>?;");
	sqlite3_bind_int64(stmt_chats, 1, time(NULL));
	while (sqlite3_step(stmt_chats)==SQLITE_ROW)
	{
		uint32_t chat_id = sqlite3_column_int(stmt_chats, 0);

		stmt_insert = dc_sqlite3_prepare(context->sql,
				"INSERT INTO locations "
				" (latitude, longitude, accuracy, timestamp, chat_id, from_id)"
				" VALUES (?,?,?,?,?,?);");
		sqlite3_bind_double(stmt_insert, 1, latitude);
		sqlite3_bind_double(stmt_insert, 2, longitude);
		sqlite3_bind_double(stmt_insert, 3, accuracy);
		sqlite3_bind_int64 (stmt_insert, 4, time(NULL));
		sqlite3_bind_int   (stmt_insert, 5, chat_id);
		sqlite3_bind_int   (stmt_insert, 6, DC_CONTACT_ID_SELF);
		sqlite3_step(stmt_insert);

		continue_streaming = 1;
	}

	if (continue_streaming) {
		context->cb(context, DC_EVENT_LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0);
		schedule_MAYBE_SEND_LOCATIONS(context, 0);
	}

cleanup:
	sqlite3_finalize(stmt_chats);
	sqlite3_finalize(stmt_insert);
	return continue_streaming;
}

pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> Result<bool> {
    if latitude == 0.0 && longitude == 0.0 {
        return Ok(true);
    }
    let mut continue_streaming = false;
    let now = time();

    let chats = context
        .sql
        .query_map(
            "SELECT id FROM chats WHERE locations_send_until>?;",
            (now,),
            |row| row.get::<_, i32>(0),
            |chats| {
                chats
                    .collect::<std::result::Result<Vec<_>, _>>()
                    .map_err(Into::into)
            },
        )
        .await?;

    let mut stored_location = false;
    for chat_id in chats {
        context.sql.execute(
                "INSERT INTO locations  \
                 (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
                 (
                    latitude,
                    longitude,
                    accuracy,
                    now,
                    chat_id,
                    ContactId::SELF,
                )).await.context("Failed to store location")?;
        stored_location = true;

        info!(context, "Stored location for chat {chat_id}.");
        continue_streaming = true;
    }
    if continue_streaming {
        context.emit_location_changed(Some(ContactId::SELF)).await?;
    };
    if stored_location {
        // Interrupt location loop so it may send a location-only message.
        context.scheduler.interrupt_location().await;
    }

    Ok(continue_streaming)
}

pub(crate) fn time() -> i64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64
}

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 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 emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
        self.emit_event(EventType::LocationChanged(contact_id));

        if let Some(msg_id) = self
            .get_config_parsed::<u32>(Config::WebxdcIntegration)
            .await?
        {
            self.emit_event(EventType::WebxdcStatusUpdate {
                msg_id: MsgId::new(msg_id),
                status_update_serial: Default::default(),
            })
        }

        Ok(())
    }

pub(crate) async fn interrupt_location(&self) {
        let inner = self.inner.read().await;
        if let InnerSchedulerState::Started(ref scheduler) = *inner {
            scheduler.interrupt_location();
        }
    }

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 {
    /// Undefined contact. Used as a placeholder for trashed messages.
    pub const UNDEFINED: ContactId = ContactId::new(0);

    /// 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 info messages.
    pub const INFO: ContactId = ContactId::new(2);

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

    /// Address to go with [`ContactId::DEVICE`].
    ///
    /// This is used by APIs which need to return an email address for this contact.
    pub const DEVICE_ADDR: &'static str = "device@localhost";
}