package yuku.alkitab.base.util;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Xml;
import com.afollestad.materialdialogs.MaterialDialog;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntLongHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;
import yuku.alkitab.base.App;
import yuku.alkitab.base.IsiActivity;
import yuku.alkitab.base.S;
import yuku.alkitab.base.ac.base.BaseActivity;
import yuku.alkitab.base.storage.Db;
import yuku.alkitab.base.storage.InternalDb;
import yuku.alkitab.debug.R;
import yuku.alkitab.model.Label;
import yuku.alkitab.model.Marker;
import yuku.alkitab.model.Marker_Label;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static yuku.alkitab.base.util.Literals.ToStringArray;
// Imported from v3. Used for once-only migration from v3 to v4.
public class BookmarkImporter {
static final String TAG = BookmarkImporter.class.getSimpleName();
// constants
static class Bookmark2_Label { // DO NOT CHANGE CONSTANT VALUES!
public static final String XMLTAG_Bookmark2_Label = "Bukmak2_Label";
public static final String XMLATTR_bookmark2_relId = "bukmak2_relId";
public static final String XMLATTR_label_relId = "label_relId";
}
// constants
static class BackupManager {
public static final String XMLTAG_Bukmak2 = "Bukmak2";
private static final String XMLATTR_ari = "ari";
private static final String XMLATTR_kind = "jenis";
private static final String XMLATTR_caption = "tulisan";
private static final String XMLATTR_addTime = "waktuTambah";
private static final String XMLATTR_modifyTime = "waktuUbah";
private static final String XMLATTR_relId = "relId";
private static final String XMLVAL_bookmark = "bukmak";
private static final String XMLVAL_note = "catatan";
private static final String XMLVAL_highlight = "stabilo";
public static final String XMLTAG_Label = "Label";
private static final String XMLATTR_title = "judul";
private static final String XMLATTR_bgColor = "warnaLatar";
@Nullable
public static Marker markerFromAttributes(Attributes attributes) {
int ari = Integer.parseInt(attributes.getValue("", XMLATTR_ari));
String kind_s = attributes.getValue("", XMLATTR_kind);
Marker.Kind kind = kind_s.equals(XMLVAL_bookmark) ? Marker.Kind.bookmark : kind_s.equals(XMLVAL_note) ? Marker.Kind.note : kind_s.equals(XMLVAL_highlight) ? Marker.Kind.highlight : null;
String caption = unescapeHighUnicode(attributes.getValue("", XMLATTR_caption));
Date addTime = Sqlitil.toDate(Integer.parseInt(attributes.getValue("", XMLATTR_addTime)));
Date modifyTime = Sqlitil.toDate(Integer.parseInt(attributes.getValue("", XMLATTR_modifyTime)));
if (kind == null) { // invalid
return null;
}
return Marker.createNewMarker(ari, kind, caption, 1, addTime, modifyTime);
}
public static int getRelId(Attributes attributes) {
String s = attributes.getValue("", XMLATTR_relId);
return s == null ? 0 : Integer.parseInt(s);
}
public static Label labelFromAttributes(Attributes attributes) {
String title = unescapeHighUnicode(attributes.getValue("", XMLATTR_title));
String bgColor = attributes.getValue("", XMLATTR_bgColor);
return Label.createNewLabel(title, 0, bgColor);
}
static ThreadLocal<Matcher> highUnicodeMatcher = new ThreadLocal<Matcher>() {
@Override
protected Matcher initialValue() {
return Pattern.compile("\\[\\[~U([0-9A-Fa-f]{6})~\\]\\]").matcher("");
}
};
public static String unescapeHighUnicode(String input) {
if (input == null) return null;
final Matcher m = highUnicodeMatcher.get();
m.reset(input);
StringBuffer res = new StringBuffer();
while (m.find()) {
String s = m.group(1);
final int cp = Integer.parseInt(s, 16);
m.appendReplacement(res, new String(new int[]{cp}, 0, 1));
}
m.appendTail(res);
return res.toString();
}
}
public static void importBookmarks(final Activity activity, @NonNull final InputStream fis, final boolean finishActivityAfterwards, final Runnable runWhenDone) {
final MaterialDialog pd = new MaterialDialog.Builder(activity)
.content(R.string.mengimpor_titiktiga)
.cancelable(false)
.progress(true, 0)
.show();
new AsyncTask<Boolean, Integer, Object>() {
int count_bookmark = 0;
int count_label = 0;
@Override
protected Object doInBackground(Boolean... params) {
final List<Marker> markers = new ArrayList<>();
final TObjectIntHashMap<Marker> markerToRelIdMap = new TObjectIntHashMap<>();
final List<Label> labels = new ArrayList<>();
final TObjectIntHashMap<Label> labelToRelIdMap = new TObjectIntHashMap<>();
final TIntLongHashMap labelRelIdToAbsIdMap = new TIntLongHashMap();
final TIntObjectHashMap<TIntList> markerRelIdToLabelRelIdsMap = new TIntObjectHashMap<>();
try {
Xml.parse(fis, Xml.Encoding.UTF_8, new DefaultHandler2() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (localName) {
case BackupManager.XMLTAG_Bukmak2:
final Marker marker = BackupManager.markerFromAttributes(attributes);
if (marker != null) {
markers.add(marker);
final int bookmark2_relId = BackupManager.getRelId(attributes);
markerToRelIdMap.put(marker, bookmark2_relId);
count_bookmark++;
}
break;
case BackupManager.XMLTAG_Label: {
final Label label = BackupManager.labelFromAttributes(attributes);
int label_relId = BackupManager.getRelId(attributes);
labels.add(label);
labelToRelIdMap.put(label, label_relId);
count_label++;
break;
}
case Bookmark2_Label.XMLTAG_Bookmark2_Label: {
final int bookmark2_relId = Integer.parseInt(attributes.getValue("", Bookmark2_Label.XMLATTR_bookmark2_relId));
final int label_relId = Integer.parseInt(attributes.getValue("", Bookmark2_Label.XMLATTR_label_relId));
TIntList labelRelIds = markerRelIdToLabelRelIdsMap.get(bookmark2_relId);
if (labelRelIds == null) {
labelRelIds = new TIntArrayList();
markerRelIdToLabelRelIdsMap.put(bookmark2_relId, labelRelIds);
}
labelRelIds.add(label_relId);
break;
}
}
}
});
fis.close();
} catch (Exception e) {
return e;
}
{ // bikin label-label yang diperlukan, juga map relId dengan id dari label.
final HashMap<String, Label> judulMap = new HashMap<>();
final List<Label> xlabelLama = S.getDb().listAllLabels();
for (Label labelLama : xlabelLama) {
judulMap.put(labelLama.title, labelLama);
}
for (Label label : labels) {
// cari apakah label yang judulnya persis sama udah ada
Label labelLama = judulMap.get(label.title);
final int labelRelId = labelToRelIdMap.get(label);
if (labelLama != null) {
// removed from v3: update warna label lama
labelRelIdToAbsIdMap.put(labelRelId, labelLama._id);
Log.d(BaseActivity.TAG, "label (lama) r->a : " + labelRelId + "->" + labelLama._id);
} else { // belum ada, harus bikin baru
Label labelBaru = S.getDb().insertLabel(label.title, label.backgroundColor);
labelRelIdToAbsIdMap.put(labelRelId, labelBaru._id);
Log.d(BaseActivity.TAG, "label (baru) r->a : " + labelRelId + "->" + labelBaru._id);
}
}
}
importBookmarks(markers, markerToRelIdMap, labelRelIdToAbsIdMap, markerRelIdToLabelRelIdsMap);
return null;
}
@Override
protected void onPostExecute(@NonNull Object result) {
pd.dismiss();
if (result instanceof Exception) {
Log.e(TAG, "Error when importing markers", (Throwable) result);
new MaterialDialog.Builder(activity)
.content(activity.getString(R.string.terjadi_kesalahan_ketika_mengimpor_pesan, ((Exception) result).getMessage()))
.positiveText(R.string.ok)
.show();
} else {
final Dialog dialog = new MaterialDialog.Builder(activity)
.content(activity.getString(R.string.impor_berhasil_angka_diproses, count_bookmark, count_label))
.positiveText(R.string.ok)
.show();
if (finishActivityAfterwards) {
dialog.setOnDismissListener(dialog1 -> activity.finish());
}
}
if (runWhenDone != null) runWhenDone.run();
}
}.execute();
}
public static void importBookmarks(List<Marker> markers, TObjectIntHashMap<Marker> markerToRelIdMap, TIntLongHashMap labelRelIdToAbsIdMap, TIntObjectHashMap<TIntList> markerRelIdToLabelRelIdsMap) {
SQLiteDatabase db = S.getDb().getWritableDatabase();
db.beginTransaction();
try {
final TIntObjectHashMap<Marker> markerRelIdToMarker = new TIntObjectHashMap<>();
{ // write new markers (if not available yet)
for (int i = 0; i < markers.size(); i++) {
Marker marker = markers.get(i);
final int marker_relId = markerToRelIdMap.get(marker);
// migrate: look for existing marker with same kind, ari, and content
try (Cursor cursor = db.query(
Db.TABLE_Marker,
null,
Db.Marker.ari + "=? and " + Db.Marker.kind + "=? and " + Db.Marker.caption + "=?",
ToStringArray(marker.ari, marker.kind.code, marker.caption),
null, null, null
)) {
if (cursor.moveToNext()) {
marker = InternalDb.markerFromCursor(cursor);
markers.set(i, marker);
} else {
InternalDb.insertMarker(db, marker);
}
// map it
markerRelIdToMarker.put(marker_relId, marker);
}
}
}
{ // now is marker-label assignments
for (final int marker_relId : markerRelIdToLabelRelIdsMap.keys()) {
final TIntList label_relIds = markerRelIdToLabelRelIdsMap.get(marker_relId);
final Marker marker = markerRelIdToMarker.get(marker_relId);
if (marker != null) {
// existing labels > 0: ignore
// existing labels == 0: insert
final int existing_label_count = (int) DatabaseUtils.queryNumEntries(db, Db.TABLE_Marker_Label, Db.Marker_Label.marker_gid + "=?", ToStringArray(marker.gid));
if (existing_label_count == 0) {
for (int label_relId : label_relIds.toArray()) {
final long label_id = labelRelIdToAbsIdMap.get(label_relId);
if (label_id > 0) {
final Label label = S.getDb().getLabelById(label_id);
final Marker_Label marker_label = Marker_Label.createNewMarker_Label(marker.gid, label.gid);
InternalDb.insertMarker_LabelIfNotExists(db, marker_label);
} else {
Log.w(TAG, "label_id is invalid!: " + label_id);
}
}
}
} else {
Log.w(TAG, "wrong marker_relId: " + marker_relId);
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
App.getLbm().sendBroadcast(new Intent(IsiActivity.ACTION_ATTRIBUTE_MAP_CHANGED));
}
}