package ru.denull.wire.model;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import javax.swing.AbstractListModel;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import ru.denull.mtproto.DataService;
import ru.denull.mtproto.Server;
import ru.denull.mtproto.Server.RPCCallback;
import ru.denull.wire.MessageCellRenderer;
import ru.denull.wire.Utils;
import ru.denull.wire.model.DialogManager.EncryptedDialog;
import tl.*;
import tl.messages.GetHistory;
import tl.messages.Messages;
import tl.messages.MessagesSlice;
import tl.messages.ReadHistory;
import tl.messages.TMessages;
public class MessageListModel extends AbstractListModel {
private static final long serialVersionUID = 5885327904689475365L;
public static final int PRELOAD_FROM_CACHE_COUNT = 20;
public static final int LOAD_FROM_CACHE_COUNT = 50;
public static final int LOAD_FROM_CACHE_DISTANCE = 10;
public static final int PRELOAD_COUNT = 80;
public static final int LOAD_COUNT = 50;
public static final int LOAD_DISTANCE = 20;
private DataService service;
private JList list;
private TInputPeer peer;
private EncryptedDialog encrypted = null;
private int encryptedPos = -1;
LinkedList<Object> items = new LinkedList<Object>();
public boolean loading = false;
boolean loadingFromCache = false;
boolean initializing = true;
boolean cachedData = true;
boolean starting = true;
int total = -1;
int loaded = 0;
int first_id = 0;
boolean scrollToLast = true;
boolean updatingScroll = false;
public MessageListModel(DataService service, JList list, TInputPeer peer) {
this(service, list, peer, null);
}
public MessageListModel(DataService service, JList list, TInputPeer peer, EncryptedDialog encrypted) {
this.service = service;
this.peer = peer;
this.list = list;
this.encrypted = encrypted;
//peer_id = Utils.getPeerID(peer, service.me);
load();
loadFromCache();
}
public Object getElementAt(int index) {
if (encrypted != null && !(encrypted.chat instanceof EncryptedChat)) {
return Utils.getEncryptedChatStatus(encrypted.chat);
}
return (peer == null) ? "Выберите диалог" : ((index < items.size()) ? items.get(index) : ((loading || loadingFromCache || initializing) ? "Загрузка..." : "Нет сообщений"));
}
public int getSize() {
if (encrypted != null && !(encrypted.chat instanceof EncryptedChat)) {
return 1;
}
return Math.max(1, items.size());
}
/*public int getItemViewType(int position) {
Object item = getItem(position);
if (item instanceof Integer) { // separator
return 4;
} else {
TMessage message = (TMessage) item;
if (message instanceof MessageService) {
return 4;
}
if (message.out) {
return (message.media instanceof MessageMediaEmpty) ? 2 : 3;
} else {
return (message.media instanceof MessageMediaEmpty) ? 0 : 1;
}
}
}
public int getViewTypeCount() {
return 5; // 0: message/in, 1: attach/in, 2: message/out, 3: attach/out, 4: date separator
}
public boolean isEnabled(int position) {
return !(getItem(position) instanceof Integer);
}*/
public boolean scrolled(int firstVisible, int lastVisible) {
if (updatingScroll) return true;
if (lastVisible > -1) {
scrollToLast = (lastVisible >= getSize() - 1);
//System.out.println("firstVis: " + firstVisible + ", lastVisible: " + lastVisible + ", size: " + getSize());
}
if (loading || loadingFromCache || initializing) return true;
if (total > -1 && loaded >= total) {
return false;
}
if (cachedData) {
if (firstVisible < LOAD_FROM_CACHE_DISTANCE) {
loadFromCache();
}
} else {
if (firstVisible < LOAD_DISTANCE) {
load();
}
}
return true;
}
private void loadFromCache() {
if (!cachedData || loadingFromCache || peer == null) return;
loadingFromCache = true;
//System.out.println("Loading history from cache up to id " + first_id);
service.threadPool.submit(new Runnable() {
public void run() {
if (cachedData) { // still allowed to query data from DB
final TMessage[] data = service.messageManager.getHistory(peer, first_id, PRELOAD_FROM_CACHE_COUNT);
loadingFromCache = false;
if (cachedData) {
add(data);
if (data.length < PRELOAD_FROM_CACHE_COUNT) { // found outdated message or just end of cache
cachedData = false;
//System.out.println("Gap in cache or cache just ended, will now load from server");
}
//checkForPreload();
}
} else {
loadingFromCache = false;
}
}
});
}
private void load() {
// TODO: check first two conditions
if (service.mainServer == null || !service.mainServer.transport.isConnected() || loading || peer == null) return;
if (encrypted != null) {
try {
if (new File(Utils.getHomeDir("encrypted_chat" + encrypted.chat.id + ".dat")).exists() && encryptedPos != 0) {
FileInputStream f = new FileInputStream(Utils.getHomeDir("encrypted_chat" + encrypted.chat.id + ".dat"));
FileChannel ch = f.getChannel();
MappedByteBuffer mb = ch.map(MapMode.READ_ONLY, 0L, encryptedPos < 0 ? ch.size() : encryptedPos);
mb.order(ByteOrder.LITTLE_ENDIAN);
encryptedPos = mb.capacity();
ArrayList<TMessage> messages = new ArrayList<TMessage>();
for (int i = 0; i < 100; i++) {
if (encryptedPos < 8) break;
mb.position(encryptedPos - 8);
encryptedPos = (int) mb.getLong();
mb.position(encryptedPos);
int user_id = mb.getInt();
TEncryptedMessage encrypted = (TEncryptedMessage) TL.read(mb);
TDecryptedMessage decrypted = (TDecryptedMessage) TL.read(mb);
TMessage stub = new Message((int) (Math.random() * 10000000), 0, new PeerUser(user_id), user_id == service.me.id, true, encrypted.date, decrypted.message, new MessageMediaEmpty());
messages.add(stub);
}
add(messages.toArray(new TMessage[messages.size()]));
cachedData = false;
f.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return;
}
// prevent from loading two times at once
loading = true;
// TODO: use max_id instead of offset?
//System.out.println("Loading history up to id " + first_id);
service.mainServer.call(new GetHistory(peer, 0, first_id, (first_id == 0) ? PRELOAD_COUNT : LOAD_COUNT), new RPCCallback<TMessages>() {
public void done(final TMessages result) {
if (result.messages.length > 0 && result.messages[0].unread && !result.messages[0].out) {
for (TMessage message : result.messages) {
if (!message.out) {
message.unread = false;
}
}
service.dialogManager.resetUnread(peer);
service.mainServer.call(new ReadHistory(peer, result.messages[0].id, 0), new Server.RPCCallback<TLObject>() {
public void done(TLObject result) {
}
public void error(int code, String message) {
}
});
}
service.chatManager.store(result.chats);
service.userManager.store(result.users);
final boolean forceCached = false;// (service.messageManager.get(result.messages[result.messages.length - 1].id) != MessageManager.empty);
service.messageManager.store(result.messages);
if (result instanceof Messages) {
total = result.messages.length;
} else {
total = ((MessagesSlice) result).count;
}
/*if (cachedData) {
items.clear();
cachedData = false;
}*/
cachedData = add(result.messages); // if loaded data overlaps data from cache, we can continue loading from cache
if (cachedData) {
//System.out.println("Overlaps cached data, will now load from cache");
} else
if (forceCached) {
cachedData = true;
//System.out.println("Last loaded item is already in cache, will now load from cache");
}
cachedData = false;
loading = false;
//checkForPreload();
}
public void error(int code, String message) {
System.out.println("Error while loading messages");
}
});
}
// returns true if data was merged with already loaded from cache
public boolean add(TMessage[] messages) {
initializing = true;
//scrollToLast = (list.getLastVisibleIndex() == -1) || (list.getLastVisibleIndex() >= getSize() - 1);
//System.out.println("last: " + list.getLastVisibleIndex() + ", size: " + getSize());
final int oldSize = items.size();
final Rectangle oldRect = list.getVisibleRect();
//System.out.println(oldRect);
boolean merge = false;
LinkedList<Object> oldItems = null;
if (!items.isEmpty() && messages.length > 0 && items.getLast() instanceof TMessage) {
TMessage last = (TMessage) items.getLast();
if (last.id <= messages[0].id) {
if (last.id >= messages[messages.length - 1].id || true) { // merge (loaded and new data overlap)
merge = true;
while (!items.isEmpty()) {
Object l = items.getLast();
if (l instanceof Integer || ((TMessage) l).id >= messages[messages.length - 1].id) {
if (l instanceof TMessage) loaded--;
items.removeLast();
} else {
break;
}
}
oldItems = items;
items = new LinkedList<Object>();
} else { // drop (there's a gap between loaded and new data)
items.clear();
}
}
}
Object last = items.isEmpty() ? null : items.getFirst();
for (TMessage message : messages) {
//if (message instanceof Message || message instanceof MessageForwarded) {
if (last != null) {
if (last instanceof Integer) {
if (Utils.sameDay((Integer) last, message.date)) {
items.removeFirst();
}
} else {
if (!Utils.sameDay(((TMessage) last).date, message.date)) {
items.addFirst(((TMessage) last).date);
}
}
}
items.addFirst(message);
//if (!merge) {
first_id = message.id;
//}
last = message;
loaded++;
//}
}
if (last != null && last instanceof TMessage) {
if ((merge && !oldItems.isEmpty() && Utils.sameDay(((TMessage) last).date, ((TMessage) oldItems.getLast()).date)) || !merge) {
items.addFirst(((TMessage) last).date);
}
}
if (merge) {
oldItems.addAll(items);
items = oldItems;
}
//fireIntervalAdded(this, oldSize, items.size() - 1);
//fireIntervalAdded(this, 0, items.size());
//fireContentsChanged(this, 0, items.size());
//fireIntervalRemoved(this, 0, items.size() - 1);
//System.out.println("oldSize: " + oldSize + ", new size: " + items.size());
/*if (listView != null && resetScroll) {
listView.setSelectionFromTop(scrollPos, scrollOffs);
}*/
final int diff = (items.size() - oldSize);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updatingScroll = true;
int li = list.getLastVisibleIndex();
if (getSize() - oldSize - 1 >= 0) {
fireIntervalAdded(this, 0, getSize() - oldSize - 1);
}
if (scrollToLast) {
if (items.size() > 0) {
//list.ensureIndexIsVisible(items.size() - 1);
list.scrollRectToVisible(list.getCellBounds(items.size() - 1, items.size() - 1));
//System.out.println("scroll to last (add)");
}
} else
if (diff > 0) {
//int fi = list.getFirstVisibleIndex();
//list.ensureIndexIsVisible(li + diff);
Rectangle addedRect = list.getCellBounds(0, items.size() - oldSize - 1);
//System.out.println("addedRect: " + addedRect + ", whole: " + list.getCellBounds(0, items.size() - 1) + ", visible: " + list.getVisibleRect() + ", old: " + oldRect);
list.scrollRectToVisible(new Rectangle(oldRect.x, oldRect.y + addedRect.height, oldRect.width, oldRect.height));
//System.out.println("now visible: " + list.getVisibleRect());
//list.ensureIndexIsVisible(fi + diff);
}
updatingScroll = false;
initializing = false;
starting = false;
}
});
state++;
return merge;
}
public void addMessage(TMessage message) {
Object first = items.isEmpty() ? null : items.getLast();
if (first == null || (first instanceof TMessage && !Utils.sameDay(((TMessage) first).date, message.date))) {
items.add(message.date);
}
items.add(message);
state++;
fireIntervalAdded(this, items.size() - 1, items.size() - 1);
if (scrollToLast) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updatingScroll = true;
//list.ensureIndexIsVisible(items.size() - 1);
list.scrollRectToVisible(list.getCellBounds(getSize() - 1, getSize() - 1));
updatingScroll = false;
scrollToLast = true;
//System.out.println("scroll to last (addMessage)");
}
});
}
}
public void addMessage(TEncryptedMessage encrypted, TDecryptedMessage decrypted) {
TMessage stub = new Message((int) (Math.random() * 10000000), 0, new PeerUser(service.me.id), false, true, encrypted.date, decrypted.message, new MessageMediaEmpty());
//service.messageManager.nextMessageID++;
addMessage(stub);
}
public void updateContents() {
state++;
fireContentsChanged(this, 0, getSize() - 1);
}
public void updateContents(int index) {
fireContentsChanged(this, index, index);
}
private long state = 0;
public long getState() {
return state;
}
public void updateContentsID(int id) {
//System.out.println("upd " + id);
for (int i = getSize() - 1; i >= 0; i--) {
Object o = getElementAt(i);
if (o instanceof TMessage && ((TMessage) o).id == id) {
updateContents(i);
//System.out.println("upd " + i + ":" + id);
return;
}
}
}
public void updateContentsID(int[] messages) {
if (messages.length == 0) return;
MessageCellRenderer renderer = (MessageCellRenderer) list.getCellRenderer();
HashSet<Integer> ids = new HashSet<Integer>();
for (int id : messages) {
ids.add(id);
renderer.cache.remove(id);
}
//int st = -1, en = -1;
for (Object o : items) {
if (o instanceof TMessage && ids.contains(((TMessage) o).id)) {
((TMessage) o).unread = false;
}
}
fireContentsChanged(this, 0, getSize() - 1);
}
}