using Gee;

using Xmpp;
using Dino.Entities;

namespace Dino {

public class StreamInteractor : Object {

    public signal void account_added(Account account);
    public signal void account_removed(Account account);
    public signal void stream_resumed(Account account, XmppStream stream);
    public signal void stream_negotiated(Account account, XmppStream stream);
    public signal void stream_attached_modules(Account account, XmppStream stream);

    public ModuleManager module_manager;
    public ConnectionManager connection_manager;
    private ArrayList<StreamInteractionModule> modules = new ArrayList<StreamInteractionModule>();

    public StreamInteractor(Database db) {
        module_manager = new ModuleManager();
        connection_manager = new ConnectionManager(module_manager);

        connection_manager.stream_opened.connect(on_stream_opened);
        connection_manager.stream_attached_modules.connect((account, stream) => {
            stream_attached_modules(account, stream);
        });
    }

    public Jid get_account_full_jid(Account account) {
        XmppStream stream = get_stream(account);
        if (stream != null) {
            Bind.Flag flag = stream.get_flag(Bind.Flag.IDENTITY);
            if (flag != null) {
                return flag.my_jid;
            }
        }
        return account.full_jid;
    }

    public void connect_account(Account account) {
        module_manager.initialize(account);
        account_added(account);
        connection_manager.connect_account(account);
    }

    public async void disconnect_account(Account account) {
        yield connection_manager.disconnect_account(account);
        account_removed(account);
    }

    public ArrayList<Account> get_accounts() {
        ArrayList<Account> ret = new ArrayList<Account>(Account.equals_func);
        foreach (Account account in connection_manager.get_managed_accounts()) {
            ret.add(account);
        }
        return ret;
    }

    public XmppStream? get_stream(Account account) {
        return connection_manager.get_stream(account);
    }

    public void add_module(StreamInteractionModule module) {
        modules.add(module);
    }

    public T? get_module<T>(ModuleIdentity<T>? identity) {
        if (identity == null) return null;
        foreach (StreamInteractionModule module in modules) {
            if (identity.matches(module)) return identity.cast(module);
        }
        return null;
    }

    public new T? get<T>() {
        foreach (StreamInteractionModule module in modules) {
            if (module.get_type() == typeof(T)) return (T?) module;
        }
        return null;
    }

    private void on_stream_opened(Account account, XmppStream stream) {
        stream.stream_negotiated.connect( (stream) => {
            var flag = stream.get_flag(Xep.StreamManagement.Flag.IDENTITY);
            if (flag == null || flag.resumed == false) {
                stream_negotiated(account, stream);
            } else if (flag != null && flag.resumed == true) {
                stream_resumed(account, stream);
            }
        });
    }
}

public class ModuleIdentity<T> : Object {
    public string id { get; private set; }

    public ModuleIdentity(string id) {
        this.id = id;
    }

    public T? cast(StreamInteractionModule module) {
#if VALA_0_56_11
        // We can't typecheck due to compiler bug
        return (T) module;
#else
        return module.get_type().is_a(typeof(T)) ? (T?) module : null;
#endif
    }

    public bool matches(StreamInteractionModule module) {
        return module.id == id;
    }
}

public interface StreamInteractionModule : Object {
    public abstract string id { get; }
}

}
