/*
 * meli - mailbox module.
 *
 * Copyright 2017 Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

/*!
 * https://wiki2.dovecot.org/MailboxFormat/demo
 */

use crate::backends::*;
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result};
use crate::get_path_hash;
use std::collections::{hash_map::HashMap, BTreeMap};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};

mod demo_corpus;

#[derive(Debug, Default)]
struct DemoMailbox {
    hash: MailboxHash,
    name: String,
    path: PathBuf,
    envs: Vec<EnvelopeHash>,
    children: Vec<MailboxHash>,
    parent: Option<MailboxHash>,
    usage: Arc<RwLock<SpecialUsageMailbox>>,
    is_subscribed: bool,
    permissions: MailboxPermissions,
    pub total: Arc<Mutex<usize>>,
    pub unseen: Arc<Mutex<usize>>,
}

impl BackendMailbox for DemoMailbox {
    fn hash(&self) -> MailboxHash {
        self.hash
    }

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

    fn path(&self) -> &str {
        self.path.to_str().unwrap()
    }

    fn change_name(&mut self, s: &str) {
        self.name = s.to_string();
    }

    fn clone(&self) -> Mailbox {
        Box::new(DemoMailbox {
            hash: self.hash,
            name: self.name.clone(),
            path: self.path.clone(),
            envs: self.envs.clone(),
            children: self.children.clone(),
            usage: self.usage.clone(),
            is_subscribed: self.is_subscribed,
            parent: self.parent,
            permissions: self.permissions,
            unseen: self.unseen.clone(),
            total: self.total.clone(),
        })
    }

    fn children(&self) -> &[MailboxHash] {
        &self.children
    }

    fn parent(&self) -> Option<MailboxHash> {
        self.parent
    }

    fn special_usage(&self) -> SpecialUsageMailbox {
        *self.usage.read().unwrap()
    }

    fn permissions(&self) -> MailboxPermissions {
        self.permissions
    }
    fn is_subscribed(&self) -> bool {
        self.is_subscribed
    }
    fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
        self.is_subscribed = new_val;
        Ok(())
    }
    fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
        *self.usage.write()? = new_val;
        Ok(())
    }

    fn count(&self) -> Result<(usize, usize)> {
        Ok((*self.unseen.lock()?, *self.total.lock()?))
    }
}

/// `BackendOp` implementor for Demo
#[derive(Debug, Default)]
pub struct DemoOp {
    hash: EnvelopeHash,
    index: Arc<HashMap<EnvelopeHash, &'static str>>,
    flags: Arc<RwLock<HashMap<EnvelopeHash, (Vec<u64>, Flag)>>>,
    tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
}

impl DemoOp {
    pub fn new(
        hash: EnvelopeHash,
        flags: Arc<RwLock<HashMap<EnvelopeHash, (Vec<u64>, Flag)>>>,
        index: Arc<HashMap<EnvelopeHash, &'static str>>,
        tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
    ) -> Self {
        DemoOp {
            hash,
            flags,
            index,
            tag_index,
        }
    }
}

impl BackendOp for DemoOp {
    fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
        let ret = self.index[&self.hash].as_bytes().to_vec();
        Ok(Box::pin(async { Ok(ret) }))
    }

    fn fetch_flags(&self) -> ResultFuture<Flag> {
        let ret = self.flags.read().unwrap()[&self.hash].1;
        Ok(Box::pin(async move { Ok(ret) }))
    }
}

/// Demo backend
#[derive(Debug, Default)]
pub struct DemoType {
    account_name: String,
    root_mailbox: String,
    mailboxes: Arc<Mutex<HashMap<MailboxHash, DemoMailbox>>>,
    envelopes: HashMap<EnvelopeHash, Envelope>,
    index: Arc<HashMap<EnvelopeHash, &'static str>>,
    flags: Arc<RwLock<HashMap<EnvelopeHash, (Vec<u64>, Flag)>>>,
    tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
}

impl MailBackend for DemoType {
    fn capabilities(&self) -> MailBackendCapabilities {
        MailBackendCapabilities {
            is_async: false,
            is_remote: false,
            extensions: None,
            supports_search: false,
            supports_tags: true,
            supports_submission: false,
        }
    }

    fn is_online(&self) -> ResultFuture<()> {
        Ok(Box::pin(async { Ok(()) }))
    }

    fn fetch(
        &mut self,
        mailbox_hash: MailboxHash,
    ) -> Result<Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>> {
        let mailboxes = self.mailboxes.clone();
        let mut mailbox_lock = mailboxes.lock().unwrap();
        let mailbox_entry = mailbox_lock.entry(mailbox_hash).or_default();
        let mut payload = Vec::with_capacity(mailbox_entry.envs.len());
        for env_hash in mailbox_entry.envs.iter() {
            payload.push(self.envelopes[env_hash].clone());
        }

        Ok(Box::pin(async_stream::try_stream! {
            yield payload;
        }))
    }

    fn watch(&self) -> ResultFuture<()> {
        Err(MeliError::new("Unimplemented."))
    }

