1package im.conversations.android.xmpp.model.data;
  2
  3import android.util.Log;
  4import com.google.common.base.CaseFormat;
  5import com.google.common.collect.Collections2;
  6import com.google.common.collect.Iterables;
  7import eu.siacs.conversations.Config;
  8import im.conversations.android.annotation.XmlElement;
  9import im.conversations.android.xmpp.model.Extension;
 10import java.util.Collection;
 11import java.util.Map;
 12
 13@XmlElement(name = "x")
 14public class Data extends Extension {
 15
 16    private static final String FORM_TYPE = "FORM_TYPE";
 17    private static final String FIELD_TYPE_HIDDEN = "hidden";
 18    private static final String FORM_TYPE_SUBMIT = "submit";
 19
 20    public Data() {
 21        super(Data.class);
 22    }
 23
 24    public String getFormType() {
 25        final var fields = this.getExtensions(Field.class);
 26        final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName()));
 27        return Iterables.getFirst(formTypeField.getValues(), null);
 28    }
 29
 30    public Collection<Field> getFields() {
 31        return Collections2.filter(
 32                this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
 33    }
 34
 35    public Field getFieldByName(final String name) {
 36        return Iterables.find(getFields(), f -> name.equals(f.getFieldName()), null);
 37    }
 38
 39    public String getValue(final String name) {
 40        final var field = getFieldByName(name);
 41        return field == null ? null : field.getValue();
 42    }
 43
 44    private void addField(final String name, final Object value) {
 45        addField(name, value, null);
 46    }
 47
 48    private void addField(final String name, final Object value, final String type) {
 49        if (value == null) {
 50            throw new IllegalArgumentException("Null values are not supported on data fields");
 51        }
 52        final var field = this.addExtension(new Field());
 53        field.setFieldName(name);
 54        if (type != null) {
 55            field.setType(type);
 56        }
 57        if (value instanceof Collection<?> collection) {
 58            Log.d(Config.LOGTAG, "submitting collection: " + collection);
 59            for (final Object subValue : collection) {
 60                if (subValue == null) {
 61                    Log.d(Config.LOGTAG, "null value in the values for " + name);
 62                } else if (subValue instanceof String s) {
 63                    final var valueExtension = field.addExtension(new Value());
 64                    valueExtension.setContent(s);
 65                } else {
 66                    throw new IllegalArgumentException(
 67                            String.format(
 68                                    "%s is not a supported field value",
 69                                    subValue.getClass().getSimpleName()));
 70                }
 71            }
 72        } else {
 73            final var valueExtension = field.addExtension(new Value());
 74            if (value instanceof String s) {
 75                valueExtension.setContent(s);
 76            } else if (value instanceof Enum<?> e) {
 77                valueExtension.setContent(
 78                        CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, e.toString()));
 79            } else if (value instanceof Integer i) {
 80                valueExtension.setContent(String.valueOf(i));
 81            } else if (value instanceof Boolean b) {
 82                valueExtension.setContent(Boolean.TRUE.equals(b) ? "1" : "0");
 83            } else {
 84                throw new IllegalArgumentException(
 85                        String.format(
 86                                "%s is not a supported field value",
 87                                value.getClass().getSimpleName()));
 88            }
 89        }
 90    }
 91
 92    private void setFormType(final String formType) {
 93        this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN);
 94    }
 95
 96    public static Data of(final String formType, final Map<String, Object> values) {
 97        final var data = new Data();
 98        data.setType(FORM_TYPE_SUBMIT);
 99        data.setFormType(formType);
100        for (final Map.Entry<String, Object> entry : values.entrySet()) {
101            data.addField(entry.getKey(), entry.getValue());
102        }
103        return data;
104    }
105
106    public Data submit(final Map<String, Object> values) {
107        final String formType = this.getFormType();
108        final var submit = new Data();
109        submit.setType(FORM_TYPE_SUBMIT);
110        if (formType != null) {
111            submit.setFormType(formType);
112        }
113        for (final Field existingField : this.getFields()) {
114            final var fieldName = existingField.getFieldName();
115            final Object submittedValue = values.get(fieldName);
116            if (submittedValue != null) {
117                Log.d(Config.LOGTAG, "submitting value " + fieldName + ": " + submittedValue);
118                submit.addField(fieldName, submittedValue);
119            } else {
120                Log.d(Config.LOGTAG, "staying with default for: " + fieldName);
121                submit.addExtension(existingField);
122            }
123        }
124        return submit;
125    }
126
127    private void setType(final String type) {
128        this.setAttribute("type", type);
129    }
130}