use crate::database::Database;
use crate::feed_api::portal::{Portal, PortalErrorKind, PortalResult};
use crate::models::{Article, ArticleFilter, ArticleID, Category, CategoryID, Feed, FeedID, FeedMapping, Headline, Marked, Read, Tag};
use failure::ResultExt;
use std::sync::Arc;

pub struct DefaultPortal {
    db: Arc<Database>,
}

unsafe impl Send for DefaultPortal {}
unsafe impl Sync for DefaultPortal {}

impl DefaultPortal {
    pub fn new(db: Arc<Database>) -> DefaultPortal {
        DefaultPortal { db }
    }
}

impl Portal for DefaultPortal {
    fn get_headlines(&self, ids: &[ArticleID]) -> PortalResult<Vec<Headline>> {
        let articles = self
            .db
            .read_articles(ArticleFilter {
                limit: None,
                offset: Some(ids.len() as i64),
                order: None,
                unread: None,
                marked: None,
                feed: None,
                feed_blacklist: None,
                category: None,
                category_blacklist: None,
                tag: None,
                ids: Some(ids),
                newer_than: None,
                older_than: None,
                search_term: None,
            })
            .context(PortalErrorKind::DB)?;
        let headlines = articles.iter().map(|a| Headline::from_article(a)).collect();
        Ok(headlines)
    }

    fn get_articles(&self, ids: &[ArticleID]) -> PortalResult<Vec<Article>> {
        let articles = self
            .db
            .read_articles(ArticleFilter {
                limit: Some(ids.len() as i64),
                offset: None,
                order: None,
                unread: None,
                marked: None,
                feed: None,
                feed_blacklist: None,
                category: None,
                category_blacklist: None,
                tag: None,
                ids: Some(ids),
                newer_than: None,
                older_than: None,
                search_term: None,
            })
            .context(PortalErrorKind::DB)?;
        Ok(articles)
    }

    fn get_article_exists(&self, id: &ArticleID) -> PortalResult<bool> {
        let exists = self.db.article_exists(id).context(PortalErrorKind::DB)?;
        Ok(exists)
    }

    fn get_article_ids_unread_feed(&self, feed_id: &FeedID) -> PortalResult<Vec<ArticleID>> {
        let mut res_articles: Vec<ArticleID> = Vec::new();
        let mut offset: Option<i64> = None;
        let limit: i64 = 100;
        loop {
            let articles = self
                .db
                .read_articles(ArticleFilter {
                    limit: Some(limit),
                    offset,
                    order: None,
                    unread: Some(Read::Unread),
                    marked: None,
                    feed: Some(feed_id.clone()),
                    feed_blacklist: None,
                    category: None,
                    category_blacklist: None,
                    tag: None,
                    ids: None,
                    newer_than: None,
                    older_than: None,
                    search_term: None,
                })
                .context(PortalErrorKind::DB)?;

            offset = match offset {
                Some(offset) => Some(offset + limit),
                None => Some(limit),
            };

            if articles.is_empty() {
                break;
            } else {
                res_articles.append(&mut articles.iter().map(|article| article.article_id.clone()).collect());
            }
        }
        Ok(res_articles)
    }

    fn get_article_ids_unread_category(&self, category_id: &CategoryID) -> PortalResult<Vec<ArticleID>> {
        let mut res_articles: Vec<ArticleID> = Vec::new();
        let mut offset: Option<i64> = None;
        let limit: i64 = 100;
        loop {
            let articles = self
                .db
                .read_articles(ArticleFilter {
                    limit: Some(limit),
                    offset,
                    order: None,
                    unread: Some(Read::Unread),
                    marked: None,
                    feed: None,
                    feed_blacklist: None,
                    category: Some(category_id.clone()),
                    category_blacklist: None,
                    tag: None,
                    ids: None,
                    newer_than: None,
                    older_than: None,
                    search_term: None,
                })
                .context(PortalErrorKind::DB)?;

            offset = match offset {
                Some(offset) => Some(offset + limit),
                None => Some(limit),
            };

            if articles.is_empty() {
                break;
            } else {
                res_articles.append(&mut articles.iter().map(|article| article.article_id.clone()).collect());
            }
        }

        Ok(res_articles)
    }

    fn get_article_ids_unread_all(&self) -> PortalResult<Vec<ArticleID>> {
        let mut res_articles: Vec<ArticleID> = Vec::new();
        let mut offset: Option<i64> = None;
        let limit: i64 = 100;
        loop {
            let articles = self
                .db
                .read_articles(ArticleFilter {
                    limit: Some(limit),
                    offset,
                    order: None,
                    unread: Some(Read::Unread),
                    marked: None,
                    feed: None,
                    feed_blacklist: None,
                    category: None,
                    category_blacklist: None,
                    tag: None,
                    ids: None,
                    newer_than: None,
                    older_than: None,
                    search_term: None,
                })
                .context(PortalErrorKind::DB)?;

            offset = match offset {
                Some(offset) => Some(offset + limit),
                None => Some(limit),
            };

            if articles.is_empty() {
                break;
            } else {
                res_articles.append(&mut articles.iter().map(|article| article.article_id.clone()).collect());
            }
        }

        Ok(res_articles)
    }

    fn get_article_ids_marked_all(&self) -> PortalResult<Vec<ArticleID>> {
        let mut res_articles: Vec<ArticleID> = Vec::new();
        let mut offset: Option<i64> = None;
        let limit: i64 = 100;
        loop {
            let articles = self
                .db
                .read_articles(ArticleFilter {
                    limit: Some(limit),
                    offset,
                    order: None,
                    unread: None,
                    marked: Some(Marked::Marked),
                    feed: None,
                    feed_blacklist: None,
                    category: None,
                    category_blacklist: None,
                    tag: None,
                    ids: None,
                    newer_than: None,
                    older_than: None,
                    search_term: None,
                })
                .context(PortalErrorKind::DB)?;

            offset = match offset {
                Some(offset) => Some(offset + limit),
                None => Some(limit),
            };

            if articles.is_empty() {
                break;
            } else {
                res_articles.append(&mut articles.iter().map(|article| article.article_id.clone()).collect());
            }
        }

        Ok(res_articles)
    }

    fn get_feeds(&self) -> PortalResult<Vec<Feed>> {
        let feeds = self.db.read_feeds().context(PortalErrorKind::DB)?;
        Ok(feeds)
    }

    fn get_categories(&self) -> PortalResult<Vec<Category>> {
        let categories = self.db.read_categories().context(PortalErrorKind::DB)?;
        Ok(categories)
    }

    fn get_mappings(&self) -> PortalResult<Vec<FeedMapping>> {
        let mappings = self.db.read_mappings(None, None).context(PortalErrorKind::DB)?;
        Ok(mappings)
    }

    fn get_tags(&self) -> PortalResult<Vec<Tag>> {
        let tags = self.db.read_tags().context(PortalErrorKind::DB)?;
        Ok(tags)
    }
}
