use crate::{ReferenceError, Testament};

pub mod nt;
pub mod ot;

pub use nt::*;
pub use ot::*;

pub struct Book {
    pub title: &'static str,
    pub testament: Testament,
    /// Alternative names for the book
    pub aliases: &'static [&'static str],
    pub abbreviations: &'static [&'static str],
    pub sort_order: Ordering,
    /// The number of verses in each chapter, indexed by chapter number (0 = placeholder)
    ///
    /// ``` rust
    /// use biblicist::GENESIS;
    /// let genesis = &GENESIS;
    /// assert_eq!(genesis.layout[1], 31); // Genesis chapter 1
    /// assert_eq!(genesis.layout[50], 26); // Genesis chapter 50
    /// ```
    pub layout: &'static [u8],
}

/// Defines placement in the order of this canonical list. If -1, the book is not included in that ordering.
pub struct Ordering {
    english: isize,
    septuagint: isize,
    vulgate: isize,
    lenningrad: isize,
    bhs: isize,
    snaith: isize,
    talmud: isize,
    vaticanus: isize,
    sinaiticus: isize,
    alexandrinus: isize,
}

impl Book {
    pub fn is_old_testament(&self) -> bool {
        matches!(self.testament, Testament::Old)
    }

    pub fn is_new_testament(&self) -> bool {
        matches!(self.testament, Testament::New)
    }

    pub fn chapters(&self) -> u8 {
        self.layout.len() as u8 - 1
    }

    /// Returns the number of chapters in this book for the given versification system.
    ///
    /// Falls back to the default (English) chapter count when no versification-specific
    /// data exists for this book.
    ///
    /// ``` rust
    /// use biblicist::{JOEL, Versification};
    /// let joel = &JOEL;
    /// assert_eq!(joel.chapters_versified(Versification::English), 3);
    /// assert_eq!(joel.chapters_versified(Versification::Masoretic), 4);
    /// ```
    #[inline]
    pub fn chapters_versified(&self, versification: crate::Versification) -> u8 {
        use crate::versification::get_layout_override;
        if let Some(override_layout) = get_layout_override(self.title, versification) {
            override_layout.len() as u8 - 1
        } else {
            self.chapters()
        }
    }

    /// Returns the numbers of verses in the specified chapter, or None if the chapter does not exist.
    /// ``` rust
    /// use biblicist::GENESIS;
    /// let genesis = &GENESIS;
    /// assert_eq!(genesis.verses_in_chapter(1), Some(31));
    /// assert_eq!(genesis.verses_in_chapter(51), None);
    /// ```
    pub fn verses_in_chapter(&self, chapter: u8) -> Option<u8> {
        self.layout.get(chapter as usize).copied()
    }

    /// Returns the number of verses in the specified chapter for the given versification system,
    /// or None if the chapter does not exist in that versification.
    ///
    /// Falls back to the default (English) layout when no versification-specific data exists
    /// for this book.
    ///
    /// ``` rust
    /// use biblicist::{JOEL, Versification};
    /// let joel = &JOEL;
    /// // English Joel has 3 chapters; Masoretic Joel has 4
    /// assert_eq!(joel.verses_in_chapter_versified(3, Versification::English), Some(21));
    /// assert_eq!(joel.verses_in_chapter_versified(3, Versification::Masoretic), Some(5));
    /// assert_eq!(joel.verses_in_chapter_versified(4, Versification::Masoretic), Some(21));
    /// assert_eq!(joel.verses_in_chapter_versified(4, Versification::English), None);
    /// ```
    #[inline]
    pub fn verses_in_chapter_versified(
        &self,
        chapter: u8,
        versification: crate::Versification,
    ) -> Option<u8> {
        use crate::versification::get_layout_override;
        if let Some(override_layout) = get_layout_override(self.title, versification) {
            override_layout.get(chapter as usize).copied()
        } else {
            self.verses_in_chapter(chapter)
        }
    }

    /// Returns the numbers of verses in the specified chapter, or an error if the chapter does not exist.
    /// ``` rust
    /// use biblicist::GENESIS;
    /// let genesis = &GENESIS;
    /// assert_eq!(genesis.try_verses_in_chapter(1).unwrap(), 31);
    /// assert!(genesis.try_verses_in_chapter(51).is_err());
    /// ```
    pub fn try_verses_in_chapter(&self, chapter: u8) -> Result<u8, ReferenceError> {
        self.verses_in_chapter(chapter)
            .ok_or_else(|| ReferenceError::InvalidChapter(self.title.to_string(), chapter))
    }

    pub fn order_english(&self) -> isize {
        self.sort_order.english
    }

