using GLib;
using Gee;

public class MessageListModel : Object, ListModel 
{
    public signal void messages_changed();

    public ArrayList<Message> messages {
        get { return _messages; }
    }

    public bool is_group_chat {
        get {
            return participants.size > 2;
        }
    }

    public Conversation conversation { get; private set; }

    private ArrayList<Message> _messages;
    private HashSet<string> participants = new HashSet<string>();
    private ulong update_handler_id = 0;
    private ulong reconnected_handler_id = 0;

    public MessageListModel(Conversation conversation) {
        _messages = new ArrayList<Message>();
        this.conversation = conversation;
    }

    ~MessageListModel() {
        // NOTE: this won't actually get destructed automatically because of a retain cycle with the signal handler. 
        // unwatch_updates() should be called explicitly when the model is no longer needed.
        unwatch_updates();
    }

    public void watch_updates() {
        if (this.update_handler_id == 0) {
            weak MessageListModel self = this;
            this.update_handler_id = Repository.get_instance().messages_updated.connect((conversation_guid) => {
                self.got_messages_updated(conversation_guid);
            });
        }

        if (this.reconnected_handler_id == 0) {
            weak MessageListModel self = this;
            this.reconnected_handler_id = Repository.get_instance().reconnected.connect(() => {
                // On reconnect, reload the messages that we're looking at now. 
                self.load_messages();
            });
        }
    }

    public void unwatch_updates() {
        if (this.update_handler_id != 0) {
            Repository.get_instance().disconnect(this.update_handler_id);
            this.update_handler_id = 0;
        }

        if (this.reconnected_handler_id != 0) {
            Repository.get_instance().disconnect(this.reconnected_handler_id);
            this.reconnected_handler_id = 0;
        }
    }

    public void load_messages(bool force_full_reload = false) {
        var previous_messages = new HashSet<Message>();
        previous_messages.add_all(_messages);

        try {
            bool first_load = _messages.size == 0;
            string last_message_id = (first_load || force_full_reload) ? "" : _messages.get(_messages.size - 1).guid;

            Message[] messages = Repository.get_instance().get_messages(conversation.guid, last_message_id);

            bool fallback_full_reload = first_load || force_full_reload;
            if (!first_load && messages.length > 0 && previous_messages.contains(messages[0])) {
                fallback_full_reload = true;
            }

            if (fallback_full_reload) {
                uint old_count = _messages.size;
                _messages.clear();
                participants.clear();

                if (old_count > 0) {
                    items_changed(0, old_count, 0);
                }

                uint position = 0;
                for (int i = 0; i < messages.length; i++) {
                    var message = messages[i];
                    participants.add(message.sender);

                    if (!first_load && !previous_messages.contains(message)) {
                        message.should_animate = true;
                    }

                    _messages.add(message);
                    position++;
                }

                if (position > 0) {
                    items_changed(0, 0, position);
                }
            } else {
                uint old_count = _messages.size;
                uint appended = 0;

                for (int i = 0; i < messages.length; i++) {
                    var message = messages[i];
                    if (previous_messages.contains(message)) {
                        continue;
                    }

                    participants.add(message.sender);
                    message.should_animate = true;
                    _messages.add(message);
                    appended++;
                }

                if (appended > 0) {
                    items_changed(old_count, 0, appended);
                }
            }
        } catch (Error e) {
            warning("Failed to load messages: %s", e.message);
        }

        messages_changed();
    }

    public void mark_as_read() {
        try {
            Repository.get_instance().mark_conversation_as_read(conversation.guid);
        } catch (Error e) {
            warning("Failed to mark conversation as read: %s", e.message);
        }
    }

    private void got_messages_updated(string conversation_guid) {
        if (conversation_guid == this.conversation.guid) {
            load_messages();
        }
    }
    
    // ListModel implementation
    public Type get_item_type() {
        return typeof(Message);
    }
    
    public uint get_n_items() {
        return _messages.size;
    }
    
    public Object? get_item(uint position) {
        return _messages.get((int)position);
    }
}
