using Gee;

namespace Xmpp.Xep.JingleMessageInitiation {
    public const string NS_URI = "urn:xmpp:jingle-message:0";

    public class Module : XmppStreamModule {
        public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0353_jingle_message_initiation");

        public signal void session_proposed(Jid from, Jid to, string sid, Gee.List<StanzaNode> descriptions);
        public signal void session_retracted(Jid from, Jid to, string sid);
        public signal void session_accepted(Jid from, Jid to, string sid);
        public signal void session_rejected(Jid from, Jid to, string sid);

        public void send_session_propose_to_peer(XmppStream stream, Jid to, string sid, Gee.List<StanzaNode> descriptions) {
            StanzaNode propose_node = new StanzaNode.build("propose", NS_URI).add_self_xmlns().put_attribute("id", sid, NS_URI);
            foreach (StanzaNode desc_node in descriptions) {
                propose_node.put_node(desc_node);
            }

            MessageStanza accepted_message = new MessageStanza() { to=to, type_=MessageStanza.TYPE_CHAT };
            accepted_message.stanza.put_node(propose_node);
            stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, accepted_message);
        }

        public void send_session_retract_to_peer(XmppStream stream, Jid to, string sid) {
            send_jmi_message(stream, "retract", to, sid);
        }

        public void send_session_accept_to_self(XmppStream stream, string sid) {
            send_jmi_message(stream, "accept", Bind.Flag.get_my_jid(stream).bare_jid, sid);
        }

        public void send_session_reject_to_self(XmppStream stream, string sid) {
            send_jmi_message(stream, "reject", Bind.Flag.get_my_jid(stream).bare_jid, sid);
        }

        public void send_session_proceed_to_peer(XmppStream stream, Jid to, string sid) {
            send_jmi_message(stream, "proceed", to, sid);
        }

        public void send_session_reject_to_peer(XmppStream stream, Jid to, string sid) {
            send_jmi_message(stream, "reject", to, sid);
        }

        private void send_jmi_message(XmppStream stream, string name, Jid to, string sid) {
            MessageStanza accepted_message = new MessageStanza() { to=to, type_=MessageStanza.TYPE_CHAT };
            accepted_message.stanza.put_node(
                    new StanzaNode.build(name, NS_URI).add_self_xmlns()
                            .put_attribute("id", sid, NS_URI));
            stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, accepted_message);
        }

        private void on_received_message(XmppStream stream, MessageStanza message) {
            if (message.type_ == MessageStanza.TYPE_GROUPCHAT) return;

            Xmpp.MessageArchiveManagement.MessageFlag? mam_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
            if (mam_flag != null) return;

            StanzaNode? mi_node = null;
            foreach (StanzaNode node in message.stanza.sub_nodes) {
                if (node.ns_uri == NS_URI) {
                    mi_node = node;
                }
            }
            if (mi_node == null) return;

            switch (mi_node.name) {
                case "accept":
                case "proceed":
                    session_accepted(message.from, message.to, mi_node.get_attribute("id"));
                    break;
                case "propose":
                    ArrayList<StanzaNode> descriptions = new ArrayList<StanzaNode>();

                    foreach (StanzaNode node in mi_node.sub_nodes) {
                        if (node.name != "description") continue;
                        descriptions.add(node);
                    }

                    if (descriptions.size > 0) {
                        session_proposed(message.from, message.to, mi_node.get_attribute("id"), descriptions);
                    }
                    break;
                case "retract":
                    session_retracted(message.from, message.to, mi_node.get_attribute("id"));
                    break;
                case "reject":
                    session_rejected(message.from, message.to, mi_node.get_attribute("id"));
                    break;
            }
        }

        public override void attach(XmppStream stream) {
            stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
            stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message);
        }

        public override void detach(XmppStream stream) {
            stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
            stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message);
        }

        public override string get_ns() { return NS_URI; }
        public override string get_id() { return IDENTITY.id; }
    }
}