    fn refresh(&mut self, _mailbox_hash: MailboxHash) -> ResultFuture<()> {
        Err(MeliError::new("Unimplemented."))
    }

    fn mailboxes(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
        debug!("entering mailboxes");
        let ret = Ok(self
            .mailboxes
            .lock()
            .unwrap()
            .iter()
            .map(|(h, f)| (*h, f.clone() as Mailbox))
            .collect());
        debug!("returning mailboxes");
        Ok(Box::pin(async { ret }))
    }

    fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
        Ok(Box::new(DemoOp::new(
            hash,
            self.flags.clone(),
            self.index.clone(),
            self.tag_index.clone(),
        )))
    }

    fn save(
        &self,
        _bytes: Vec<u8>,
        _mailbox_hash: MailboxHash,
        _flags: Option<Flag>,
    ) -> ResultFuture<()> {
        Err(MeliError::new("Unimplemented."))
    }

    fn copy_messages(
        &mut self,
        _env_hashes: EnvelopeHashBatch,
        _source_mailbox_hash: MailboxHash,
        _destination_mailbox_hash: MailboxHash,
        _move_: bool,
    ) -> ResultFuture<()> {
        Err(MeliError::new("Unimplemented."))
    }

    fn set_flags(
        &mut self,
        env_hashes: EnvelopeHashBatch,
        _mailbox_hash: MailboxHash,
        flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
    ) -> ResultFuture<()> {
        for hash in env_hashes.iter() {
            for set in flags.iter() {
                match set {
                    (Ok(flag), value) => {
                        let mut flags = self.flags.read().unwrap()[&hash].1;
                        flags.set(*flag, *value);
                        self.flags.write().unwrap().get_mut(&hash).unwrap().1 = flags;
                    }
                    (Err(tag), value) => {
                        let hash = tag_hash!(tag);
                        if *value {
                            self.tag_index.write().unwrap().insert(hash, tag.into());
                        }
                    }
                }
            }
        }
        Ok(Box::pin(async { Ok(()) }))
    }

    fn delete_messages(
        &mut self,
        _env_hashes: EnvelopeHashBatch,
        _mailbox_hash: MailboxHash,
    ) -> ResultFuture<()> {
        Ok(Box::pin(async { Ok(()) }))
    }

    fn as_any(&self) -> &dyn ::core::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
        self
    }
}

impl DemoType {
    pub fn new(
        s: &AccountSettings,
        _is_subscribed: Box<dyn Fn(&str) -> bool>,
        _event_consumer: BackendEventConsumer,
    ) -> Result<Box<dyn MailBackend>> {
        let mut ret = DemoType {
            account_name: s.name().to_string(),
            root_mailbox: s.root_mailbox.to_string(),
            ..Default::default()
        };
        {
            let mut mbox_lck = ret.mailboxes.lock().unwrap();
            let mut flags_lck = ret.flags.write().unwrap();
            let index_lck = Arc::get_mut(&mut ret.index).unwrap();
            //let tag_lck = ret.tag_index.write().unwrap();
            for (path, name, envelopes) in demo_corpus::DEMO_MAILBOXES {
                let hash = get_path_hash!(name);
                let mut mbox = DemoMailbox {
                    hash,
                    path: path.into(),
                    name: name.to_string(),
                    envs: vec![],
                    children: Vec::new(),
                    parent: None,
                    usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
                    is_subscribed: true,
                    permissions: MailboxPermissions {
                        create_messages: false,
                        remove_messages: false,
                        set_flags: false,
                        create_child: false,
                        rename_messages: false,
                        delete_messages: false,
                        delete_mailbox: false,
                        change_permissions: false,
                    },
                    unseen: Arc::new(Mutex::new(0)),
                    total: Arc::new(Mutex::new(0)),
                };
                for bytes in *envelopes {
                    let env = Envelope::from_bytes(bytes.as_bytes(), None).unwrap();
                    index_lck.insert(env.hash(), bytes);
                    flags_lck.insert(env.hash(), (vec![], env.flags()));
                    mbox.envs.push(env.hash());
                    ret.envelopes.insert(env.hash(), env);
                }
                mbox_lck.insert(hash, mbox);
            }
        }
        Ok(Box::new(ret))
    }

    pub fn validate_config(_s: &AccountSettings) -> Result<()> {
        Ok(())
    }
}

static _DEMO_MAILBOXES: &'static [(&'static str, &'static str, &'static [&'static str])] = &[
    (
        "INBOX",
        "INBOX",
        &[include_str!(
            "../../../demo/ermis-f/cur/6813190.1075861340968.JavaMail.evans@thyme:2,S"
        )],
    ),
    ("INBOX/All", "All", &[]),
    ("INBOX/Discussion Threads", "Discussion Threads", &[]),
    ("INBOX/Sent", "Sent", &[]),
    ("INBOX/Notes", "Notes", &[]),
    ("INBOX/Trash", "Trash", &[]),
];
