package yuku.alkitab.model;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import yuku.alkitab.util.Ari;
import yuku.alkitab.util.IntArrayList;

import java.util.List;

public abstract class Version {
	/**
	 * Get the short name (abbreviation) of this version.
	 */
	public abstract String getShortName();

	public abstract String getLongName();

	public abstract String getLocale();

	/**
	 * @return The highest bookId on this version plus one.
	 */
	public abstract int getMaxBookIdPlusOne();
	
	/**
	 * For enumerating available books.
	 * Note that using this, no guarantee that return_value[bookId].bookId == bookId.
	 */
	public abstract Book[] getConsecutiveBooks();
	
	/**
	 * @return null if bookId is out of range, or the book is not available on this version.
	 */
	@Nullable
	public abstract Book getBook(int bookId);
	
	public abstract Book getFirstBook();

	/**
	 * Load a single verse from this version.
	 * @return null if the verse is not available.
	 */
	@Nullable public abstract String loadVerseText(int ari);

	/**
	 * Load a single verse from this version.
	 * @return null if the verse is not available.
	 */
	@Nullable public abstract String loadVerseText(Book book, int chapter_1, int verse_1);

	/**
	 * @param ariRanges list of aris where even-indexed elements are start and odd-indexed elements are end (inclusive) aris
	 * @param result_aris (non-null, will be cleared first) list of aris loaded
	 * @param result_verses (non-null, will be cleared first) list of verse texts loaded
	 * @return the number of verses successfully loaded
	 */
	public abstract int loadVersesByAriRanges(IntArrayList ariRanges, IntArrayList result_aris, List<String> result_verses);

	/**
	 * Loads the list of pericopes for a chapter
	 * @param aris output parameter; will be filled in with the aris where the pericopes start
	 * @param pericopeBlocks output parameter; will be filled with the content of the pericopes
	 * @return the number of pericopes loaded. 0 if the version does not have pericopes or some errors happen.
	 */
	public abstract int loadPericope(int bookId, int chapter_1, List<Integer> aris, List<PericopeBlock> pericopeBlocks);

	@Nullable
	public abstract SingleChapterVerses loadChapterText(Book book, int chapter_1);

	@Nullable
	public abstract SingleChapterVerses loadChapterTextLowercased(Book book, int chapter_1);

	/**
	 * Load a whole chapter as a single string with verses separated by '\n' from this version.
	 * @return null if the chapter is not available.
	 */
	public abstract String loadChapterTextLowercasedWithoutSplit(Book book, int chapter_1);
	
	/**
	 * @param arif 24bit ari at the MSB + which xref field at the 8bit LSB (starts from 1)
	 */
	public abstract XrefEntry getXrefEntry(final int arif);

	/**
	 * @param arif 24bit ari at the MSB + which xref field at the 8bit LSB (starts from 1)
	 */
	public abstract FootnoteEntry getFootnoteEntry(final int arif);

	private static class UnavailableBookNames {
		static String[] names = {
			"Gen", "Ex", "Lev", "Num", "Deut", "Josh", "Judg", "Ruth", "1Sam", "2Sam", "1Ki", "2Ki", "1Chr", "2Chr", "Ezr", "Neh", "Est", "Job", "Ps", "Prov", "Ecc", "Song", "Isa", "Jer", "Lam", "Eze", "Dan", "Hos", "Joel", "Amos", "Ob", "Jon", "Mi", "Nah", "Hab", "Zeph", "Hag", "Zech", "Mal", "Mt", "Mk", "Luk", "Jn", "Acts", "Rm", "1Cor", "2Cor", "Gal", "Eph", "Php", "Col", "1Ths", "2The", "1Tim", "2Tim", "Tit", "Phm", "Hb", "Jam", "1Pt", "2Pt", "1Jn", "2Jn", "3Jn", "Jud", "Rev",
		};
	}

	public String reference(int ari) {
		int bookId = Ari.toBook(ari);
		int chapter_1 = Ari.toChapter(ari);
		int verse_1 = Ari.toVerse(ari);

		return reference(bookId, chapter_1, verse_1);
	}

	public String referenceWithVerseCount(final int ari, final int verseCount) {
		int bookId = Ari.toBook(ari);
		int chapter_1 = Ari.toChapter(ari);
		int verse_1 = Ari.toVerse(ari);

		if (verse_1 == 0 || verseCount == 1) { // verseCount does not matter
			return reference(bookId, chapter_1, verse_1);
		} else {
			return reference(bookId, chapter_1, verse_1) + "\u2013" /* endash */ + (verse_1 + verseCount - 1);
		}
	}

	private String getBookOrPlaceholder(final int bookId) {
		final Book book = getBook(bookId);

		if (book != null) {
			return book.shortName;
		}

		if (bookId < 0 || bookId >= UnavailableBookNames.names.length) {
			return "[?]";
		}

		return "[[" + UnavailableBookNames.names[bookId] + "]]";
	}

