package yuku.alkitabconverter.yes_common;
import yuku.alkitab.model.FootnoteEntry;
import yuku.alkitab.model.XrefEntry;
import yuku.alkitab.yes2.Yes2Writer;
import yuku.alkitab.yes2.compress.SnappyOutputStream;
import yuku.alkitab.yes2.io.MemoryRandomOutputStream;
import yuku.alkitab.yes2.io.RandomAccessFileRandomOutputStream;
import yuku.alkitab.yes2.io.RandomOutputStream;
import yuku.alkitab.yes2.model.PericopeData;
import yuku.alkitab.yes2.model.VerseBytes;
import yuku.alkitab.yes2.model.Yes2Book;
import yuku.alkitab.yes2.section.BooksInfoSection;
import yuku.alkitab.yes2.section.FootnotesSection;
import yuku.alkitab.yes2.section.PericopesSection;
import yuku.alkitab.yes2.section.VersionInfoSection;
import yuku.alkitab.yes2.section.XrefsSection;
import yuku.alkitab.yes2.section.base.SectionContent;
import yuku.alkitabconverter.util.FootnoteDb;
import yuku.alkitabconverter.util.TextDb;
import yuku.alkitabconverter.util.TextDb.VerseState;
import yuku.alkitabconverter.util.XrefDb;
import yuku.bintex.BintexWriter;
import yuku.bintex.ValueMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Scanner;
public class Yes2Common {
public static final String TAG = Yes2Common.class.getSimpleName();
public static class VersionInfo {
public String locale;
public String shortName;
public String longName;
public String description;
private List<String> bookNames;
private List<String> bookAbbreviations;
public String getBookShortName(int bookId) {
return bookNames.get(bookId);
}
public String getBookAbbreviation(final int bookId) {
if (bookAbbreviations == null) return null;
return bookAbbreviations.get(bookId);
}
public void setBookNamesAndAbbreviations(List<String> bookNames, List<String> bookAbbreviations) {
this.bookNames = bookNames;
this.bookAbbreviations = bookAbbreviations;
}
public void setBookNamesFromFile(final String inputBookNames) throws IOException {
List<String> bookNames = new ArrayList<>();
final Scanner sc = new Scanner(new File(inputBookNames), "utf-8");
int c = 0;
while (sc.hasNextLine()) {
final String line = sc.nextLine().trim();
bookNames.add(line);
c++;
}
this.bookNames = bookNames;
}
}
private static BooksInfoSection getBooksInfoSection(VersionInfo versionInfo, TextDb textDb) throws IOException {
// no nulls allowed
final List<Yes2Book> yes2books = new ArrayList<>();
// for the text offset from the beginning of text section
int offsetTotal = 0;
// for the text offset from the beginning of book
int offsetPassed = 0;
for (int bookId: textDb.getBookIds()) {
Yes2Book b = new Yes2Book();
b.bookId = bookId;
b.chapter_count = textDb.getChapterCountForBook(bookId);
b.chapter_offsets = new int[b.chapter_count + 1];
b.offset = offsetTotal;
b.shortName = versionInfo.getBookShortName(bookId);
b.abbreviation = versionInfo.getBookAbbreviation(bookId);
b.verse_counts = new int[b.chapter_count];
b.chapter_offsets[0] = 0;
for (int chapter_0 = 0; chapter_0 < b.chapter_count; chapter_0++) {
b.verse_counts[chapter_0] = textDb.getVerseCountForBookChapter(bookId, chapter_0 + 1);
for (int verse_0 = 0; verse_0 < b.verse_counts[chapter_0]; verse_0++) {
String verseText = textDb.getVerseText(bookId, chapter_0+1, verse_0+1);
offsetPassed += VerseBytes.bytesForAVerse(verseText).length;
}
b.chapter_offsets[chapter_0 + 1] = offsetPassed;
}
yes2books.add(b);
//# reset
offsetTotal += offsetPassed;
offsetPassed = 0;
}
BooksInfoSection res = new BooksInfoSection();
res.yes2Books = yes2books;
return res;
}
private static VersionInfoSection getVersionInfoSection(VersionInfo versionInfo, TextDb textDb, boolean hasPericopes) {
VersionInfoSection res = new VersionInfoSection();
res.book_count = textDb.getBookCount();
res.description = versionInfo.description;
res.hasPericopes = hasPericopes? 1: 0;
res.locale = versionInfo.locale;
res.longName = versionInfo.longName;
res.shortName = versionInfo.shortName;
res.buildTime = (int) (System.currentTimeMillis() / 1000);
res.textEncoding = 2; // utf-8
return res;
}
public static void createYesFile(final File outputFile, final VersionInfo versionInfo, final TextDb textDb, PericopeData pericopeData, boolean compressed) throws IOException {
createYesFile(outputFile, versionInfo, textDb, pericopeData, compressed, null, null);
}
public static void createYesFile(final File outputFile, final VersionInfo versionInfo, final TextDb textDb, PericopeData pericopeData, boolean compressed, final LinkedHashMap<Integer, XrefEntry> xrefEntries, final LinkedHashMap<Integer, FootnoteEntry> footnoteEntries) throws IOException {
RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
raf.setLength(0);
RandomOutputStream output = new RandomAccessFileRandomOutputStream(raf);
createYesFile(output, versionInfo, textDb, pericopeData, compressed, xrefEntries, footnoteEntries);
output.close();
}
public static void createYesFile(final RandomOutputStream ros, final VersionInfo versionInfo, final TextDb textDb, PericopeData pericopeData, boolean compressed, final LinkedHashMap<Integer, XrefEntry> xrefEntries, final LinkedHashMap<Integer, FootnoteEntry> footnoteEntries) throws IOException {
VersionInfoSection versionInfoSection = getVersionInfoSection(versionInfo, textDb, pericopeData != null);
BooksInfoSection booksInfoSection = getBooksInfoSection(versionInfo, textDb);
Yes2Writer yesWriter = new Yes2Writer();
yesWriter.sections.add(versionInfoSection);
yesWriter.sections.add(booksInfoSection);
if (pericopeData != null) {
yesWriter.sections.add(new CompressiblePericopesSection(pericopeData, compressed));
}
yesWriter.sections.add(new CompressibleLazyText(textDb, compressed));
if (xrefEntries != null) {
yesWriter.sections.add(new CompressibleXrefsSection(xrefEntries, compressed));
}
if (footnoteEntries != null) {
yesWriter.sections.add(new CompressibleFootnotesSection(footnoteEntries, compressed));
}
yesWriter.writeToFile(ros);
}
static class CompressionInfo {
final boolean compressed;
final int COMPRESS_BLOCK_SIZE = 32768;
ByteArrayOutputStream outputBuffer;
SnappyOutputStream snappyOutputStream; // could be null. Used for flushing.
private int[] compressed_block_sizes;
CompressionInfo(final boolean compressed) {
this.compressed = compressed;
}
OutputStream getOutputStream() {
outputBuffer = new ByteArrayOutputStream();
if (compressed) {
snappyOutputStream = new SnappyOutputStream(outputBuffer, COMPRESS_BLOCK_SIZE);
return snappyOutputStream;
}
return outputBuffer;
}
public ValueMap getSectionAttributes() {
final ValueMap res = new ValueMap();
if (compressed) {
assert compressed_block_sizes != null;
ValueMap compressionInfo = new ValueMap();
compressionInfo.put("block_size", COMPRESS_BLOCK_SIZE);
compressionInfo.put("compressed_block_sizes", compressed_block_sizes);
res.put("compression.name", "snappy-blocks");
res.put("compression.version", 1);
res.put("compression.info", compressionInfo);
}
return res;
}
public void finalizeOutputStream() throws IOException {
if (snappyOutputStream != null) {
snappyOutputStream.flush();
compressed_block_sizes = snappyOutputStream.getCompressedBlockSizes();
}
}
public void writeOutputBufferTo(final RandomOutputStream output) throws IOException {
outputBuffer.writeTo(output);
}
}
static class CompressibleLazyText extends SectionContent implements SectionContent.Writer {
final CompressionInfo compressionInfo;
public CompressibleLazyText(TextDb textDb, boolean compressed) throws IOException {
super("text");
compressionInfo = new CompressionInfo(compressed);
final BintexWriter bw = new BintexWriter(compressionInfo.getOutputStream());
textDb.processEach(new TextDb.TextProcessor() {
@Override public void process(int ari, VerseState verseState) {
byte[] bytes = VerseBytes.bytesForAVerse(verseState.text);
try {
bw.writeRaw(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
compressionInfo.finalizeOutputStream();
}
@Override public ValueMap getAttributes() {
return compressionInfo.getSectionAttributes();
}
@Override public void write(RandomOutputStream output) throws IOException {
compressionInfo.writeOutputBufferTo(output);
}
}
static class CompressiblePericopesSection extends PericopesSection implements SectionContent.Writer {
final CompressionInfo compressionInfo;
public CompressiblePericopesSection(PericopeData pericopeData, boolean compressed) {
super(pericopeData);
compressionInfo = new CompressionInfo(compressed);
try {
final MemoryRandomOutputStream mem = new MemoryRandomOutputStream();
super.write(mem);
final OutputStream os = compressionInfo.getOutputStream();
os.write(mem.getBuffer(), mem.getBufferOffset(), mem.getBufferLength());
compressionInfo.finalizeOutputStream();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override public ValueMap getAttributes() {
return compressionInfo.getSectionAttributes();
}
@Override public void write(RandomOutputStream output) throws IOException {
// DO NOT CALL SUPER!
compressionInfo.writeOutputBufferTo(output);
}
}
static class CompressibleXrefsSection extends SectionContent implements SectionContent.Writer {
final CompressionInfo compressionInfo;
public CompressibleXrefsSection(final LinkedHashMap<Integer, XrefEntry> xrefEntries, boolean compressed) throws IOException {
super(XrefsSection.SECTION_NAME);
compressionInfo = new CompressionInfo(compressed);
final BintexWriter bw = new BintexWriter(compressionInfo.getOutputStream());
XrefDb.writeXrefEntriesTo(xrefEntries, bw);
compressionInfo.finalizeOutputStream();
}
@Override public ValueMap getAttributes() {
return compressionInfo.getSectionAttributes();
}
@Override public void write(RandomOutputStream output) throws IOException {
compressionInfo.writeOutputBufferTo(output);
}
}
static class CompressibleFootnotesSection extends SectionContent implements SectionContent.Writer {
final CompressionInfo compressionInfo;
public CompressibleFootnotesSection(final LinkedHashMap<Integer, FootnoteEntry> footnoteEntries, boolean compressed) throws IOException {
super(FootnotesSection.SECTION_NAME);
compressionInfo = new CompressionInfo(compressed);
final BintexWriter bw = new BintexWriter(compressionInfo.getOutputStream());
FootnoteDb.writeFootnoteEntriesTo(footnoteEntries, bw);
compressionInfo.finalizeOutputStream();
}
@Override public ValueMap getAttributes() {
return compressionInfo.getSectionAttributes();
}
@Override public void write(RandomOutputStream output) throws IOException {
compressionInfo.writeOutputBufferTo(output);
}
}
}