package yuku.alkitab.base.sync;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.gson.reflect.TypeToken;
import yuku.alkitab.base.App;
import yuku.alkitab.base.S;
import yuku.alkitab.base.U;
import yuku.alkitab.base.model.SyncShadow;
import yuku.alkitab.base.util.Literals;
import yuku.alkitab.base.util.Sqlitil;
import yuku.alkitab.model.Label;
import yuku.alkitab.model.Marker;
import yuku.alkitab.model.Marker_Label;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
public class Sync_Mabel {
public static Sync.GetClientStateResult<Content> getClientStateAndCurrentEntities() {
final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_MABEL);
final List<Sync.Entity<Content>> srcs = ss == null? Literals.List(): entitiesFromShadow(ss);
final List<Sync.Entity<Content>> dsts = getEntitiesFromCurrent();
final Sync.Delta<Content> delta = new Sync.Delta<>();
// additions and modifications
for (final Sync.Entity<Content> dst : dsts) {
final Sync.Entity<Content> existing = findEntity(srcs, dst.gid, dst.kind);
if (existing == null) {
delta.operations.add(new Sync.Operation<>(Sync.Opkind.add, dst.kind, dst.gid, dst.content));
} else {
if (!isSameContent(dst, existing)) { // only when it changes
delta.operations.add(new Sync.Operation<>(Sync.Opkind.mod, dst.kind, dst.gid, dst.content));
}
}
}
// deletions
for (final Sync.Entity<Content> src : srcs) {
final Sync.Entity<Content> still_have = findEntity(dsts, src.gid, src.kind);
if (still_have == null) {
delta.operations.add(new Sync.Operation<>(Sync.Opkind.del, src.kind, src.gid, null));
}
}
return new Sync.GetClientStateResult<>(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), srcs, dsts);
}
private static boolean isSameContent(final Sync.Entity<Content> a, final Sync.Entity<Content> b) {
if (!U.equals(a.gid, b.gid)) return false;
if (!U.equals(a.kind, b.kind)) return false;
return U.equals(a.content, b.content);
}
private static Sync.Entity<Content> findEntity(final List<Sync.Entity<Content>> list, final String gid, final String kind) {
for (final Sync.Entity<Content> entity : list) {
if (U.equals(gid, entity.gid) && U.equals(kind, entity.kind)) {
return entity;
}
}
return null;
}
private static List<Sync.Entity<Content>> entitiesFromShadow(@NonNull final SyncShadow ss) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(ss.data), Charset.forName("utf-8")));
final Sync.SyncShadowDataJson<Content> data = App.getDefaultGson().fromJson(reader, new TypeToken<Sync.SyncShadowDataJson<Content>>() {}.getType());
return data.entities;
}
@NonNull public static SyncShadow shadowFromEntities(@NonNull final List<Sync.Entity<Content>> entities, final int revno) {
final Sync.SyncShadowDataJson<Content> data = new Sync.SyncShadowDataJson<>();
data.entities = entities;
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(baos, Charset.forName("utf-8")));
App.getDefaultGson().toJson(data, new TypeToken<Sync.SyncShadowDataJson<Content>>() {}.getType(), w);
U.wontThrow(() -> w.flush());
final SyncShadow res = new SyncShadow();
res.data = baos.toByteArray();
res.syncSetName = SyncShadow.SYNC_SET_MABEL;
res.revno = revno;
return res;
}
@NonNull public static List<Sync.Entity<Content>> getEntitiesFromCurrent() {
final List<Sync.Entity<Content>> res = new ArrayList<>();
{ // markers
for (final Marker marker : S.getDb().listAllMarkers()) {
final Content content = new Content();
content.ari = marker.ari;
content.caption = marker.caption;
content.kind = marker.kind.code;
content.verseCount = marker.verseCount;
content.createTime = Sqlitil.toInt(marker.createTime);
content.modifyTime = Sqlitil.toInt(marker.modifyTime);
final Sync.Entity<Content> entity = new Sync.Entity<>(Sync.Entity.KIND_MARKER, marker.gid, content);
res.add(entity);
}
}
{ // labels
for (final Label label : S.getDb().listAllLabels()) {
final Content content = new Content();
content.title = label.title;
content.backgroundColor = label.backgroundColor;
content.ordering = label.ordering;
final Sync.Entity<Content> entity = new Sync.Entity<>(Sync.Entity.KIND_LABEL, label.gid, content);
res.add(entity);
}
}
{ // marker_labels
for (final Marker_Label marker_label : S.getDb().listAllMarker_Labels()) {
final Content content = new Content();
content.marker_gid = marker_label.marker_gid;
content.label_gid = marker_label.label_gid;
final Sync.Entity<Content> entity = new Sync.Entity<>(Sync.Entity.KIND_MARKER_LABEL, marker_label.gid, content);
res.add(entity);
}
}
return res;
}
/**
* Modify or create a label from an entity content. This is called when the server append delta
* asks for an add or a mod operation.
* This will not merge content, will only overwrite.
* @param label an existing label (content will be modified), or null to create a new label
* @param content entity content, containing the new data.
*/
@NonNull public static Label updateLabelWithEntityContent(@Nullable final Label label, @NonNull final String gid, @NonNull final Content content) {
final Label res = label != null ? label : Label.createEmptyLabel();
res.gid = gid;
res.title = content.title;
res.ordering = content.ordering;
res.backgroundColor = content.backgroundColor;
return res;
}
/**
* Modify or create a marker-label association from an entity content. This is called when the server append delta
* asks for an add or a mod operation.
* This will not merge content, will only overwrite.
* @param marker_label an existing marker-label association (content will be modified), or null to create a new marker-label association
* @param content entity content, containing the new data.
*/
@NonNull public static Marker_Label updateMarker_LabelWithEntityContent(@Nullable final Marker_Label marker_label, @NonNull final String gid, @NonNull final Content content) {
final Marker_Label res = marker_label != null ? marker_label : Marker_Label.createEmptyMarker_Label();
res.gid = gid;
res.marker_gid = content.marker_gid;
res.label_gid = content.label_gid;
return res;
}
/**
* Modify or create a marker from an entity content. This is called when the server append delta
* asks for an add or a mod operation.
* This will not merge content, will only overwrite.
* @param marker an existing marker (content will be modified), or null to create a new marker
* @param content entity content, containing the new data.
*/
@NonNull public static Marker updateMarkerWithEntityContent(@Nullable final Marker marker, @NonNull final String gid, @NonNull final Content content) {
final Marker res = marker != null ? marker : Marker.createEmptyMarker();
res.gid = gid;
res.ari = content.ari;
res.kind = Marker.Kind.fromCode(content.kind);
res.caption = content.caption;
res.verseCount = content.verseCount;
res.createTime = Sqlitil.toDate(content.createTime);
res.modifyTime = Sqlitil.toDate(content.modifyTime);
return res;
}
/**
* Entity content for {@link yuku.alkitab.model.Marker} and {@link yuku.alkitab.model.Label}.
*/
public static class Content {
public Integer ari; // marker
public Integer kind; // marker
public String caption; // marker
public Integer verseCount; // marker
public Integer createTime; // marker
public Integer modifyTime; // marker
public String title; // label
public Integer ordering; // label
public String backgroundColor; // label
public String marker_gid; // marker_label
public String label_gid; // marker_label
//region boilerplate equals and hashCode methods
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Content that = (Content) o;
if (ari != null ? !ari.equals(that.ari) : that.ari != null) return false;
if (backgroundColor != null ? !backgroundColor.equals(that.backgroundColor) : that.backgroundColor != null) return false;
if (caption != null ? !caption.equals(that.caption) : that.caption != null) return false;
if (createTime != null ? !createTime.equals(that.createTime) : that.createTime != null) return false;
if (kind != null ? !kind.equals(that.kind) : that.kind != null) return false;
if (label_gid != null ? !label_gid.equals(that.label_gid) : that.label_gid != null) return false;
if (marker_gid != null ? !marker_gid.equals(that.marker_gid) : that.marker_gid != null) return false;
if (modifyTime != null ? !modifyTime.equals(that.modifyTime) : that.modifyTime != null) return false;
if (ordering != null ? !ordering.equals(that.ordering) : that.ordering != null) return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
if (verseCount != null ? !verseCount.equals(that.verseCount) : that.verseCount != null) return false;
return true;
}
@Override
public int hashCode() {
int result = ari != null ? ari.hashCode() : 0;
result = 31 * result + (kind != null ? kind.hashCode() : 0);
result = 31 * result + (caption != null ? caption.hashCode() : 0);
result = 31 * result + (verseCount != null ? verseCount.hashCode() : 0);
result = 31 * result + (createTime != null ? createTime.hashCode() : 0);
result = 31 * result + (modifyTime != null ? modifyTime.hashCode() : 0);
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (ordering != null ? ordering.hashCode() : 0);
result = 31 * result + (backgroundColor != null ? backgroundColor.hashCode() : 0);
result = 31 * result + (marker_gid != null ? marker_gid.hashCode() : 0);
result = 31 * result + (label_gid != null ? label_gid.hashCode() : 0);
return result;
}
//endregion
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
if (ari != null) sb.append(ari).append(' ');
if (kind != null) sb.append(kind).append(' ');
if (caption != null) sb.append(q(caption)).append(' ');
if (verseCount != null) sb.append(verseCount).append(' ');
if (createTime != null) sb.append(createTime).append(' ');
if (modifyTime != null) sb.append(modifyTime).append(' ');
if (title != null) sb.append(q(title)).append(' ');
if (ordering != null) sb.append(ordering).append(' ');
if (backgroundColor != null) sb.append(backgroundColor).append(' ');
if (marker_gid != null) sb.append(marker_gid.length() <= 10? marker_gid: marker_gid.substring(0, 10)).append(' ');
if (label_gid != null) sb.append(label_gid.length() <= 10? label_gid: label_gid.substring(0, 10)).append(' ');
sb.setLength(sb.length() - 1);
sb.append('}');
return sb.toString();
}
@NonNull
static String q(@NonNull String s) {
final String c;
if (s.length() > 20) {
c = s.substring(0, 19).replace("\n", "\\n") + "…";
} else {
c = s.replace("\n", "\\n");
}
return "'" + c + "'";
}
}
}