at.jclehner.rxdroid.db.Entries.java Source code

Java tutorial

Introduction

Here is the source code for at.jclehner.rxdroid.db.Entries.java

Source

/**
 * RxDroid - A Medication Reminder
 * Copyright (C) 2011-2014 Joseph Lehner <joseph.c.lehner@gmail.com>
 *
 *
 * RxDroid is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version. Additional terms apply (see LICENSE).
 *
 * RxDroid is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with RxDroid.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

package at.jclehner.rxdroid.db;

import android.util.Log;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import at.jclehner.rxdroid.Fraction;
import at.jclehner.rxdroid.Fraction.MutableFraction;
import at.jclehner.rxdroid.R;
import at.jclehner.rxdroid.RxDroid;
import at.jclehner.rxdroid.Settings;
import at.jclehner.rxdroid.util.Constants;
import at.jclehner.rxdroid.util.DateTime;
import at.jclehner.rxdroid.util.Util;
import at.jclehner.rxdroid.util.WrappedCheckedException;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;

import org.joda.time.Days;
import org.joda.time.LocalDate;

public final class Entries {
    @SuppressWarnings("unused")
    private static final String TAG = Entries.class.getSimpleName();

    private static final Dao<DoseEvent, Integer> sDoseEventDao = Database.USE_CUSTOM_CACHE ? null
            : Database.getHelper().getDaoChecked(DoseEvent.class);

    private static final String[] TIME_NAMES = { "MORNING", "NOON", "EVENING", "NIGHT" };

    public static List<Drug> getAllDrugs(int patientId) {
        final List<Drug> list = new ArrayList<Drug>();

        for (Drug drug : Database.getCached(Drug.class)) {
            final boolean matches;

            if (patientId == 0)
                matches = (drug.getPatient() == null || drug.getPatient().isDefaultPatient());
            else
                matches = (patientId == drug.getPatientId());

            if (matches)
                list.add(drug);
        }

        return list;
    }

    public static CharSequence[] getAllPatientNames() {
        final List<Patient> patients = Database.getCached(Patient.class);
        final String[] names = new String[patients.size()];

        for (int i = 0; i != names.length; ++i)
            names[i] = patients.get(i).getName();

        return names;
    }

    public static boolean hasMissingDosesBeforeDate(Drug drug, Date date) {
        if (!drug.isActive())
            return false;

        final Date lastScheduleUpdateDate = drug.getLastScheduleUpdateDate();
        if (lastScheduleUpdateDate != null && date.before(lastScheduleUpdateDate))
            return false;

        final int repeatMode = drug.getRepeatMode();
        /*switch(repeatMode)
        {
           case Drug.REPEAT_EVERY_N_DAYS:
           case Drug.REPEAT_WEEKDAYS:
           {
        if(drug.hasDoseOnDate(date))
           return false;
           }
        }*/

        if (repeatMode == Drug.REPEAT_EVERY_N_DAYS) {
            long days = drug.getRepeatArg();

            final Date origin = drug.getRepeatOrigin();
            if (date.before(origin))
                return false;

            long elapsedDays = (date.getTime() - origin.getTime()) / Constants.MILLIS_PER_DAY;

            int offset = (int) -(elapsedDays % days);
            if (offset == 0)
                offset = (int) -days;

            final Date lastIntakeDate = DateTime.add(date, Calendar.DAY_OF_MONTH, offset);
            if (lastScheduleUpdateDate != null && lastIntakeDate.compareTo(drug.getLastScheduleUpdateDate()) == -1)
                return false;

            //if(!isDateAfterLastScheduleUpdateOfDrug(lastIntakeDate, drug))
            //   return false;

            return !hasAllDoseEvents(drug, lastIntakeDate);
        } else if (repeatMode == Drug.REPEAT_WEEKDAYS) {
            // FIXME this may fail to report a missed intake if
            // the dose was taken on a later date (i.e. unscheduled)
            // in the week before.

            int expectedIntakeCount = 0;
            int actualScheduledIntakeCount = 0;

            for (int i = 0; i != 7; ++i) {
                final Date checkDate = DateTime.add(date, Calendar.DAY_OF_MONTH, -7 + i);
                if (!isDateAfterLastScheduleUpdateOfDrug(checkDate, drug))
                    continue;

                for (int doseTime : Constants.DOSE_TIMES) {
                    if (!drug.getDose(doseTime, checkDate).isZero()) {
                        ++expectedIntakeCount;
                        actualScheduledIntakeCount += countDoseEvents(drug, checkDate, doseTime);
                    }
                }
            }

            return actualScheduledIntakeCount < expectedIntakeCount;
        }

        return false;
    }

    /**
     * Get the number of days the drug's supply will last.
     */
    public static int getSupplyDaysLeftForDrug(Drug drug, Date date) {
        // TODO this function currently does not take into account doses
        // that were taken after the specified date.

        if (date == null)
            date = DateTime.today();

        final MutableFraction doseLeftOnDate = new MutableFraction();

        if (date.equals(DateTime.today()) && drug.hasDoseOnDate(date)) {
            for (int doseTime : Constants.DOSE_TIMES) {
                if (countDoseEvents(drug, date, doseTime) == 0)
                    doseLeftOnDate.add(drug.getDose(doseTime, date));
            }
        }

        final double supply = drug.getCurrentSupply().doubleValue() - doseLeftOnDate.doubleValue();
        return (int) (Math.floor(supply / getDailyDose(drug) * getSupplyCorrectionFactor(drug)));
    }

    public static <T extends Entry> T findInCollectionById(Collection<T> collection, int id) {
        for (T t : collection) {
            if (t.getId() == id)
                return t;
        }

        return null;
    }

    /**
     * Find all events meeting the specified criteria.
     * <p>
     * @param drug The drug to search for (based on its database ID).
     * @param date The intake's date. Can be <code>null</code>.
     * @param doseTime The intake's doseTime. Can be <code>null</code>.
     */
    public static List<DoseEvent> findDoseEvents(Drug drug, Date date, Integer doseTime) {
        if (Database.USE_CUSTOM_CACHE) {
            final List<DoseEvent> events = new LinkedList<DoseEvent>();

            for (DoseEvent intake : Database.getCached(DoseEvent.class)) {
                if (DoseEvent.has(intake, drug, date, doseTime))
                    events.add(intake);
            }

            return events;
        } else {
            try {
                final QueryBuilder<DoseEvent, Integer> qb = sDoseEventDao.queryBuilder();
                final Where<DoseEvent, Integer> where = qb.where();

                if (drug != null)
                    where.eq("drug_id", drug.id).and();

                if (date != null)
                    where.eq("date", date).and();

                if (doseTime != null)
                    where.eq("doseTime", doseTime);

                return sDoseEventDao.query(qb.prepare());
            } catch (SQLException e) {
                throw new WrappedCheckedException(e);
            }
        }
    }

    public static int countDoseEvents(Drug drug, Date date, Integer doseTime) {
        return findDoseEvents(drug, date, doseTime).size();
    }

    public static boolean hasAllDoseEvents(Drug drug, Date date) {
        return hasAllDoseEvents(drug, date, Schedule.TIME_INVALID);
    }

    public static boolean hasAllDoseEvents(Drug drug, Date date, int untilDoseTime) {
        return hasAllDoseEvents(drug, date, untilDoseTime, true);
    }

    public static boolean hasAllDoseEvents(Drug drug, Date date, int untilDoseTime,
            boolean inclusiveUntilDoseTime) {
        final int lastDoseTime = inclusiveUntilDoseTime ? untilDoseTime : untilDoseTime - 1;
        if (lastDoseTime < Schedule.TIME_MORNING)
            return true;

        if (!drug.hasDoseOnDate(date))
            return true;

        for (int doseTime : Constants.DOSE_TIMES) {
            Fraction dose = drug.getDose(doseTime, date);
            if (!dose.isZero()) {
                if (countDoseEvents(drug, date, doseTime) == 0)
                    return false;
            }

            if (doseTime == lastDoseTime)
                break;
        }

        return true;
    }

    public static String getDoseTimeString(int doseTime) {
        return TIME_NAMES[doseTime];
    }

    public static Fraction getTotalDoseInTimePeriod_dumb(Drug drug, Date begin, Date end,
            boolean stopIfSupplyIsEmpty) {
        final MutableFraction totalDose = new MutableFraction();

        final Calendar cal = DateTime.calendarFromDate(begin);
        cal.add(Calendar.DAY_OF_MONTH, 1);
        Date date;

        while ((date = cal.getTime()).before(end) || date.equals(end)) {
            getTotalDose(drug, date, totalDose);

            if (totalDose.isNegative() && stopIfSupplyIsEmpty)
                return drug.getCurrentSupply();

            cal.add(Calendar.DAY_OF_MONTH, 1);
        }

        return totalDose;
    }

    public static Fraction getTotalDoseInTimePeriod_smart(Drug drug, Date begin, Date end) {
        final int repeatMode = drug.getRepeatMode();
        final MutableFraction baseDose = new MutableFraction();
        int doseMultiplier = 0;

        if (drug.isAsNeeded())
            return Fraction.ZERO;

        if (repeatMode == Drug.REPEAT_DAILY) {
            getTotalDose(drug, null, baseDose);
            doseMultiplier = (int) DateTime.diffDays(begin, end);
        } else if (repeatMode == Drug.REPEAT_EVERY_N_DAYS) {
            getTotalDose(drug, null, baseDose);

            final long arg = drug.getRepeatArg();
            final long daysInPeriod = DateTime.diffDays(begin, end);

            if (drug.hasDoseOnDate(begin) || drug.hasDoseOnDate(end))
                doseMultiplier = 1;

            doseMultiplier += daysInPeriod / arg;
        } else if (repeatMode == Drug.REPEAT_WEEKDAYS) {
            getTotalDose(drug, null, new MutableFraction());

            final Calendar cal = DateTime.calendarFromDate(begin);
            int weekDay;

            // Manually check the days before the first Monday
            while ((weekDay = cal.get(Calendar.DAY_OF_WEEK)) != Calendar.MONDAY) {
                if (drug.hasDoseOnWeekday(weekDay))
                    ++doseMultiplier;

                cal.add(Calendar.DAY_OF_WEEK, 1);
            }

            final Date firstMonday = cal.getTime();

            // Now check the days after the last Sunday
            cal.setTime(end);

            while ((weekDay = cal.get(Calendar.DAY_OF_WEEK)) != Calendar.MONDAY) {
                if (drug.hasDoseOnWeekday(weekDay))
                    ++doseMultiplier;

                cal.add(Calendar.DAY_OF_WEEK, -1);
            }

            final Date lastSunday = cal.getTime();

            // Now comes the easy part: dealing with the full week(s) in between
            final long days = DateTime.diffDays(firstMonday, lastSunday);
            if (days > 7) {
                if (days % 7 != 0)
                    throw new IllegalStateException("Not a full week: " + firstMonday + " - " + lastSunday);

                doseMultiplier += days / 7 * Long.bitCount(drug.getRepeatArg());
            }
        } else if (repeatMode == Drug.REPEAT_21_7) {
            getTotalDose(drug, null, baseDose);

            final Date origin = drug.getRepeatOrigin();

            long daysInTimePeriod = DateTime.diffDays(begin, end);
            final long daysFromOriginToBegin = DateTime.diffDays(origin, begin);
            final long daysFromOriginToEnd = DateTime.diffDays(origin, end);

            long index = daysFromOriginToBegin % 28;
            if (index < 21) {
                final long days = 21 - index;
                daysInTimePeriod -= days + 7;
                doseMultiplier += days;
            }

            index = daysFromOriginToEnd % 28;
            if (index < 21) {
                final long days = 21 - index;
                daysInTimePeriod -= days + 7;
                doseMultiplier += days;
            }

            if (daysInTimePeriod > 28)
                doseMultiplier += (daysInTimePeriod * 3) / 4;
        } else
            throw new UnsupportedOperationException();

        return baseDose.times(doseMultiplier);
    }

    public static boolean isDateAfterLastScheduleUpdateOfDrug(Date date, Drug drug) {
        final Date lastScheduleUpdateDate = drug.getLastScheduleUpdateDate();
        if (lastScheduleUpdateDate == null)
            return true;

        return date.after(lastScheduleUpdateDate);
    }

    public static boolean hasLowSupplies(Drug drug, Date date) {
        if (!drug.isActive() || drug.getRefillSize() == 0 || drug.hasNoDoses())
            return false;

        final int minSupplyDays = Settings.getStringAsInt(Settings.Keys.LOW_SUPPLY_THRESHOLD, 10);
        if (minSupplyDays == 0)
            return false;

        final int supplyDaysLeft = getSupplyDaysLeftForDrug(drug, date);

        final LocalDate scheduleEnd = drug.getScheduleEndDate();
        if (scheduleEnd != null) {
            if (supplyDaysLeft >= Days.daysBetween(scheduleEnd, LocalDate.fromDateFields(date)).getDays())
                return false;
        }

        return getSupplyDaysLeftForDrug(drug, date) < minSupplyDays;
    }

    public static LocalDate getSupplyEndDate(Drug drug, Date date) {
        return LocalDate.fromDateFields(date).plusDays(getSupplyDaysLeftForDrug(drug, date));
    }

    public static boolean willExpireSoon(Drug drug, Date date) {
        if (!drug.isActive() || drug.getRefillSize() == 0)
            return false;

        final LocalDate expirationDate = drug.getExpiryDate();
        if (expirationDate == null)
            return false;

        final LocalDate scheduleEnd = drug.getScheduleEndDate();
        if (scheduleEnd != null && expirationDate.isAfter(scheduleEnd))
            return false;

        final int minSupplyDays = Settings.getStringAsInt(Settings.Keys.LOW_SUPPLY_THRESHOLD, 10);
        if (minSupplyDays == 0)
            return false;

        return LocalDate.fromDateFields(date).plusDays(minSupplyDays).isAfter(expirationDate);
    }

    public static String getDrugName(Drug drug) {
        final String name = drug.getName();
        // this should never happen unless there's a DB problem
        if (name == null || name.length() == 0)
            return "<???>";

        if (Settings.getBoolean(Settings.Keys.SCRAMBLE_NAMES, false)) {
            // We rot13 word by word and ignore those beginning with
            // a digit, so things like 10mg won't get converted to 10zt.

            final StringBuilder sb = new StringBuilder(name.length());
            final String[] tokens = name.split(" ");

            for (int i = 0; i != tokens.length; ++i) {
                if (i != 0)
                    sb.append(" ");

                final String token = tokens[i];

                if (token.length() == 0 || Character.isDigit(token.charAt(0)))
                    sb.append(token);
                else
                    sb.append(Util.rot13(token));
            }

            return sb.toString();
        }

        return name;
    }

    public static void markAllNotifiedDosesAsTaken(int patientId) {
        final Settings.DoseTimeInfo dtInfo = Settings.getDoseTimeInfo();

        final boolean isActiveDoseTime;

        Date date = dtInfo.activeDate();
        int doseTime = dtInfo.activeDoseTime();

        if (doseTime == Schedule.TIME_INVALID) {
            isActiveDoseTime = false;
            doseTime = dtInfo.nextDoseTime();
            date = dtInfo.nextDoseTimeDate();
        } else
            isActiveDoseTime = true;

        final List<Drug> drugs = getAllDrugs(patientId);
        final List<DoseEvent> events = new ArrayList<DoseEvent>();

        if (isActiveDoseTime)
            getDrugsWithDueDoses(drugs, date, doseTime, events);

        getDrugsWithMissedDoses(drugs, date, doseTime, isActiveDoseTime, events);

        int skipped = 0, taken = 0;

        for (DoseEvent event : events) {
            final boolean skip;

            final Drug drug = event.getDrug();
            final Fraction dose = event.getDose();

            final Fraction supply = drug.getCurrentSupply();
            if (!supply.isZero()) {
                final Fraction newSupply = supply.minus(dose);
                if (!newSupply.isNegative()) {
                    drug.setCurrentSupply(newSupply);
                    Database.update(drug);
                    skip = false;
                } else
                    skip = true;
            } else
                skip = drug.getRefillSize() != 0;

            if (skip) {
                ++skipped;
                event.setDose(Fraction.ZERO);
            } else
                ++taken;

            Database.create(event);

            Log.d(TAG, "Creating event: " + event);
        }

        if (skipped != 0)
            RxDroid.toastLong(R.string._toast_some_doses_skipped);
        else if (taken != 0)
            RxDroid.toastLong(R.string._toast_all_doses_taken);
        else
            RxDroid.toastShort(R.string._toast_no_doses_to_take);
    }

    public static int getDrugsWithDueDoses(List<Drug> inDrugs, Date date, int doseTime, List<DoseEvent> outEvents) {
        int count = 0;

        for (Drug drug : inDrugs) {
            final Fraction dose = drug.getDose(doseTime, date);

            if (!drug.isActive() || dose.isZero() || drug.hasAutoDoseEvents() || drug.isAsNeeded())
                continue;

            if (Entries.countDoseEvents(drug, date, doseTime) == 0) {
                ++count;

                if (outEvents != null)
                    outEvents.add(new DoseEvent(drug, date, doseTime, dose));
            }
        }

        return count;
    }

    public static int getDrugsWithMissedDoses(List<Drug> inDrugs, Date date, int activeOrNextDoseTime,
            boolean isActiveDoseTime, List<DoseEvent> outEvents) {
        int begin = Schedule.TIME_MORNING;
        int end = Schedule.TIME_INVALID;

        if (!isActiveDoseTime && activeOrNextDoseTime == Drug.TIME_MORNING) {
            // FIXME fails if we're between end of TIME_NIGHT and midnight!
            date = DateTime.add(date, Calendar.DAY_OF_MONTH, -1);
            begin = Drug.TIME_NIGHT;
        } else
            end = activeOrNextDoseTime;

        int count = 0;

        for (int doseTime = begin; doseTime != end; ++doseTime)
            count += getDrugsWithDueDoses(inDrugs, date, doseTime, outEvents);

        return count;
    }

    private static void getTotalDose(Drug drug, Date date, MutableFraction outTotalDose) {
        if ((date != null && !drug.hasDoseOnDate(date)) || drug.isAsNeeded())
            return;

        for (int doseTime : Constants.DOSE_TIMES) {
            final Fraction dose;

            if (date == null)
                dose = drug.getDose(doseTime);
            else
                dose = drug.getDose(doseTime, date);

            outTotalDose.add(dose);
        }

        return;
    }

    private static double getDailyDose(Drug drug) {
        double dailyDose = 0.0;
        for (int doseTime : Constants.DOSE_TIMES)
            dailyDose += drug.getDose(doseTime).doubleValue();
        return dailyDose;
    }

    private static double getSupplyCorrectionFactor(Drug drug) {
        switch (drug.getRepeatMode()) {
        case Drug.REPEAT_EVERY_N_DAYS:
            return drug.getRepeatArg();

        case Drug.REPEAT_WEEKDAYS:
            return 7.0 / Long.bitCount(drug.getRepeatArg());

        case Drug.REPEAT_21_7:
            return 1.0 / 0.75;

        default:
            return 1.0;
        }
    }

    private Entries() {
    }
}