package yuku.alkitab.base.model;
import android.support.annotation.Nullable;
import android.util.Log;
import yuku.alkitab.base.config.AppConfig;
import yuku.alkitab.base.storage.InternalReader;
import yuku.alkitab.base.storage.OldVerseTextDecoder;
import yuku.alkitab.io.BibleReader;
import yuku.alkitab.model.Book;
import yuku.alkitab.model.FootnoteEntry;
import yuku.alkitab.model.PericopeBlock;
import yuku.alkitab.model.SingleChapterVerses;
import yuku.alkitab.model.Version;
import yuku.alkitab.model.XrefEntry;
import yuku.alkitab.util.Ari;
import yuku.alkitab.util.IntArrayList;
import java.util.List;
public class VersionImpl extends Version {
public static final String TAG = VersionImpl.class.getSimpleName();
private BibleReader bibleReader;
private Book[] cache_books;
private Book[] cache_consecutiveBooks;
private static Version internalVersion;
public VersionImpl(BibleReader bibleReader) {
super();
this.bibleReader = bibleReader;
}
public static synchronized Version getInternalVersion() {
if (internalVersion == null) {
final AppConfig c = AppConfig.get();
internalVersion = new VersionImpl(new InternalReader(c.internalPrefix, c.internalLocale, c.internalShortName, c.internalLongName, new OldVerseTextDecoder.Utf8()));
}
return internalVersion;
}
/**
* Get the short name (abbreviation) of this version.
*/
@Override
public String getShortName() {
return bibleReader.getShortName();
}
@Override
public String getLongName() {
return bibleReader.getLongName();
}
@Override
public String getLocale() {
return bibleReader.getLocale();
}
/**
* Some books can be null. Using this method, the return value indexed will have the same value as the {@link Book#bookId}.
* i.e. return_value[bookId].bookId == bookId.
*/
private synchronized Book[] getBooks() {
if (cache_books == null) {
cache_books = this.bibleReader.loadBooks();
}
return cache_books;
}
/**
* @return The highest bookId on this version plus one.
*/
@Override
public synchronized int getMaxBookIdPlusOne() {
int max = -1;
for (Book b: getBooks()) {
if (b != null) {
if (b.bookId > max) max = b.bookId;
}
}
return max + 1;
}
/**
* @return same as {@link #getBooks()}, but none of the array elements is null.
* For enumerating available books.
* Note that using this, no guarantee that return_value[bookId].bookId == bookId.
*/
@Override
public synchronized Book[] getConsecutiveBooks() {
if (cache_consecutiveBooks == null) {
Book[] books1 = getBooks();
// count
int book_count = 0;
for (Book b: books1) {
if (b != null) {
book_count++;
}
}
Book[] books2 = new Book[book_count];
int c = 0;
for (Book b: books1) {
if (b != null) {
books2[c++] = b;
}
}
cache_consecutiveBooks = books2;
}
return cache_consecutiveBooks;
}
/**
* @return null if bookId is out of range, or the book is not available on this version.
*/
@Override
public synchronized Book getBook(int bookId) {
if (bookId < 0) return null;
final Book[] books = getBooks();
if (books == null) return null;
if (bookId < books.length) {
// fast path for OT+NT complete versions
Book book = books[bookId];
if (book != null && book.bookId == bookId) return book;
}
// linear search
for (Book book: books) {
if (book != null && book.bookId == bookId) {
return book;
}
}
return null;
}
@Override
public synchronized Book getFirstBook() {
Book[] books = getBooks();
for (Book b: books) {
if (b != null) return b;
}
Log.e(TAG, "No books available on this version. Version info: " + (this.bibleReader == null? "reader=null": (this.bibleReader.getLongName() + " books.length=" + books.length)));
return null;
}
@Override
@Nullable public synchronized String loadVerseText(int ari) {
return loadVerseText(getBook(Ari.toBook(ari)), Ari.toChapter(ari), Ari.toVerse(ari));
}
@Override
@Nullable public synchronized String loadVerseText(Book book, int chapter_1, int verse_1) {
if (book == null) {
return null;
}
SingleChapterVerses verses = bibleReader.loadVerseText(book, chapter_1, false, false);
if (verses == null) {
return null;
}
int verse_0 = verse_1 - 1;
if (verse_0 >= verses.getVerseCount()) {
return null;
}
return verses.getVerse(verse_0);
}
/**
* @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
*/
@Override
public synchronized int loadVersesByAriRanges(IntArrayList ariRanges, IntArrayList result_aris, List<String> result_verses) {
int res = 0;
result_aris.clear();
result_verses.clear();
for (int i = 0, len = ariRanges.size(); i < len; i+=2) {
int ari_start = ariRanges.get(i);
int ari_end = ariRanges.get(i + 1);
if (ari_start == 0 || ari_end == 0) {
continue;
}
if (Ari.toVerse(ari_start) == 0) {
// if start has verse 0, it means the start is verse 1.
ari_start |= 0x01;
}
if (Ari.toVerse(ari_end) == 0) {
// if end has verse 0, it means until the end of chapter.
ari_end |= 0xff;
}
if (ari_start == ari_end) {
// case: single verse
//noinspection UnnecessaryLocalVariable
int ari = ari_start;
Book book = getBook(Ari.toBook(ari));
if (book != null) {
final String verseText = loadVerseText(ari);
if (verseText != null) {
result_aris.add(ari);
result_verses.add(verseText);
res++;
}
}
} else {
int ari_start_bc = Ari.toBookChapter(ari_start);
int ari_end_bc = Ari.toBookChapter(ari_end);
if (ari_start_bc == ari_end_bc) {
// case: multiple verses in the same chapter
Book book = getBook(Ari.toBook(ari_start));
if (book != null) {
res += resultForOneChapter(book, ari_start_bc, Ari.toVerse(ari_start), Ari.toVerse(ari_end), result_aris, result_verses);
}
} else {
// case: multiple verses in different chapters
for (int ari_bc = ari_start_bc; ari_bc <= ari_end_bc; ari_bc += 0x0100) {
Book book = getBook(Ari.toBook(ari_bc));
int chapter_1 = Ari.toChapter(ari_bc);
if (book == null || chapter_1 <= 0 || chapter_1 > book.chapter_count) {
continue;
}
if (ari_bc == ari_start_bc) { // we're at the first requested chapter
res += resultForOneChapter(book, ari_bc, Ari.toVerse(ari_start), 0xff, result_aris, result_verses);
} else if (ari_bc == ari_end_bc) { // we're at the last requested chapter
res += resultForOneChapter(book, ari_bc, 0x01, Ari.toVerse(ari_end), result_aris, result_verses);
} else { // we're at the middle, request all verses!
res += resultForOneChapter(book, ari_bc, 0x01, 0xff, result_aris, result_verses);
}
}
}
}
}
return res;
}
/**
* @return number of verses put into the cursor
*/
private int resultForOneChapter(Book book, int ari_bc, int v_1_start, int v_1_end, IntArrayList result_aris, List<String> result_verses) {
final SingleChapterVerses verses = loadChapterText(book, Ari.toChapter(ari_bc));
if (verses == null) {
return 0;
}
int count = 0;
for (int v_1 = v_1_start; v_1 <= v_1_end; v_1++) {
final int v_0 = v_1 - 1;
if (v_0 < verses.getVerseCount()) {
final int ari = ari_bc | v_1;
final String verseText = verses.getVerse(v_0);
if (verseText != null) {
result_aris.add(ari);
result_verses.add(verseText);
count++;
}
} else {
// we're done with this chapter, no need to loop again
break;
}
}
return count;
}
/**
* 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
* @param max the maximum number of pericopes to return. The output arrays must have at least max entries.
* @return the number of pericopes loaded. 0 if the version does not have pericopes or some errors happen.
*/
@Override
public synchronized int loadPericope(int bookId, int chapter_1, int[] aris, PericopeBlock[] pericopeBlocks, int max) {
return bibleReader.loadPericope(bookId, chapter_1, aris, pericopeBlocks, max);
}
@Override
@Nullable
public synchronized SingleChapterVerses loadChapterText(Book book, int chapter_1) {
if (book == null) {
return null;
}
return bibleReader.loadVerseText(book, chapter_1, false, false);
}
@Override
@Nullable
public synchronized SingleChapterVerses loadChapterTextLowercased(Book book, int chapter_1) {
if (book == null) {
return null;
}
return bibleReader.loadVerseText(book, chapter_1, false, true);
}
@Override
public synchronized String loadChapterTextLowercasedWithoutSplit(Book book, int chapter_1) {
if (book == null) {
return null;
}
SingleChapterVerses singleVerse = bibleReader.loadVerseText(book, chapter_1, true, true);
if (singleVerse == null) {
return null;
}
return singleVerse.getVerse(0);
}
/**
* @param arif 24bit ari at the MSB + which xref field at the 8bit LSB (starts from 1)
*/
@Override
public synchronized XrefEntry getXrefEntry(final int arif) {
return bibleReader.getXrefEntry(arif);
}
/**
* @param arif 24bit ari at the MSB + which xref field at the 8bit LSB (starts from 1)
*/
@Override
public synchronized FootnoteEntry getFootnoteEntry(final int arif) {
return bibleReader.getFootnoteEntry(arif);
}
}