using Dino;
using Dino.Entities;
using Xmpp;
using Xmpp.Xep;
using Gee;
using Gtk;

namespace Dino.Ui.ConversationDetails {

    public void populate_dialog(Model.ConversationDetails model, Conversation conversation, StreamInteractor stream_interactor) {
        model.conversation = conversation;
        model.display_name = stream_interactor.get_module(ContactModels.IDENTITY).get_display_name_model(conversation);
        model.blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(model.conversation.account, model.conversation.counterpart);
        model.domain_blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(model.conversation.account, model.conversation.counterpart.domain_jid);

        if (conversation.type_ == Conversation.Type.GROUPCHAT) {
            stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => {
                model.data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res);
                if (model.data_form == null) return;
                model.data_form_bak = model.data_form.stanza_node.to_string();
            });
        }
        if (conversation.type_ == Conversation.Type.CHAT) {
            stream_interactor.get_module(EntityInfo.IDENTITY).get_utc_offset_minutes_for_bare_jid.begin(conversation.account, conversation.counterpart, (_, res) => {
                model.utc_offset_minutes = stream_interactor.get_module(EntityInfo.IDENTITY).get_utc_offset_minutes_for_bare_jid.end(res);
            });
        }
    }

    public void bind_dialog(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) {
        // Set some data once
        view_model.avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(model.conversation);
        view_model.show_blocked = model.conversation.type_ == Conversation.Type.CHAT && stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(model.conversation.account);
        view_model.members_sorted.set_model(model.members);
        view_model.members.set_map_func((item) => {
            var conference_member = (Ui.Model.ConferenceMember) item;
            Jid? nick_jid = stream_interactor.get_module(MucManager.IDENTITY).get_occupant_jid(model.conversation.account, model.conversation.counterpart, conference_member.jid);
            return new Ui.ViewModel.ConferenceMemberListRow() {
                avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(model.conversation, conference_member.jid),
                name = nick_jid != null ? nick_jid.resourcepart : conference_member.jid.localpart,
                jid = conference_member.jid.to_string(),
                affiliation = conference_member.affiliation
            };
        });
        view_model.account_jid = stream_interactor.get_accounts().size > 1 ? model.conversation.account.bare_jid.to_string() : null;

        if (model.domain_blocked) {
            view_model.blocked = DOMAIN;
        } else if (model.blocked) {
            view_model.blocked = USER;
        } else {
            view_model.blocked = UNBLOCK;
        }

        // Bind properties
        model.display_name.bind_property("display-name", view_model, "name", BindingFlags.SYNC_CREATE);
        model.conversation.bind_property("notify-setting", view_model, "notification", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
            switch (model.conversation.get_notification_setting(stream_interactor)) {
                case ON:
                    to = ViewModel.ConversationDetails.NotificationSetting.ON;
                    break;
                case OFF:
                    to = ViewModel.ConversationDetails.NotificationSetting.OFF;
                    break;
                case HIGHLIGHT:
                    to = ViewModel.ConversationDetails.NotificationSetting.HIGHLIGHT;
                    break;
                case DEFAULT:
                    // A "default" setting should have been resolved to the actual default value
                    assert_not_reached();
            }
            return true;
        });
        model.conversation.bind_property("notify-setting", view_model, "notification-is-default", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
            var notify_setting = (Conversation.NotifySetting) from;
            to = notify_setting == Conversation.NotifySetting.DEFAULT;
            return true;
        });
        model.conversation.bind_property("pinned", view_model, "pinned", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
            var from_int = (int) from;
            to = from_int > 0;
            return true;
        });
        model.conversation.bind_property("type-", view_model, "notification-options", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
            var ty = (Conversation.Type) from;
            to = ty == Conversation.Type.GROUPCHAT ? ViewModel.ConversationDetails.NotificationOptions.ON_HIGHLIGHT_OFF : ViewModel.ConversationDetails.NotificationOptions.ON_OFF;
            return true;
        });
        model.bind_property("data-form", view_model, "room-configuration-rows", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
            var data_form = (DataForms.DataForm) from;
            if (data_form == null) return true;
            var list_store = new GLib.ListStore(typeof(ViewModel.PreferencesRow.Any));

            foreach (var field in data_form.fields) {
                var field_view_model = Util.get_data_form_field_view_model(field);
                if (field_view_model != null) {
                    list_store.append(field_view_model);
                }
            }

            to = list_store;
            return true;
        });

        view_model.pin_changed.connect(() => {
            model.conversation.pinned = model.conversation.pinned == 1 ? 0 : 1;
        });
        view_model.block_changed.connect((view_model, action) => {
            switch (action) {
                case USER:
                    stream_interactor.get_module(BlockingManager.IDENTITY).block(model.conversation.account, model.conversation.counterpart);
                    stream_interactor.get_module(BlockingManager.IDENTITY).unblock(model.conversation.account, model.conversation.counterpart.domain_jid);
                    break;
                case DOMAIN:
                    stream_interactor.get_module(BlockingManager.IDENTITY).block(model.conversation.account, model.conversation.counterpart.domain_jid);
                    break;
                case UNBLOCK:
                    stream_interactor.get_module(BlockingManager.IDENTITY).unblock(model.conversation.account, model.conversation.counterpart);
                    stream_interactor.get_module(BlockingManager.IDENTITY).unblock(model.conversation.account, model.conversation.counterpart.domain_jid);
                    break;
            }
            view_model.blocked = action;
        });
        view_model.notification_changed.connect((setting) => {
            switch (setting) {
                case ON:
                    model.conversation.notify_setting = ON;
                    break;
                case OFF:
                    model.conversation.notify_setting = OFF;
                    break;
                case HIGHLIGHT:
                    model.conversation.notify_setting = HIGHLIGHT;
                    break;
                case DEFAULT:
                    model.conversation.notify_setting = DEFAULT;
                    break;
            }
        });

        view_model.notification_flipped.connect((view_model) => {
            model.conversation.notify_setting = view_model.notification == ON ? Conversation.NotifySetting.OFF : Conversation.NotifySetting.ON;
        });
    }

    private static string format_utc_offset_minutes(int utc_offset_minutes) {
        var datetime = new DateTime.now(new TimeZone.offset(utc_offset_minutes * 60));
        return datetime.format(Util.is_24h_format() ?
                /* xgettext:no-c-format */ /* Weekday and time in 24h format (w/o seconds) */ _("%A, %H∶%M") :
                /* xgettext:no-c-format */ /* Weekday and time in 12h format (w/o seconds) */ _("%A, %l∶%M %p"));
    }

    private uint get_interval_till_next_full_minute() {
        return 60000 - (int) (new DateTime.now_utc().get_seconds()*1000d);
    }

    private static void notify_binding_once(Binding binding) {
        binding.source.notify_property(binding.source_property);
    }

    public void set_about_rows(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) {
        Conversation conversation = model.conversation;
        view_model.about_rows.append(new ViewModel.PreferencesRow.Text() {
            title = _("XMPP Address"),
            text = conversation.counterpart.to_string()
        });
        if (conversation.type_ == Conversation.Type.CHAT) {
            var display_name = model.display_name;
            var about_row = new ViewModel.PreferencesRow.Entry() {
                title = _("Display name"),
                text = display_name.display_name
            };
            about_row.changed.connect((about_row) => {
                if (about_row.text != display_name.display_name) {
                    stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, about_row.text);
                }
            });
            view_model.about_rows.append(about_row);
            var time_row = new ViewModel.PreferencesRow.Text() {
                title = _("Local Time")
            };
            model.bind_property("utc-offset-minutes", time_row, "visible", BindingFlags.SYNC_CREATE, (binding, from, ref to) => {
                int utc_offset_minutes = (int) from;
                to = utc_offset_minutes != int.MIN;
                return true;
            });
            model.bind_property("utc-offset-minutes", time_row, "text", BindingFlags.SYNC_CREATE, (binding, from, ref to) => {
                var interval = get_interval_till_next_full_minute();
                to = format_utc_offset_minutes((int) from);
                WeakTimeout.add_once(interval, binding, notify_binding_once);
                return true;
            });
            view_model.about_rows.append(time_row);
        }
        if (conversation.type_ == Conversation.Type.GROUPCHAT) {
            var topic = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(conversation.counterpart, conversation.account);

            Ui.ViewModel.PreferencesRow.Any preferences_row = null;
            Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
            if (own_muc_jid != null) {
                Xep.Muc.Role? own_role = stream_interactor.get_module(MucManager.IDENTITY).get_role(own_muc_jid, conversation.account);
                if (own_role != null) {
                    if (own_role == MODERATOR) {
                        var preferences_row_entry = new ViewModel.PreferencesRow.Entry() {
                            title = _("Topic"),
                            text = topic
                        };
                        preferences_row_entry.changed.connect(() => {
                            if (preferences_row_entry.text != topic) {
                                stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, preferences_row_entry.text);
                            }
                        });
                        preferences_row = preferences_row_entry;
                    }
                }
            }
            if (preferences_row == null && topic != null && topic != "") {
                preferences_row = new ViewModel.PreferencesRow.Text() {
                    title = _("Topic"),
                    text = Util.parse_add_markup(topic, null, true, true)
                };
            }
            if (preferences_row != null) {
                view_model.about_rows.append(preferences_row);
            }
        }
    }

    public Dialog setup_dialog(Conversation conversation, StreamInteractor stream_interactor) {
        var dialog = new Dialog();
        var model = new Model.ConversationDetails();
        model.populate(stream_interactor, conversation);
        bind_dialog(model, dialog.model, stream_interactor);

        set_about_rows(model, dialog.model, stream_interactor);

        dialog.closed.connect(() => {
            // Only send the config form if something was changed
            if (model.data_form_bak != null && model.data_form_bak != model.data_form.stanza_node.to_string()) {
                stream_interactor.get_module(MucManager.IDENTITY).set_config_form.begin(conversation.account, conversation.counterpart, model.data_form);
            }
        });

        Plugins.ContactDetails contact_details = new Plugins.ContactDetails();
        var settings_rows = dialog.model.settings_rows;
        contact_details.add_settings_action_row.connect((entry_row_model) => {
            settings_rows.append((Ui.ViewModel.PreferencesRow.Any) entry_row_model);
        });
        Application app = GLib.Application.get_default() as Application;
        app.plugin_registry.register_contact_details_entry(new ContactDetails.SettingsProvider(stream_interactor));
        app.plugin_registry.register_contact_details_entry(new ContactDetails.PermissionsProvider(stream_interactor));

        foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) {
            var preferences_group = (Adw.PreferencesGroup) provider.get_widget(conversation);
            if (preferences_group != null) {
                dialog.add_encryption_tab_element((Adw.PreferencesGroup) provider.get_widget(conversation));
            }
            provider.populate(conversation, contact_details, Plugins.WidgetType.GTK4);
        }

        return dialog;
    }
}
