package yuku.alkitab.base.model;

import android.util.Log;
import yuku.alkitab.base.S;
import yuku.alkitab.base.ac.VersionsActivity;
import yuku.alkitab.base.storage.YesReaderFactory;
import yuku.alkitab.io.BibleReader;
import yuku.alkitab.model.Version;

import java.io.File;

/**
 * Version that is defined in the database.
 * If the version is downloaded from a definition in the preset list, the {@link #preset_name} will be non-null.
 */
public class MVersionDb extends MVersion {
	public static final int DEFAULT_ORDERING_START = 100;

	public String filename;
	public String preset_name;
	public boolean cache_active; // so we don't need to keep reading/writing from/to db
	/**
	 * The last-known update time of the version, so we can notify the user if their downloaded versions has update.
	 * If this is 0, it means the version does not support update notification, because the version is converted from legacy version of this app,
	 * or added from pdb/yes files manually.
	 */
	public int modifyTime;

	/**
	 * The version id for MVersionDb can be "preset/" followed by preset_name,
	 * or "file/" followed by absolute path name of the yes file.
	 *
	 * Therefore, the id starting with "preset/" does not always indicate MVersionPreset,
	 * since it probably indicate MVersionDb as well.
	 */
	@Override
	public String getVersionId() {
		if (preset_name != null) {
			return "preset/" + preset_name;
		}
		return "file/" + filename;
	}

	/**
	 * Return the preset_name of a version id, if possible. Null otherwise.
	 */
	public static String presetNameFromVersionId(String versionId) {
		if (versionId != null && versionId.startsWith("preset/")) {
			return versionId.substring(7);
		} else {
			return null;
		}
	}

	@Override
	public Version getVersion() {
		if (hasDataFile()) {
			final BibleReader reader = YesReaderFactory.createYesReader(filename);
			if (reader == null) {
				Log.e(VersionsActivity.TAG, "YesReaderFactory failed to open the yes file");
				return null;
			}
			return new VersionImpl(reader);
		} else {
			return null;
		}
	}

	public void setActive(boolean active) {
		this.cache_active = active;
		S.getDb().setVersionActive(this, active);
	}

	@Override
	public boolean getActive() {
		return this.cache_active;
	}

	@Override public boolean hasDataFile() {
		final File f = new File(filename);
		return f.exists() && f.canRead();
	}
}

@@ -269,16 +269,99 @@
 io.File;
+%0Aimport java.lang.ref.SoftReference;%0Aimport java.util.concurrent.ConcurrentHashMap;
 %0A%0A/**%0A *
@@ -1090,16 +1090,299 @@
 yTime;%0A%0A
+%09/**%0A%09 * VersionImpl instance cache, so we do not reinstantiate VersionImpl objects every time%0A%09 * we access versions. Especially when doing a version compare.%0A%09 */%0A%09static ConcurrentHashMap%3CString /* filename */, SoftReference%3CVersionImpl%3E%3E impl_cache = new ConcurrentHashMap%3C%3E();%0A%0A
 %09/**%0A%09 *
@@ -2153,16 +2153,221 @@
 le()) %7B%0A
+%09%09%09// check cache first%0A%09%09%09final SoftReference%3CVersionImpl%3E ref = impl_cache.get(filename);%0A%09%09%09if (ref != null) %7B%0A%09%09%09%09final VersionImpl res = ref.get();%0A%09%09%09%09if (res != null) %7B%0A%09%09%09%09%09return res;%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%0A
 %09%09%09final
@@ -2560,25 +2560,43 @@
 l;%0A%09%09%09%7D%0A
+%0A
 %09%09%09
-return
+final VersionImpl res =
  new Ver
@@ -2613,26 +2613,180 @@
 eader);%0A
-%09%09%7D else %7B
+%0A%09%09%09// put to cache%0A%09%09%09impl_cache.put(filename, new SoftReference%3C%3E(res));%0A%0A%09%09%09return res;%0A%09%09%7D else %7B%0A%09%09%09// clear from cache if any%0A%09%09%09impl_cache.remove(filename);%0A
 %0A%09%09%09retu

Implement a version cache, so we do not load versions repeatedly. Cache values are implemented using SoftReferences so it is sometimes too quickly evicted by GC, but I think this is better than using a fixed number of strong references, because we don't want RAM not freed for too long.