    /// Returns the order for the given ordering name. Returns None if the ordering name is not recognized.
    /// Supported orderings: "english", "septuagint", "vulgate", "lenningrad", "bhs", "snaith", "talmud", "vaticanus", "sinaiticus", "alexandrinus"
    pub fn order_by_name(&self, name: &str) -> Option<isize> {
        match name.to_lowercase().as_str() {
            "english" => Some(self.sort_order.english),
            "septuagint" => Some(self.sort_order.septuagint),
            "vulgate" => Some(self.sort_order.vulgate),
            "lenningrad" => Some(self.sort_order.lenningrad),
            "bhs" => Some(self.sort_order.bhs),
            "snaith" => Some(self.sort_order.snaith),
            "talmud" => Some(self.sort_order.talmud),
            "vaticanus" => Some(self.sort_order.vaticanus),
            "sinaiticus" => Some(self.sort_order.sinaiticus),
            "alexandrinus" => Some(self.sort_order.alexandrinus),
            _ => None,
        }
    }

    /// Checks if the provided name matches the book's title, any of its aliases, or any of its abbreviations.
    pub fn is_name(&self, name: &str) -> bool {
        let name_lower = name.to_lowercase();
        if self.title.to_lowercase() == name_lower {
            return true;
        }
        for &alias in self.aliases {
            if alias.to_lowercase() == name_lower {
                return true;
            }
        }
        for &abbr in self.abbreviations {
            if abbr.to_lowercase() == name_lower {
                return true;
            }
        }
        false
    }

    /// Returns a vec of everything that can be a reference to this book: title, aliases, and abbreviations.
    pub fn all_refs(&self) -> Vec<&'static str> {
        let mut names = vec![self.title];
        names.extend(self.aliases);
        names.extend(self.abbreviations);
        names
    }
}

pub fn is_book(value: &str) -> bool {
    ALL_BOOKS.iter().any(|&book| book.is_name(value))
}

pub fn get_book(value: &str) -> Option<&'static Book> {
    ALL_BOOKS.iter().find_map(|&book| {
        if book.is_name(value) {
            Some(book)
        } else {
            None
        }
    })
}

pub static ALL_BOOKS: [&Book; 66] = [
    &GENESIS,
    &EXODUS,
    &LEVITICUS,
    &NUMBERS,
    &DEUTERONOMY,
    &JOSHUA,
    &JUDGES,
    &RUTH,
    &FIRST_SAMUEL,
    &SECOND_SAMUEL,
    &FIRST_KINGS,
    &SECOND_KINGS,
    &FIRST_CHRONICLES,
    &SECOND_CHRONICLES,
    &EZRA,
    &NEHEMIAH,
    &ESTHER,
    &JOB,
    &PSALMS,
    &PROVERBS,
    &ECCLESIASTES,
    &SONG_OF_SONGS,
    &ISAIAH,
    &JEREMIAH,
    &LAMENTATIONS,
    &EZEKIEL,
    &DANIEL,
    &HOSEA,
    &JOEL,
    &AMOS,
    &OBADIAH,
    &JONAH,
    &MICAH,
    &NAHUM,
    &HABAKKUK,
    &ZEPHANIAH,
    &HAGGAI,
    &ZECHARIAH,
    &MALACHI,
    &MATTHEW,
    &MARK,
    &LUKE,
    &JOHN,
    &ACTS,
    &ROMANS,
    &FIRST_CORINTHIANS,
    &SECOND_CORINTHIANS,
    &GALATIANS,
    &EPHESIANS,
    &PHILIPPIANS,
    &COLOSSIANS,
    &FIRST_THESSALONIANS,
    &SECOND_THESSALONIANS,
    &FIRST_TIMOTHY,
    &SECOND_TIMOTHY,
    &TITUS,
    &PHILEMON,
    &HEBREWS,
    &JAMES,
    &FIRST_PETER,
    &SECOND_PETER,
    &FIRST_JOHN,
    &SECOND_JOHN,
    &THIRD_JOHN,
    &JUDE,
    &REVELATION,
];

#[cfg(test)]
mod tests {
    use crate::book::nt::MATTHEW;
    use crate::book::ot::GENESIS;

    #[test]
    fn identify_testament() {
        assert!(GENESIS.is_old_testament());
        assert!(MATTHEW.is_new_testament() && !MATTHEW.is_old_testament());
    }

    #[test]
    fn chapter_count() {
        assert_eq!(GENESIS.chapters(), 50);
        assert_eq!(MATTHEW.chapters(), 28);
    }

    #[test]
    fn is_book_name() {
        assert!(GENESIS.is_name("Genesis"));
        assert!(GENESIS.is_name("Gen"));
        assert!(!GENESIS.is_name("The First Book of Moses Called Genesis"));
        assert!(!GENESIS.is_name("Exodus"));
    }

    #[test]
    fn can_get_book_by_name() {
        let book = crate::book::get_book("Gen").unwrap();
        assert_eq!(book.title, "Genesis");

        let book = crate::book::get_book("Matthew").unwrap();
        assert_eq!(book.title, "Matthew");

        assert!(crate::book::get_book("Nonexistent Book").is_none());
    }
}
