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