/** * */ package org.commcare.android.util; import java.util.Date; import org.javarosa.core.model.utils.DateUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; /** * @author ctsims * */ public class DotsData { Date anchor; int[] regimens; DotsDay[] days; //Labels within each regimen for what kind of dose new days should be int[][] regLabels; private static final int[][] equivM = new int[][] { new int[] {0,-1,-1,-1}, new int[] {0,-1,1,-1}, new int[] {0,1, 2,-1}, new int[] {0,1,2, 3} }; public static enum MedStatus { unchecked, full, empty, partial; } public static enum ReportType { direct, pillbox, self } public static final class DotsBox { MedStatus status; String missedMeds; ReportType type; int doseLabel; public DotsBox(MedStatus status, ReportType type) { this(status, type, null, -1); } public DotsBox(MedStatus status, ReportType type, String missedMeds, int doselabel) { this.status = status; this.missedMeds = missedMeds; this.type = type; this.doseLabel = doselabel; } public MedStatus status() { return status; } public String missedMeds() { return missedMeds; } public ReportType reportType() { return type; } public int getDoseLabel() { return doseLabel; } public static DotsBox deserialize(String box){ try { return DotsBox.deserialize(new JSONArray(new JSONTokener(box))); }catch (JSONException e) { throw new RuntimeException(e); } } public static DotsBox deserialize(JSONArray box) throws JSONException { String missed = null; if(box.length() > 2) { missed= box.getString(2); } int label = -1; if(box.length() > 3) { label= box.getInt(3); } String status = box.getString(0); String type = box.getString(1); return new DotsBox(MedStatus.valueOf(status), ReportType.valueOf(type), missed, label); } public JSONArray serialize() { JSONArray ser = new JSONArray(); ser.put(status.toString()); ser.put(type.toString()); if(missedMeds != null){ ser.put(missedMeds); } else { ser.put(""); } ser.put(doseLabel); return ser; } public DotsBox update(DotsBox deserialize) { return new DotsBox(deserialize.status,deserialize.type, deserialize.missedMeds == null? this.missedMeds : deserialize.missedMeds, deserialize.doseLabel == -1 ? this.doseLabel : deserialize.doseLabel); } } public static final class DotsDay { DotsBox[][] boxes; public DotsDay(DotsBox[][] boxes) { this.boxes = boxes; } public DotsBox[][] boxes() { return boxes; } /** * Whether this box contains the default observations for a * new day object. * * @return True if the day is indistinguishable from a default day, * false otherwise */ public boolean isDefault() { for(int i = 0; i < boxes.length ; ++i) { for(int j = 0; j < boxes[i].length; ++j) { if(boxes[i][j].status != MedStatus.unchecked) { return false; } } } return true; } public JSONArray serialize() { JSONArray day = new JSONArray(); for(int i = 0; i < boxes.length ; ++i) { JSONArray regimen = new JSONArray(); for(int j = 0; j < boxes[i].length ; ++j) { regimen.put(boxes[i][j].serialize()); } day.put(regimen); } return day; } public static DotsDay deserialize(String day) { try { return deserialize(new JSONArray(new JSONTokener(day))); } catch(JSONException e) { throw new RuntimeException(e); } } public static DotsDay deserialize(JSONArray day) throws JSONException { DotsBox[][] fullDay = new DotsBox[day.length()][]; for(int i = 0 ; i < day.length() ; ++i) { JSONArray regimen = day.getJSONArray(i); DotsBox[] boxes = new DotsBox[regimen.length()]; for(int j = 0 ; j < regimen.length() ; ++j) { boxes[j] = DotsBox.deserialize(regimen.getJSONArray(j)); } fullDay[i] = boxes; } return new DotsDay(fullDay); } public int getMaxReg() { return 4; // int max = 0; // for(DotsBox[] reg : boxes) { // if(reg.length > max) { // max = reg.length; // } // } // return max; } /** * Takes in a index between 0 and 3 representing a potential dose, and * returns either an index between 0 and 3 representing the actual dose * window (AM, Noon, etc) represented by the box at that index. * * @param regimenIndex the index of a potential dose (AM,noon,pm, etc) * which may be occurring. * * @return */ public int[] getRegIndexes(int regimenIndex) { int max = this.getMaxReg(); int[] retVal = new int[boxes.length]; //ART v. non-ART regimen: for(int i = 0 ; i < boxes.length ; ++i) { if(boxes[i].length == 0) { retVal[i] = -1; } else { //See if there's an explicitly labeled index for //this regimen for(int j = 0 ; j < boxes[i].length; ++j) { if(boxes[i][j].doseLabel == regimenIndex) { retVal[i] = j; continue regimen; } } //otherwise, grab what the default one should be int defaultIndex = equivM[boxes[i].length -1 ][regimenIndex]; //Make sure the default index is either unused (-1), or if not, isn't //actually pointing to something already if(defaultIndex == -1 || boxes[i][defaultIndex].doseLabel == -1) { retVal[i] = defaultIndex; continue; } else { retVal[i] = -1; continue; } } } return retVal; } public DotsDay updateDose(int dose, DotsBox[] newboxes) { int[] indices = getRegIndexes(dose); for(int i = 0 ; i < boxes.length; ++i) { if(indices[i] == -1) { //Nothing to do } else { this.boxes[i][indices[i]] = newboxes[i]; } } return this; } public MedStatus status() { MedStatus ret = null; for(int i = 0 ; i < boxes.length; ++i) { for(int j = 0 ; j < boxes[i].length; ++j) { DotsBox b = boxes[i][j]; if(ret == null) { if(b.status != MedStatus.unchecked) { ret = MedStatus.empty; } else { ret = MedStatus.unchecked; } } else { if(ret == MedStatus.unchecked) { if(b.status != MedStatus.unchecked) { return MedStatus.partial; } } else if(ret == MedStatus.empty) { if(b.status == MedStatus.unchecked) { return MedStatus.partial; } } } } } if(ret == null) { return MedStatus.unchecked; } else { return ret; } } } public DotsDay[] days() { return days; } public Date anchor() { return anchor; } private DotsData(Date anchor, int[] regimens, DotsDay[] days, int[][] regLabels) { this.anchor = anchor; this.regimens = regimens; this.days = days; this.regLabels = regLabels; } public int recenter(int[] regimens, Date newAnchor) { this.regimens = regimens; int difference = DateUtils.dateDiff(anchor, newAnchor); if(difference == 0) { return 0; } DotsDay[] newDays = new DotsDay[this.days.length]; for(int i = 0; i < newDays.length; ++i) { if(difference + i >= 0 && difference + i < this.days.length) { newDays[i] = this.days[difference + i]; } else { newDays[i] = new DotsDay(emptyBoxes(this.regimens, this.regLabels)); } } this.anchor = newAnchor; this.days = newDays; return difference; } public String SerializeDotsData() { try{ JSONObject object = new JSONObject(); object.put("anchor", anchor.toGMTString()); JSONArray jRegs = new JSONArray(); for(int i : regimens) { jRegs.put(i); } object.put("regimens", jRegs); JSONArray jDays = new JSONArray(); for(DotsDay day : days) { jDays.put(day.serialize()); } if(this.regLabels != null) { JSONArray regLabelJson = new JSONArray(); for(int i = 0; i < regLabels.length ; ++i) { JSONArray regLabelSub = new JSONArray(); for(int j = 0 ; j < regLabels[i].length; ++j) { regLabelSub.put(regLabels[i][j]); } regLabelJson.put(regLabelSub); } object.put("regimen_labels", regLabelJson); } object.put("days", jDays); return object.toString(); } catch(JSONException e) { throw new RuntimeException(e); } } public static DotsData DeserializeDotsData(String dots) { try { JSONObject data = new JSONObject(new JSONTokener(dots)); Date anchor = new Date(Date.parse(data.getString("anchor"))); JSONArray jRegs = data.getJSONArray("regimens"); int[] regs = new int[jRegs.length()]; for(int i = 0 ; i < regs.length ; ++i) { regs[i] = jRegs.getInt(i); } int[][] regLabels = new int[regs.length][]; if(data.has("regimen_labels")) { JSONArray jRegLabels = data.getJSONArray("regimen_labels"); if(jRegLabels.length() != regs.length) { //TODO: specific exception type here throw new RuntimeException("Invalid DOTS model! Regimens and Labels are incompatible lengths"); } for(int i = 0 ; i < jRegLabels.length() ; ++i) { JSONArray jLabels = jRegLabels.getJSONArray(i); regLabels[i] = new int[jLabels.length()]; for(int j = 0 ; j < jLabels.length() ; ++j) { regLabels[i][j] = jLabels.getInt(j); } } } else { //No default regimen labels regLabels = null; } JSONArray jDays = data.getJSONArray("days"); DotsDay[] days = new DotsDay[jDays.length()]; for(int i = 0 ; i < days.length ; ++i) { days[i] = DotsDay.deserialize(jDays.getJSONArray(i)); } return new DotsData(anchor, regs, days, regLabels); } catch(JSONException e) { throw new RuntimeException(e); } } public static DotsData CreateDotsData(int[] regType, Date anchor) { DotsDay[] days = new DotsDay[21]; for(int j = 0 ; j < days.length ; ++j ) { days[j] = new DotsDay(emptyBoxes(regType, null)); } DotsData data = new DotsData(anchor, regType, days, null); return data; } public static DotsBox[][] emptyBoxes(int[] lengths, int[][] regLabels) { DotsBox[][] boxes = new DotsBox[lengths.length][]; for(int i = 0 ; i < lengths.length; ++i ) { boxes[i] = new DotsBox[lengths[i]]; for(int j = 0 ; j < boxes[i].length ; ++j ) { int label = -1; if(regLabels != null) { label = regLabels[i][j]; } boxes[i][j] = new DotsBox(MedStatus.unchecked, ReportType.pillbox, null, label); } } return boxes; } }