/*
* Copyright (C) 2015 Actor LLC. <https://actor.im>
*/
package im.actor.runtime.generic.mvvm;
import com.google.j2objc.annotations.AutoreleasePool;
import com.google.j2objc.annotations.ObjectiveCName;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import im.actor.runtime.generic.mvvm.alg.ChangeBuilder;
import im.actor.runtime.generic.mvvm.alg.Modification;
import im.actor.runtime.actors.Actor;
import im.actor.runtime.actors.ActorCreator;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.Props;
import im.actor.runtime.generic.mvvm.alg.Modifications;
import static im.actor.runtime.actors.ActorSystem.system;
// Disabling Bounds checks for speeding up calculations
/*-[
#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1
]-*/
public class DisplayList<T> {
static {
system().addDispatcher("display_list");
}
private static int NEXT_ID = 0;
private final int DISPLAY_LIST_ID;
private ActorRef executor;
private ArrayList<T>[] lists;
private volatile int currentList;
private final OperationMode operationMode;
private volatile Object processedList;
private CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<AndroidChangeListener<T>> androidListeners = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<AppleChangeListener<T>> appleListeners = new CopyOnWriteArrayList<>();
private ListProcessor<T> listProcessor = null;
@ObjectiveCName("initWithMode:")
public DisplayList(OperationMode operationMode) {
this(operationMode, new ArrayList<T>());
}
@ObjectiveCName("initWithMode:withValues:")
public DisplayList(OperationMode operationMode, List<T> defaultValues) {
im.actor.runtime.Runtime.checkMainThread();
this.DISPLAY_LIST_ID = NEXT_ID++;
this.operationMode = operationMode;
this.executor = system().actorOf(Props.create(new ActorCreator() {
@Override
public ListSwitcher create() {
return new ListSwitcher(DisplayList.this);
}
}).changeDispatcher("display_list"), "display_lists/" + DISPLAY_LIST_ID);
this.lists = new ArrayList[2];
this.currentList = 0;
this.lists[0] = new ArrayList<T>(defaultValues);
this.lists[1] = new ArrayList<T>(defaultValues);
}
@ObjectiveCName("size")
public int getSize() {
// im.actor.runtime.Runtime.checkMainThread();
return lists[currentList].size();
}
@ObjectiveCName("itemWithIndex:")
public T getItem(int index) {
// im.actor.runtime.Runtime.checkMainThread();
return lists[currentList].get(index);
}
@ObjectiveCName("positionWithFind:")
public int getPosition(Object find) {
for (int i = 0; i < lists[currentList].size(); i++) {
if (lists[currentList].get(i).equals(find)) {
return i;
}
}
return -1;
}
@ObjectiveCName("editList:")
public void editList(Modification<T> mod) {
editList(mod, null);
}
@ObjectiveCName("editList:withCompletion:")
public void editList(Modification<T> mod, Runnable executeAfter) {
this.executor.send(new EditList<T>(mod, executeAfter, false));
}
@ObjectiveCName("editList:withCompletion:withLoadMoreFlag:")
public void editList(Modification<T> mod, Runnable executeAfter, boolean isLoadMore) {
this.executor.send(new EditList<T>(mod, executeAfter, isLoadMore));
}
@ObjectiveCName("editList:withLoadMoreFlag:")
public void editList(Modification<T> mod, boolean isLoadMore) {
this.executor.send(new EditList<T>(mod, null, isLoadMore));
}
@ObjectiveCName("forcePreprocessing")
public void forcePreprocessing() {
this.executor.send(new EditList<T>((Modification<T>) Modifications.noOp(), null, false));
}
@ObjectiveCName("setListProcessor:")
public void setListProcessor(ListProcessor<T> listProcessor) {
this.listProcessor = listProcessor;
}
@ObjectiveCName("getListProcessor")
public ListProcessor<T> getListProcessor() {
return listProcessor;
}
@ObjectiveCName("getProcessedList")
public Object getProcessedList() {
return processedList;
}
@ObjectiveCName("addListener:")
public void addListener(Listener listener) {
//im.actor.runtime.Runtime.checkMainThread();
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
@ObjectiveCName("removeListener:")
public void removeListener(Listener listener) {
//im.actor.runtime.Runtime.checkMainThread();
listeners.remove(listener);
}
@ObjectiveCName("addAndroidListener:")
public void addAndroidListener(AndroidChangeListener<T> listener) {
if (operationMode != OperationMode.ANDROID && operationMode != OperationMode.GENERAL) {
throw new RuntimeException("Unable to set Android Listener in iOS mode");
}
//im.actor.runtime.Runtime.checkMainThread();
if (!androidListeners.contains(listener)) {
androidListeners.add(listener);
}
}
@ObjectiveCName("removeAndroidListener:")
public void removeAndroidListener(AndroidChangeListener<T> listener) {
if (operationMode != OperationMode.ANDROID && operationMode != OperationMode.GENERAL) {
throw new RuntimeException("Unable to set Android Listener in iOS mode");
}
//im.actor.runtime.Runtime.checkMainThread();
androidListeners.remove(listener);
}
@ObjectiveCName("addAppleListener:")
public void addAppleListener(AppleChangeListener<T> listener) {
if (operationMode != OperationMode.IOS && operationMode != OperationMode.GENERAL) {
throw new RuntimeException("Unable to set Android Listener in Android mode");
}
//im.actor.runtime.Runtime.checkMainThread();
if (!appleListeners.contains(listener)) {
appleListeners.add(listener);
}
}
@ObjectiveCName("removeAppleListener:")
public void removeAppleListener(AppleChangeListener<T> listener) {
if (operationMode != OperationMode.IOS && operationMode != OperationMode.GENERAL) {
throw new RuntimeException("Unable to set Android Listener in Android mode");
}
//im.actor.runtime.Runtime.checkMainThread();
appleListeners.remove(listener);
}
// Update actor
private static class ListSwitcher<T> extends Actor {
private ArrayList<ModificationHolder<T>> pending = new ArrayList<ModificationHolder<T>>();
private boolean isLocked;
private DisplayList<T> displayList;
private ListSwitcher(DisplayList<T> displayList) {
this.displayList = displayList;
}
@AutoreleasePool
public void onEditList(final Modification<T> modification, final Runnable runnable, boolean isLoadMore) {
if (modification != null) {
pending.add(new ModificationHolder<T>(modification, runnable, isLoadMore));
}
if (isLocked) {
return;
}
if (pending.size() == 0) {
// Nothing to update
return;
}
ArrayList<T> backgroundList = displayList.lists[(displayList.currentList + 1) % 2];
ArrayList<T> initialList = new ArrayList<T>(backgroundList);
int count = 1;
for (ModificationHolder h : pending) {
if (h.isLoadMore) {
break;
}
}
ModificationHolder<T>[] dest = new ModificationHolder[count];
for (int i = 0; i < count; i++) {
dest[i] = pending.remove(0);
}
ArrayList<ChangeDescription<T>> modRes = new ArrayList<ChangeDescription<T>>();
for (ModificationHolder<T> m : dest) {
List<ChangeDescription<T>> changes = m.modification.modify(backgroundList);
modRes.addAll(changes);
}
// Build changes
ArrayList<ChangeDescription<T>> androidChanges = null;
AppleListUpdate appleChanges = null;
if (displayList.operationMode == OperationMode.ANDROID
|| displayList.operationMode == OperationMode.GENERAL) {
androidChanges = ChangeBuilder.processAndroidModifications(modRes, initialList);
}
if (displayList.operationMode == OperationMode.IOS
|| displayList.operationMode == OperationMode.GENERAL) {
appleChanges = ChangeBuilder.processAppleModifications(modRes, initialList, dest[0].isLoadMore);
}
Object processedList = null;
if (displayList.listProcessor != null) {
processedList = displayList.listProcessor.process(backgroundList, displayList.processedList);
}
requestListSwitch(dest, initialList, androidChanges, appleChanges, dest[0].isLoadMore,
processedList);
}
@AutoreleasePool
private void requestListSwitch(final ModificationHolder<T>[] modifications,
final ArrayList<T> initialList,
final ArrayList<ChangeDescription<T>> androidChanges,
final AppleListUpdate appleChanges,
final boolean isLoadedMore,
final Object processedList) {
isLocked = true;
im.actor.runtime.Runtime.postToMainThread(new Runnable() {
@Override
public void run() {
displayList.currentList = (displayList.currentList + 1) % 2;
displayList.processedList = processedList;
if (androidChanges != null) {
for (AndroidChangeListener<T> l : displayList.androidListeners) {
l.onCollectionChanged(new AndroidListUpdate<T>(initialList, androidChanges, isLoadedMore));
}
}
if (appleChanges != null) {
for (AppleChangeListener<T> l : displayList.appleListeners) {
l.onCollectionChanged(appleChanges);
}
}
for (Listener l : displayList.listeners) {
l.onCollectionChanged();
}
for (ModificationHolder m : modifications) {
if (m.executeAfter != null) {
m.executeAfter.run();
}
}
self().send(new ListSwitched<T>(modifications));
}
});
}
@AutoreleasePool
public void onListSwitched(ModificationHolder<T>[] modifications) {
isLocked = false;
ArrayList<T> backgroundList = displayList.lists[(displayList.currentList + 1) % 2];
for (ModificationHolder m : modifications) {
m.modification.modify(backgroundList);
}
if (pending.size() > 0) {
self().send(new EditList<T>(null, null, false));
}
}
@Override
public void onReceive(Object message) {
if (message instanceof ListSwitched) {
onListSwitched(((ListSwitched<T>) message).modifications);
} else if (message instanceof EditList) {
onEditList(((EditList<T>) message).modification, ((EditList) message).executeAfter,
((EditList) message).isLoadMore);
} else {
super.onReceive(message);
}
}
}
private static class ListSwitched<T> {
private ModificationHolder<T>[] modifications;
private ListSwitched(ModificationHolder<T>[] modifications) {
this.modifications = modifications;
}
}
private static class EditList<T> {
private Modification<T> modification;
private Runnable executeAfter;
private boolean isLoadMore;
private EditList(Modification<T> modification, Runnable executeAfter, boolean isLoadMore) {
this.modification = modification;
this.executeAfter = executeAfter;
this.isLoadMore = isLoadMore;
}
}
private static class ModificationHolder<T> {
private Modification<T> modification;
private Runnable executeAfter;
private boolean isLoadMore;
private ModificationHolder(Modification<T> modification, Runnable executeAfter, boolean isLoadMore) {
this.modification = modification;
this.executeAfter = executeAfter;
this.isLoadMore = isLoadMore;
}
}
public interface Listener {
@ObjectiveCName("onCollectionChanged")
void onCollectionChanged();
}
public interface AndroidChangeListener<T> {
@ObjectiveCName("onCollectionChangedWithChanges:")
void onCollectionChanged(AndroidListUpdate<T> modification);
}
public interface AppleChangeListener<T> {
@ObjectiveCName("onCollectionChangedWithChanges:")
void onCollectionChanged(AppleListUpdate modification);
}
public enum OperationMode {
GENERAL, ANDROID, IOS
}
}