package com.closedcircles.client.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
import android.os.Bundle;
import android.util.Log;
import com.closedcircles.client.WebConnectionManager;
public class Circle {
private final long mId;
private final String mName;
private final ArrayList<Message> mMessages = new ArrayList<Message>();
HashMap<Long, Integer> mMsgMap = new HashMap<Long, Integer>();
private final Map<Long, Thread> mThreads = new HashMap<Long, Thread>();
private long mCursor = -1;
public Circle(long id, String name) {
mId = id;
mName = name;
}
public Map<Long, Thread> getThreads() { return mThreads; }
public long getCursor() {
return mCursor;
}
public void setCursor(long cursor) {
mCursor = cursor;
}
public long getId() {
return mId;
}
public String getName() {
return mName;
}
@Override
public String toString() {
//return Long.toString(mId) + ": " + mName;
return "#" + mName;
}
public void clearAll() {
mThreads.clear();
mMessages.clear();
mMsgMap.clear();
mCursor = -1;
}
public int getNumMessages() {
return mMessages.size();
}
public Message getMessage(int position) {
return mMessages.get(position);
}
public boolean isUnreadExists(){
for (Circle.Thread t : mThreads.values()) {
if ( t.hasUnread() )
return true;
}
return false;
}
public void add(Message message) {
if ( mMsgMap.containsKey( Long.valueOf(message.getId())) ) {
mMessages.set( mMsgMap.get( Long.valueOf(message.getId())), message); // replace prev message
}
else {
mMessages.add(message);
mMsgMap.put( Long.valueOf(message.getId()), mMessages.size() - 1);
long tid = message.getThread();
Thread thread = mThreads.get(tid);
if (thread == null) {
thread = new Thread(tid);
mThreads.put(tid, thread);
}
thread.add(mMessages.size() - 1);
}
}
public void removeMessages(TreeSet<Integer> positions){
int add = 0;
for (Integer i: positions) {
mMessages.remove(i + add);
add--;
}
}
public void deleteMessage(long msg_id){
if ( mMsgMap.containsKey( Long.valueOf(msg_id)) ) {
Message msg = mMessages.get(mMsgMap.get( Long.valueOf(msg_id)));
msg.setText("");
mMessages.set( mMsgMap.get(Long.valueOf(msg_id)), msg); // replace prev message
}
}
public void setMarkers(Map<Long, Long> markers, long userId) {
for (Entry<Long, Long> entry: markers.entrySet()) {
if (mThreads.containsKey(entry.getKey())) {
mThreads.get(entry.getKey()).setUnread(entry.getValue(), userId);
} else {
Log.w(getClass().getName(), "Unknown thread " + entry.getKey());
}
}
}
public void addAll(Collection<? extends Message> messages) {
for (Message message: messages) {
add(message);
}
sortThreads();
removeOldThreads();
}
private void sortThreads(){
for (Entry<Long, Thread> entry: mThreads.entrySet()) {
Thread thread = entry.getValue();
thread.sortMessages();
}
}
// updates are downloaded continuously, so old messages should be removed
private void removeOldThreads(){
ArrayList<Thread> threads = new ArrayList<Thread>();
for (Entry<Long, Thread> entry: mThreads.entrySet()) {
threads.add(entry.getValue());
}
Collections.sort(threads, new Comparator<Thread>() {
@Override
public int compare(Thread t1, Thread t2) {
long id1 = Circle.this.getMessage(t1.getLast()).getId();
long id2 = Circle.this.getMessage(t2.getLast()).getId();
return (int)(id2-id1);
}
});
boolean wasRemoved = false;
TreeSet<Integer> to_remove = new TreeSet<Integer>();
for ( int i=WebConnectionManager.HISTORY_LENGTH;i<threads.size();++i){
Thread t = threads.get(i);
for ( int j=0;j<t.size();++j){
to_remove.add(t.get(j));
wasRemoved = true;
}
}
Circle.this.removeMessages(to_remove);
// refill threads one more time
// TODO: think how to avoid second pass
if ( wasRemoved ) {
Map<Long, Thread> mapThreads = new HashMap<Long, Thread>();
mapThreads.putAll(mThreads);
mThreads.clear();
mMsgMap.clear();
ArrayList<Message> messages = new ArrayList<Message>();
messages.addAll(mMessages);
mMessages.clear();
for (int i = 0; i < messages.size(); ++i)
Circle.this.add(messages.get(i));
// apply read markers
for ( Entry<Long, Thread> e: mapThreads.entrySet() ){
int firstUnread = e.getValue().getFirstUread();
if( mThreads.get(e.getKey()) != null )
mThreads.get(e.getKey()).setFirstUnread(firstUnread);
}
}
}
public Map<Long, Long> lastMessages() {
Map<Long, Long> result = new HashMap<Long, Long>();
for (Entry<Long, Thread> entry: mThreads.entrySet()) {
Thread thread = entry.getValue();
if (thread.hasUnread()) {
result.put(thread.getId(), mMessages.get(thread.getLast()).getId());
}
}
return result;
}
public int getFirstUnreadPosition() {
for (int i = 0; i < mMessages.size(); ++i) {
if (!mMessages.get(i).isRead()) {
return i;
}
}
return mMessages.size() - 1;
}
private static final String KEY_ID = "circle_id";
private static final String KEY_THREAD = "thread_id";
private static final String KEY_NAME = "circle_name";
private static final String KEY_CURSOR = "cursor";
private static final String KEY_MESSAGES_SIZE = "messages_size";
private static final String KEY_MESSAGES_PREFIX = "message-";
private static final String KEY_THREADS_SIZE = "threads_size";
private static final String KEY_THREAD_ID_PREFIX = "thread_id-";
private static final String KEY_THREAD_MARKER_PREFIX = "thread_marker-";
public void saveState(Bundle outState) {
outState.putLong(KEY_ID, mId);
outState.putString(KEY_NAME, mName);
outState.putLong(KEY_CURSOR, mCursor);
outState.putInt(KEY_MESSAGES_SIZE, mMessages.size());
for (int i = 0; i < mMessages.size(); ++i) {
Bundle message = new Bundle();
mMessages.get(i).saveState(message);
outState.putBundle(KEY_MESSAGES_PREFIX + i, message);
}
outState.putInt(KEY_THREADS_SIZE, mThreads.size());
int i = 0;
for (Entry<Long, Thread> entry: mThreads.entrySet()) {
Thread thread = entry.getValue();
outState.putLong(KEY_THREAD_ID_PREFIX + i, thread.getId());
outState.putLong(KEY_THREAD_MARKER_PREFIX + i, thread.getMarker());
i++;
}
}
public Circle(Bundle inState, long userId) {
mId = inState.getLong(KEY_ID);
mName = inState.getString(KEY_NAME);
mCursor = inState.getLong(KEY_CURSOR);
int sz = inState.getInt(KEY_MESSAGES_SIZE);
for (int i = 0; i < sz; ++i) {
Message message = new Message(inState.getBundle(KEY_MESSAGES_PREFIX + i));
add(message);
}
long tid = 0;
int num = inState.getInt(KEY_THREADS_SIZE);
for (int i = 0; i < num; i++) {
tid = inState.getLong(KEY_THREAD_ID_PREFIX + i);
long marker = inState.getLong(KEY_THREAD_MARKER_PREFIX + i);
if (mThreads.containsKey(tid)) {
mThreads.get(tid).setUnread(marker, userId);
}
}
}
public class Thread {
private final long mId;
private final List<Integer> mMessages = new ArrayList<Integer>();
private int mFirstUnread = 0;
private long mMarker = -1;
public Thread(long id) {
mId = id;
}
public long getId() {
return mId;
}
public void add(int message) {
mMessages.add(message);
}
public int get(int position) {
return mMessages.get(position);
}
public int getLast() {
return mMessages.get(mMessages.size() - 1);
}
public int size() {
return mMessages.size();
}
public int getUnreadCount(){
return size()-getFirstUread();
}
public void setUnread(long marker, long userId) {
mMarker = marker;
while (mFirstUnread < mMessages.size()) {
Message message = Circle.this.mMessages.get(mMessages.get(mFirstUnread));
if (message.getId() <= marker) {
message.doRead();
} else {
break;
}
mFirstUnread++;
}
while (mFirstUnread < mMessages.size()) {
Message message = Circle.this.mMessages.get(mMessages.get(mFirstUnread));
if (message.getUserId() == userId ) {
message.doRead();
} else {
break;
}
mFirstUnread++;
}
}
public void sortMessages(){
Collections.sort(mMessages, new Comparator<Integer>(){
@Override
public int compare(Integer a, Integer b) {
Message m1 = Circle.this.getMessage(a);
Message m2 = Circle.this.getMessage(b);
return (int)(m1.getId()-m2.getId());
};
});
}
public boolean hasUnread() {
return getUnreadCount() != 0;
}
public int getFirstUread() { return mFirstUnread;}
public void setFirstUnread(int position) { mFirstUnread = position;}
public long getMarker() {
return mMarker;
}
}
static public class MsgThread{
public String toString() { return msg; }
public String authour;
public String date;
public String msg;
public String unreadMsg;
public long thread_id;
public long msg_id;
public boolean isRead; // is message read
}
}