package org.commcare.utils;
import org.javarosa.core.model.utils.DateUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.util.Date;
/**
* @author ctsims
*/
public class DotsData {
private Date anchor;
private int[] regimens;
private DotsDay[] days;
//Labels within each regimen for what kind of dose new days should be
private final 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 enum MedStatus {
unchecked,
full,
empty,
partial
}
public enum ReportType {
direct,
pillbox,
self
}
public static final class DotsBox {
final MedStatus status;
final String missedMeds;
final ReportType type;
final int doseLabel;
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 {
final 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 (DotsBox[] dotBoxes : boxes) {
for (int j = 0; j < dotBoxes.length; ++j) {
if (dotBoxes[j].status != MedStatus.unchecked) {
return false;
}
}
}
return true;
}
public JSONArray serialize() {
JSONArray day = new JSONArray();
for (DotsBox[] dotBoxes : boxes) {
JSONArray regimen = new JSONArray();
for (int j = 0; j < dotBoxes.length; ++j) {
regimen.put(dotBoxes[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.
*/
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;
} else {
retVal[i] = -1;
}
}
}
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 (DotsBox[] dotBoxes : boxes) {
for (int j = 0; j < dotBoxes.length; ++j) {
DotsBox b = dotBoxes[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[] labels : regLabels) {
JSONArray regLabelSub = new JSONArray();
for (int j = 0; j < labels.length; ++j) {
regLabelSub.put(labels[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));
}
return new DotsData(anchor, regType, days, null);
}
private 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;
}
}