package ch.threema.domain.protocol.csp.messages.poll;

import androidx.annotation.NonNull;
import ch.threema.domain.protocol.csp.messages.BadMessageException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class PollData {
    private final static String KEY_DESCRIPTION = "d";
    private final static String KEY_STATE = "s";
    private final static String KEY_ASSESSMENT_TYPE = "a";
    private final static String KEY_TYPE = "t";
    private final static String KEY_CHOICE_TYPE = "o";
    private final static String KEY_CHOICES = "c";
    private final static String KEY_PARTICIPANTS = "p";
    private final static String KEY_DISPLAY_TYPE = "u";

    public enum State {
        OPEN(0), CLOSED(1);

        private final int value;

        State(int value) {
            this.value = value;
        }

        static State fromId(int id) {
            for (State f : values()) {
                if (f.value == id) return f;
            }
            throw new IllegalArgumentException();
        }
    }

    public enum AssessmentType {
        SINGLE(0), MULTIPLE(1);

        private final int value;

        AssessmentType(int value) {
            this.value = value;
        }

        static AssessmentType fromId(int id) {
            for (AssessmentType f : values()) {
                if (f.value == id) return f;
            }
            throw new IllegalArgumentException();
        }
    }

    public enum Type {
        RESULT_ON_CLOSE(0), INTERMEDIATE(1);

        private final int value;

        Type(int value) {
            this.value = value;
        }

        static Type fromId(int id) {
            for (Type f : values()) {
                if (f.value == id) return f;
            }
            throw new IllegalArgumentException();
        }
    }

    public enum ChoiceType {
        TEXT(0);

        private final int value;

        ChoiceType(int value) {
            this.value = value;
        }

        static ChoiceType fromId(int id) {
            for (ChoiceType f : values()) {
                if (f.value == id) return f;
            }
            throw new IllegalArgumentException();
        }
    }

    public enum DisplayType {
        LIST_MODE(0), SUMMARY_MODE(1);

        private final int value;

        DisplayType(int value) {
            this.value = value;
        }

        static DisplayType fromId(int id) {
            for (DisplayType f : values()) {
                if (f.value == id) return f;
            }
            throw new IllegalArgumentException();
        }
    }

    private String description;
    private State state;
    private AssessmentType assessmentType;
    private Type type;
    //default choice type text
    private ChoiceType choiceType = ChoiceType.TEXT;
    //default display type list mode
    private DisplayType displayType = DisplayType.LIST_MODE;

    private final List<PollDataChoice> choiceList = new ArrayList<>();
    private final List<String> participants = new ArrayList<>();

    public PollData setDescription(String description) {
        this.description = description;
        return this;
    }

    public String getDescription() {
        return this.description;
    }

    /**
     * Return a reference to the choice list.
     */
    public List<PollDataChoice> getChoiceList() {
        return this.choiceList;
    }

    /**
     * Add a choice.
     */
    public void addChoice(@NonNull PollDataChoice choice) {
        this.choiceList.add(choice);
    }

    public PollData setState(State state) {
        this.state = state;
        return this;
    }

    public State getState() {
        return this.state;
    }

    public PollData setAssessmentType(AssessmentType assessmentType) {
        this.assessmentType = assessmentType;
        return this;
    }

    public AssessmentType getAssessmentType() {
        return this.assessmentType;
    }

    public PollData setType(Type type) {
        this.type = type;
        return this;
    }

    public Type getType() {
        return this.type;
    }

    public ChoiceType getChoiceType() {
        return this.choiceType;
    }

    public PollData setChoiceType(ChoiceType choiceType) {
        this.choiceType = choiceType;
        return this;
    }

    public DisplayType getDisplayType() {
        return this.displayType;
    }

    public PollData setDisplayType(DisplayType displayType) {
        this.displayType = displayType;
        return this;
    }

    public int addParticipant(String identity) {
        for (int pos = 0; pos < this.participants.size(); pos++) {
            if (identity.equals(this.participants.get(pos))) {
                return pos;
            }
        }

        this.participants.add(identity);
        return this.participants.size() - 1;
    }

    public List<String> getParticipants() {
        return this.participants;
    }

    @NonNull
    public static PollData parse(String jsonObjectString) throws BadMessageException {
        try {
            JSONObject o = new JSONObject(jsonObjectString);

            PollData pollData = new PollData();
            pollData.description = o.getString(KEY_DESCRIPTION);
            try {
                pollData.state = State.fromId(o.getInt(KEY_STATE));
            } catch (IllegalArgumentException e) {
                throw new BadMessageException("TM030");
            }

            try {
                pollData.assessmentType = AssessmentType.fromId(o.getInt(KEY_ASSESSMENT_TYPE));
            } catch (IllegalArgumentException e) {
                throw new BadMessageException("TM031");
            }

            try {
                pollData.type = Type.fromId(o.getInt(KEY_TYPE));
            } catch (IllegalArgumentException e) {
                throw new BadMessageException("TM032");
            }
            try {
                pollData.choiceType = ChoiceType.fromId(o.getInt(KEY_CHOICE_TYPE));
            } catch (IllegalArgumentException e) {
                throw new BadMessageException("TM034");
            }
            try {
                if (o.has(KEY_DISPLAY_TYPE)) {
                    pollData.displayType = DisplayType.fromId(o.getInt(KEY_DISPLAY_TYPE));
                } else {
                    pollData.displayType = DisplayType.LIST_MODE;
                }
            } catch (IllegalArgumentException e) {
                throw new BadMessageException("TM035");
            }

            JSONArray choices = o.getJSONArray(KEY_CHOICES);
            for (int n = 0; n < choices.length(); n++) {
                pollData.getChoiceList().add(PollDataChoice.parse(choices.getJSONObject(n)));
            }

            if (o.has(KEY_PARTICIPANTS)) {
                JSONArray participants = o.getJSONArray(KEY_PARTICIPANTS);
                for (int n = 0; n < participants.length(); n++) {
                    pollData.participants.add(participants.getString(n));
                }
            }

            return pollData;
        } catch (JSONException jsonException) {
            throw new BadMessageException("TM029", jsonException);
        }
    }

    public void write(@NonNull ByteArrayOutputStream bos) throws Exception {
        bos.write(this.generateString().getBytes(StandardCharsets.UTF_8));
    }

    public String generateString() throws BadMessageException {
        JSONObject o = new JSONObject();
        try {
            o.put(KEY_DESCRIPTION, this.description);
            o.put(KEY_STATE, this.state.value);
            o.put(KEY_ASSESSMENT_TYPE, this.assessmentType.value);
            o.put(KEY_TYPE, this.type.value);
            o.put(KEY_CHOICE_TYPE, this.choiceType.value);
            o.put(KEY_DISPLAY_TYPE, this.displayType.value);

            JSONArray a = new JSONArray();
            for (PollDataChoice c : this.getChoiceList()) {
                a.put(c.getJsonObject());
            }
            o.put(KEY_CHOICES, a);

            JSONArray p = new JSONArray();
            for (String i : this.participants) {
                p.put(i);
            }
            o.put(KEY_PARTICIPANTS, p);
        } catch (Exception jsonException) {
            throw new BadMessageException("TM033", jsonException);
        }

        return o.toString();
    }
}