	public String reference(int bookId, int chapter_1, int verse_1) {
		final String shortName = getBookOrPlaceholder(bookId);

		if (verse_1 == 0) {
			if (chapter_1 == 0) {
				return shortName;
			} else {
				return Book.reference(shortName, chapter_1);
			}
		} else {
			return Book.reference(shortName, chapter_1, verse_1);
		}
	}

	private String getLastVerseString(final int bookId, final int chapter_1) {
		final Book book = getBook(bookId);
		if (book == null) {
			return "end";
		}

		if (chapter_1 < 1 || chapter_1 > book.chapter_count) {
			return "end";
		}

		return "" + book.verse_counts[chapter_1 - 1];
	}

	public String referenceRange(final int ari_start, final int ari_end) {
		// 5 parts required for e.g. Matthew 5:3–6:10
		// bcstr_start "Matthew 5"
		// vstr_start ":3"
		// en-dash "–"
		// bcstr_end "6"
		// vstr_end ":10"
		final String bcstr_start;
		final String vstr_start;
		final String en_dash;
		final String bcstr_end;
		final String vstr_end;

		final boolean same_book_and_chapter;

		if (Ari.toBookChapter(ari_start) == Ari.toBookChapter(ari_end)) { // same book, same chapter
			final int bookId = Ari.toBook(ari_start);
			final int chapter_1 = Ari.toChapter(ari_start);
			bcstr_start = Book.reference(getBookOrPlaceholder(bookId), chapter_1);
			bcstr_end = "";
			same_book_and_chapter = true;

		} else if (Ari.toBook(ari_start) == Ari.toBook(ari_end)) { // same book, different chapter
			final int bookId = Ari.toBook(ari_start);
			final int chapter_1_start = Ari.toChapter(ari_start);
			final int chapter_1_end = Ari.toChapter(ari_end);
			bcstr_start = Book.reference(getBookOrPlaceholder(bookId), chapter_1_start);
			bcstr_end = "" + chapter_1_end;
			same_book_and_chapter = false;

		} else { // different book, different chapter
			final int bookId_start = Ari.toBook(ari_start);
			final int bookId_end = Ari.toBook(ari_end);
			final int chapter_1_start = Ari.toChapter(ari_start);
			final int chapter_1_end = Ari.toChapter(ari_end);
			bcstr_start = Book.reference(getBookOrPlaceholder(bookId_start), chapter_1_start);
			bcstr_end = Book.reference(getBookOrPlaceholder(bookId_end), chapter_1_end);
			same_book_and_chapter = false;
		}

		// en-dash is only empty if both aris are exactly the same
		en_dash = ari_start == ari_end ? "" : "\u2013";

		// How to show verse depending on whether the verse is 0:
		// start, end, show start as, show end as (0 means verse is 0, 1 means verse is non-0)
		// 0, 0, nothing, nothing
		// 0, 1, :1, :verse
		// 1, 0, :verse, :end
		// 1, 1, :verse, :verse
		final int verse_1_start = Ari.toVerse(ari_start);
		final int verse_1_end = Ari.toVerse(ari_end);

		switch ((verse_1_start != 0 ? 2 : 0) + (verse_1_end != 0 ? 1 : 0)) {
			case 0:
			default: // should not happen
				vstr_start = vstr_end = "";
				break;
			case 1:
				vstr_start = ":1";
				vstr_end = (same_book_and_chapter ? "" : ":") + verse_1_end;
				break;
			case 2:
				vstr_start = ":" + verse_1_start;
				vstr_end = (same_book_and_chapter ? "" : ":") + getLastVerseString(Ari.toBook(ari_end), Ari.toChapter(ari_end));
				break;
			case 3:
				vstr_start = ":" + verse_1_start;
				if (ari_start == ari_end) {
					vstr_end = "";
				} else {
					vstr_end = (same_book_and_chapter ? "" : ":") + verse_1_end;
				}
				break;
		}

		return bcstr_start + vstr_start + en_dash + bcstr_end + vstr_end;
	}

	@NonNull
	public String getInitials() {
		final String shortName = getShortName();
		if (shortName != null) {
			return shortName;
		} else {
			final String longName = getLongName();
			if (longName == null) {
				return "";
			}

			if (longName.length() <= 6) {
				return longName.toUpperCase();
			}

			// try to get the first letter of each word
			final String[] words = longName.split("[^A-Za-z0-9]+");
			final char[] chars = new char[words.length];
			int cnt = 0;
			for (int i = 0; i < chars.length; i++) {
				if (words[i].length() > 0) {
					chars[cnt++] = Character.toUpperCase(words[i].charAt(0));
				}
			}
			return new String(chars, 0, cnt);
		}
	}
}
