com.marand.thinkmed.medications.process.impl.TherapyTaskCreatorImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.marand.thinkmed.medications.process.impl.TherapyTaskCreatorImpl.java

Source

/*
 * Copyright (c) 2010-2014 Marand d.o.o. (www.marand.com)
 *
 * This file is part of Think!Med Clinical Medication Management.
 *
 * Think!Med Clinical Medication Management is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Think!Med Clinical Medication Management 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Think!Med Clinical Medication Management.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.marand.thinkmed.medications.process.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.base.Preconditions;
import com.marand.maf.core.Pair;
import com.marand.maf.core.data.object.HourMinuteDto;
import com.marand.maf.core.daterule.service.MafDateRuleService;
import com.marand.maf.core.openehr.util.DvUtils;
import com.marand.maf.core.openehr.util.InstructionTranslator;
import com.marand.maf.core.time.DayType;
import com.marand.openehr.medications.tdo.AdministrationDetailsCluster;
import com.marand.openehr.medications.tdo.IngredientsAndFormCluster;
import com.marand.openehr.medications.tdo.MedicationOrderComposition;
import com.marand.openehr.medications.tdo.StructuredDoseCluster;
import com.marand.openehr.util.DataValueUtils;
import com.marand.openehr.util.OpenEhrLinkType;
import com.marand.thinkmed.medications.AdministrationTypeEnum;
import com.marand.thinkmed.medications.DosingFrequencyTypeEnum;
import com.marand.thinkmed.medications.MedicationDeliveryMethodEnum;
import com.marand.thinkmed.medications.TherapyDoseTypeEnum;
import com.marand.thinkmed.medications.business.MedicationsBo;
import com.marand.thinkmed.medications.business.impl.MedicationsEhrUtils;
import com.marand.thinkmed.medications.converter.MedicationFromEhrConverter;
import com.marand.thinkmed.medications.dto.AdministrationTimingDto;
import com.marand.thinkmed.medications.dto.DosingFrequencyDto;
import com.marand.thinkmed.medications.dto.RoundsIntervalDto;
import com.marand.thinkmed.medications.dto.TherapyDoseDto;
import com.marand.thinkmed.medications.process.TherapyTaskCreator;
import com.marand.thinkmed.medications.process.task.MedicationTaskDef;
import com.marand.thinkmed.process.definition.TaskVariable;
import com.marand.thinkmed.process.dto.NewTaskRequestDto;
import com.marand.thinkmed.process.dto.TaskDto;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Minutes;
import org.joda.time.format.ISODateTimeFormat;
import org.openehr.jaxb.rm.DvCodedText;
import org.openehr.jaxb.rm.DvDate;
import org.openehr.jaxb.rm.DvQuantity;
import org.openehr.jaxb.rm.DvTime;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import static com.marand.openehr.medications.tdo.IngredientsAndFormCluster.IngredientCluster.IngredientQuantityCluster;
import static com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.MedicationInstructionInstruction;
import static com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.MedicationInstructionInstruction.OrderActivity;
import static com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.MedicationInstructionInstruction.OrderActivity.MedicationTimingCluster;
import static com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.MedicationInstructionInstruction.OrderActivity.MedicationTimingCluster.TimingCluster;
import static com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.MedicationInstructionInstruction.OrderActivity.MedicationTimingCluster.TimingCluster.DayOfWeek;

/**
 * @author Mitja Lapajne
 */
public class TherapyTaskCreatorImpl implements TherapyTaskCreator, InitializingBean {
    private MafDateRuleService mafDateRuleService;
    private MedicationsBo medicationsBo;

    public void setMafDateRuleService(final MafDateRuleService mafDateRuleService) {
        this.mafDateRuleService = mafDateRuleService;
    }

    public void setMedicationsBo(final MedicationsBo medicationsBo) {
        this.medicationsBo = medicationsBo;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(mafDateRuleService, "mafDateRuleService is required");
        Assert.notNull(medicationsBo, "medicationsBo is required");
    }

    @Override
    public List<NewTaskRequestDto> createTasks(final Long patientId,
            final Pair<MedicationOrderComposition, MedicationInstructionInstruction> therapyInstructionPair,
            final AdministrationTimingDto administrationTiming, final RoundsIntervalDto roundsInterval,
            final DateTime actionTimestamp, final boolean therapyStart, final DateTime lastTaskTimestamp) {
        final MedicationOrderComposition composition = therapyInstructionPair.getFirst();
        final MedicationInstructionInstruction instruction = therapyInstructionPair.getSecond();

        if (!MedicationsEhrUtils.isContinuousInfusion(therapyInstructionPair.getSecond())
                && MedicationsEhrUtils.isWhenNeeded(therapyInstructionPair.getSecond())) {
            return new ArrayList<>();
        }

        final List<Interval> tasksCreationIntervals = calculateAdministrationTasksInterval(roundsInterval,
                instruction, actionTimestamp, therapyStart);

        return createTasks(patientId, composition, instruction, administrationTiming, roundsInterval,
                tasksCreationIntervals, therapyStart, lastTaskTimestamp);
    }

    private List<NewTaskRequestDto> createTasks(final Long patientId, final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction, final AdministrationTimingDto administrationTiming,
            final RoundsIntervalDto roundsInterval, final List<Interval> tasksCreationIntervals,
            final boolean therapyStart, final DateTime lastTaskTimestamp) {
        final boolean simple = MedicationsEhrUtils.isSimpleInstruction(instruction);
        final boolean variable = MedicationsEhrUtils.isVariableInstruction(instruction);
        final boolean variableDays = MedicationsEhrUtils.isVariableDaysInstruction(instruction);
        String linkedTherapyId = null; //last task triggers task creation for linked therapy

        final Pair<MedicationOrderComposition, MedicationInstructionInstruction> instructionPair = Pair
                .of(composition, instruction);
        for (final MedicationInstructionInstruction inst : composition.getMedicationDetail()
                .getMedicationInstruction()) {
            if (medicationsBo.doesInstructionHaveLinkToCompareInstruction(inst, instructionPair,
                    OpenEhrLinkType.ISSUE)) {
                linkedTherapyId = InstructionTranslator.translate(inst, composition);
                break;
            }
        }

        final List<NewTaskRequestDto> taskRequests = new ArrayList<>();
        for (final Interval interval : tasksCreationIntervals) {
            if (variableDays) {
                final Map<DateTime, TherapyDoseDto> administrationDateTimesMap = new HashMap<>();
                for (final OrderActivity orderActivity : instruction.getOrder()) {
                    final TherapyDoseDto dose = getTherapyDoseDto(orderActivity, simple);
                    if (dose != null) {
                        final DvTime dvTime = orderActivity.getMedicationTiming().getTiming().getTime().get(0);
                        final DvDate dvDate = orderActivity.getMedicationTiming().getTiming().getDate().get(0);
                        final Pair<Integer, Integer> parsedTime = DvUtils.getHourMinute(dvTime);
                        final DateTime parsedDate = ISODateTimeFormat.date().parseDateTime(dvDate.getValue());

                        administrationDateTimesMap.put(
                                new DateTime(parsedDate.getYear(), parsedDate.getMonthOfYear(),
                                        parsedDate.getDayOfMonth(), parsedTime.getFirst(), parsedTime.getSecond()),
                                dose);
                    }
                }
                final List<NewTaskRequestDto> tasks = createTasksForVariableDaysTherapy(patientId, instruction,
                        composition.getUid().getValue(), interval, administrationDateTimesMap, therapyStart);
                taskRequests.addAll(tasks);
            } else if (variable) {
                final Map<HourMinuteDto, TherapyDoseDto> administrationTimesMap = new HashMap<>();
                for (final OrderActivity orderActivity : instruction.getOrder()) {
                    final TherapyDoseDto dose = getTherapyDoseDto(orderActivity, simple);
                    final Pair<Integer, Integer> parsedTime = DvUtils
                            .getHourMinute(orderActivity.getMedicationTiming().getTiming().getTime().get(0));
                    administrationTimesMap.put(new HourMinuteDto(parsedTime.getFirst(), parsedTime.getSecond()),
                            dose);
                }
                final List<NewTaskRequestDto> tasks = createTasksForVariableTherapy(patientId, instruction,
                        composition.getUid().getValue(), interval, administrationTimesMap, therapyStart);
                taskRequests.addAll(tasks);
            } else {
                final TherapyDoseDto dose = getTherapyDoseDto(instruction.getOrder().get(0), simple);
                final List<NewTaskRequestDto> tasks = createTasksForConstantTherapy(patientId, instruction,
                        composition.getUid().getValue(), interval, dose, administrationTiming, roundsInterval,
                        therapyStart, lastTaskTimestamp, linkedTherapyId);
                taskRequests.addAll(tasks);
            }
        }
        return taskRequests;
    }

    private List<NewTaskRequestDto> createTasksForConstantTherapy(final long patientId,
            final MedicationInstructionInstruction instruction, final String compositionUid,
            final Interval taskCreationInterval, final TherapyDoseDto dose,
            final AdministrationTimingDto administrationTiming, final RoundsIntervalDto roundsIntervalDto,
            final boolean therapyStart, final DateTime lastTaskTimestamp, final String linkedTherapyId) {
        final DateTime therapyEnd = DataValueUtils
                .getDateTime(instruction.getOrder().get(0).getMedicationTiming().getStopDate());
        final boolean therapyEndsWithLastTask = taskCreationInterval.getEnd().equals(therapyEnd);
        final DosingFrequencyDto dosingFrequency = MedicationFromEhrConverter
                .getDosingFrequency(instruction.getOrder().get(0).getMedicationTiming());

        final List<NewTaskRequestDto> taskRequests = new ArrayList<>();

        final DateTime calculationBaseTimestamp = therapyStart ? taskCreationInterval.getStart()
                : lastTaskTimestamp;
        if (therapyStart) {
            final NewTaskRequestDto startTaskRequest = createMedicationTaskRequest(instruction, compositionUid,
                    patientId, AdministrationTypeEnum.START, taskCreationInterval.getStart(), dose);
            taskRequests.add(startTaskRequest);
        }
        if (dosingFrequency != null && dosingFrequency.getType() != DosingFrequencyTypeEnum.ONCE_THEN_EX) {
            List<HourMinuteDto> defaultAdministrationTimes = getPossibleAdministrations(administrationTiming,
                    dosingFrequency.getKey());
            if (defaultAdministrationTimes == null
                    && dosingFrequency.getType() == DosingFrequencyTypeEnum.DAILY_COUNT) {
                defaultAdministrationTimes = calculatePossibleAdministrationsForDailyCount(
                        dosingFrequency.getValue(), roundsIntervalDto, calculationBaseTimestamp);
            }
            final DateTime nextTime = getNextAdministrationTime(calculationBaseTimestamp,
                    defaultAdministrationTimes, dosingFrequency);

            DateTime time = calculationBaseTimestamp;

            if (therapyStart && dosingFrequency.getType() != DosingFrequencyTypeEnum.BETWEEN_DOSES
                    && Minutes.minutesBetween(time, nextTime).getMinutes() < 30) {
                time = getNextAdministrationTime(nextTime, defaultAdministrationTimes, dosingFrequency);
            } else {
                time = nextTime;
            }

            while (!time.isAfter(taskCreationInterval.getEnd())) {
                if (time.isAfter(taskCreationInterval.getStart())) {
                    taskRequests.add(createMedicationTaskRequest(instruction, compositionUid, patientId,
                            AdministrationTypeEnum.START, time, dose));
                }
                time = getNextAdministrationTime(time, defaultAdministrationTimes, dosingFrequency);
            }
        } else if (MedicationsEhrUtils.isContinuousInfusion(instruction)) {
            if (therapyEnd != null && !therapyEnd.isAfter(taskCreationInterval.getEnd())) {
                final NewTaskRequestDto endTaskRequest = createMedicationTaskRequest(instruction, compositionUid,
                        patientId, AdministrationTypeEnum.STOP, therapyEnd, dose);
                taskRequests.add(endTaskRequest);
            }
        }

        if (therapyEndsWithLastTask) {
            final NewTaskRequestDto lastTask = taskRequests.get(taskRequests.size() - 1);
            lastTask.getVariables().add(Pair.of(MedicationTaskDef.TRIGGERS_THERAPY_ID, linkedTherapyId));
        }
        return taskRequests;
    }

    private List<NewTaskRequestDto> createTasksForVariableTherapy(final long patientId,
            final MedicationInstructionInstruction instruction, final String compositionUid,
            final Interval taskCreationInterval, final Map<HourMinuteDto, TherapyDoseDto> administrationTimesMap,
            final boolean therapyStart) {
        final List<NewTaskRequestDto> taskRequests = new ArrayList<>();

        Pair<DateTime, TherapyDoseDto> timeWithDose = getNextAdministrationTimeWithDose(
                therapyStart ? taskCreationInterval.getStart().minusMinutes(30) : taskCreationInterval.getStart(),
                administrationTimesMap);
        if (therapyStart) {
            final NewTaskRequestDto startTaskRequest = createMedicationTaskRequest(instruction, compositionUid,
                    patientId, AdministrationTypeEnum.START, taskCreationInterval.getStart(),
                    timeWithDose.getSecond());
            taskRequests.add(startTaskRequest);
            if (Minutes.minutesBetween(taskCreationInterval.getStart(), timeWithDose.getFirst())
                    .getMinutes() < 30) {
                timeWithDose = getNextAdministrationTimeWithDose(taskCreationInterval.getStart(),
                        administrationTimesMap);
            }
        }
        final boolean isContinuousInfusion = MedicationsEhrUtils.isContinuousInfusion(instruction);
        if (therapyStart || !isContinuousInfusion) //don't create tasks on review if continuous infusion
        {
            while (!timeWithDose.getFirst().isAfter(taskCreationInterval.getEnd())) {
                if (timeWithDose.getFirst().isAfter(taskCreationInterval.getStart())) {
                    final NewTaskRequestDto medicationTaskRequest = createMedicationTaskRequest(instruction,
                            compositionUid, patientId,
                            isContinuousInfusion ? AdministrationTypeEnum.ADJUST_INFUSION
                                    : AdministrationTypeEnum.START,
                            timeWithDose.getFirst(), timeWithDose.getSecond());
                    taskRequests.add(medicationTaskRequest);
                }
                timeWithDose = getNextAdministrationTimeWithDose(timeWithDose.getFirst(), administrationTimesMap);

                if (MedicationsEhrUtils.isContinuousInfusion(instruction) && !timeWithDose.getFirst()
                        .withTimeAtStartOfDay().equals(taskCreationInterval.getStart().withTimeAtStartOfDay())) {
                    break;
                }
            }
        }

        if (MedicationsEhrUtils.isContinuousInfusion(instruction)) {
            final DateTime therapyEnd = DataValueUtils
                    .getDateTime(instruction.getOrder().get(0).getMedicationTiming().getStopDate());
            if (therapyEnd != null && !therapyEnd.isAfter(taskCreationInterval.getEnd())) {
                final NewTaskRequestDto endTaskRequest = createMedicationTaskRequest(instruction, compositionUid,
                        patientId, AdministrationTypeEnum.STOP, therapyEnd, timeWithDose.getSecond());
                taskRequests.add(endTaskRequest);
            }
        }
        return taskRequests;
    }

    private List<NewTaskRequestDto> createTasksForVariableDaysTherapy( //TODO Mitja
            final long patientId, final MedicationInstructionInstruction instruction, final String compositionUid,
            final Interval taskCreationInterval, final Map<DateTime, TherapyDoseDto> administrationDateTimesMap,
            final boolean therapyStart) {
        final List<NewTaskRequestDto> taskRequests = new ArrayList<>();

        for (final DateTime administrationTime : administrationDateTimesMap.keySet()) {
            final NewTaskRequestDto medicationTaskRequest = createMedicationTaskRequest(instruction, compositionUid,
                    patientId, AdministrationTypeEnum.START, administrationTime,
                    administrationDateTimesMap.get(administrationTime));
            taskRequests.add(medicationTaskRequest);
        }
        return taskRequests;
    }

    @Override
    public TherapyDoseDto getTherapyDoseDto(final OrderActivity orderActivity, final boolean simple) {
        final StructuredDoseCluster structuredDose = orderActivity.getStructuredDose();
        if (simple) {
            return getTherapyDoseForSimple(structuredDose);
        }

        //complex therapy
        final AdministrationDetailsCluster administrationDetails = orderActivity.getAdministrationDetails();

        final List<IngredientsAndFormCluster.IngredientCluster> ingredients = orderActivity.getIngredientsAndForm()
                .getIngredient();

        final AdministrationDetailsCluster.InfusionAdministrationDetailsCluster infusionDetails = administrationDetails
                .getInfusionAdministrationDetails().isEmpty() ? null
                        : administrationDetails.getInfusionAdministrationDetails().get(0);
        final boolean infusionHasSpeedDefined = infusionDetails != null
                && (infusionDetails.getDoseAdministrationRate() instanceof DvQuantity
                        || infusionDetails.getDoseAdministrationFormula() instanceof DvQuantity);
        final boolean isContinuousInfusion = MedicationDeliveryMethodEnum.CONTINUOUS_INFUSION
                .isEqualTo(administrationDetails.getDeliveryMethod());

        if (isContinuousInfusion || infusionHasSpeedDefined) {
            return setTherapyDoseRateForComplex(infusionDetails);
        }
        if (ingredients.size() > 1) {
            return getTherapyDoseVolumeSumForComplex(structuredDose);
        }
        if (!ingredients.isEmpty()) {
            return getTherapyDoseQuantityForComplex(ingredients.get(0).getIngredientQuantity());
        }

        return new TherapyDoseDto();
    }

    private TherapyDoseDto getTherapyDoseForSimple(final StructuredDoseCluster structuredDose) {
        if (structuredDose == null) {
            return null;
        }
        final TherapyDoseDto therapyDoseDto = new TherapyDoseDto();
        if (structuredDose.getQuantity() != null) {
            therapyDoseDto.setNumerator(structuredDose.getQuantity().getMagnitude());
            therapyDoseDto.setNumeratorUnit(structuredDose.getDoseUnit().getDefiningCode().getCodeString());
        } else if (structuredDose.getRatioNumerator() != null) {
            therapyDoseDto.setNumerator(structuredDose.getRatioNumerator().getAmount().getMagnitude());
            therapyDoseDto.setNumeratorUnit(
                    structuredDose.getRatioNumerator().getDoseUnit().getDefiningCode().getCodeString());
            therapyDoseDto.setDenominator(structuredDose.getRatioDenominator().getAmount().getMagnitude());
            therapyDoseDto.setDenominatorUnit(
                    structuredDose.getRatioDenominator().getDoseUnit().getDefiningCode().getCodeString());
        }
        therapyDoseDto.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.QUANTITY);
        return therapyDoseDto;
    }

    private TherapyDoseDto getTherapyDoseVolumeSumForComplex(final StructuredDoseCluster structuredDose) {
        final TherapyDoseDto therapyDoseDto = new TherapyDoseDto();
        therapyDoseDto.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.VOLUME_SUM);
        therapyDoseDto.setNumerator(structuredDose.getQuantity().getMagnitude());
        therapyDoseDto.setNumeratorUnit(DvUtils.getString(structuredDose.getDoseUnit()));
        return therapyDoseDto;
    }

    private TherapyDoseDto getTherapyDoseQuantityForComplex(final IngredientQuantityCluster ingredientQuantity) {
        final TherapyDoseDto therapyDoseDto = new TherapyDoseDto();
        therapyDoseDto.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.QUANTITY);
        if (ingredientQuantity.getQuantity() != null) {
            therapyDoseDto.setNumerator(ingredientQuantity.getQuantity().getMagnitude());
            therapyDoseDto.setNumeratorUnit(ingredientQuantity.getDoseUnit().getDefiningCode().getCodeString());
        } else {
            if (ingredientQuantity.getRatioNumerator() != null) {
                therapyDoseDto.setNumerator(ingredientQuantity.getRatioNumerator().getAmount().getMagnitude());
                therapyDoseDto.setNumeratorUnit(
                        ingredientQuantity.getRatioNumerator().getDoseUnit().getDefiningCode().getCodeString());
            }
            if (ingredientQuantity.getRatioDenominator() != null) {
                therapyDoseDto.setDenominator(ingredientQuantity.getRatioDenominator().getAmount().getMagnitude());
                therapyDoseDto.setDenominatorUnit(
                        ingredientQuantity.getRatioDenominator().getDoseUnit().getDefiningCode().getCodeString());
            }
        }
        return therapyDoseDto;
    }

    private TherapyDoseDto setTherapyDoseRateForComplex(
            final AdministrationDetailsCluster.InfusionAdministrationDetailsCluster infusionDetails) {
        final TherapyDoseDto therapyDoseDto = new TherapyDoseDto();
        therapyDoseDto.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.RATE);

        if (infusionDetails.getDoseAdministrationRate() instanceof DvQuantity) //using numerator for rate
        {
            final DvQuantity administrationRateQuantity = (DvQuantity) infusionDetails.getDoseAdministrationRate();
            therapyDoseDto.setNumerator(administrationRateQuantity.getMagnitude());
            therapyDoseDto.setNumeratorUnit(administrationRateQuantity.getUnits());
        }
        if (infusionDetails.getDoseAdministrationFormula() instanceof DvQuantity) //using denominator for formula
        {
            final DvQuantity administrationRateFormula = (DvQuantity) infusionDetails
                    .getDoseAdministrationFormula();
            therapyDoseDto.setDenominator(administrationRateFormula.getMagnitude());
            therapyDoseDto.setDenominatorUnit(administrationRateFormula.getUnits());
        }
        return therapyDoseDto;
    }

    List<Interval> calculateAdministrationTasksInterval(final RoundsIntervalDto roundsInterval,
            final MedicationInstructionInstruction instruction, final DateTime actionTimestamp,
            final boolean therapyStart) {
        final MedicationTimingCluster medicationTiming = instruction.getOrder().get(0).getMedicationTiming();
        final TimingCluster timing = medicationTiming.getTiming();
        final DateTime therapyStartTime = DataValueUtils.getDateTime(medicationTiming.getStartDate());
        final DateTime therapyEndTime = DataValueUtils.getDateTime(medicationTiming.getStopDate());

        final DateTime end = getAdministrationTasksIntervalEnd(
                !therapyStart || actionTimestamp.isBefore(therapyStartTime) ? actionTimestamp : therapyStartTime,
                therapyEndTime, roundsInterval);

        return removeInactiveTherapyDaysFromTasksInterval(therapyStart ? therapyStartTime : actionTimestamp, end,
                roundsInterval, timing);
    }

    private DateTime getAdministrationTasksIntervalEnd(final DateTime start, final DateTime end,
            final RoundsIntervalDto roundsInterval) {
        final DateTime startOfTodaysRounds = start.withTimeAtStartOfDay().plusHours(roundsInterval.getStartHour())
                .plusMinutes(roundsInterval.getStartMinute());

        DateTime endTimestamp = start.withTimeAtStartOfDay().plusHours(roundsInterval.getEndHour())
                .plusMinutes(roundsInterval.getEndMinute());

        if (start.isAfter(startOfTodaysRounds) || start.equals(startOfTodaysRounds)) {
            endTimestamp = endTimestamp.plusDays(1);
        }

        boolean workingDay = mafDateRuleService.isDateOfType(endTimestamp.withTimeAtStartOfDay(),
                DayType.WORKING_DAY);

        while (!workingDay) {
            endTimestamp = endTimestamp.plusDays(1);
            workingDay = mafDateRuleService.isDateOfType(endTimestamp.withTimeAtStartOfDay(), DayType.WORKING_DAY);
        }
        if (end != null && endTimestamp.isAfter(end)) {
            endTimestamp = end;
        }
        return endTimestamp;
    }

    private List<Interval> removeInactiveTherapyDaysFromTasksInterval(final DateTime start, final DateTime end,
            final RoundsIntervalDto roundsInterval, final TimingCluster timing) {
        final List<Interval> intervals = new ArrayList<>();
        DateTime tasksStart = new DateTime(start);

        final DateTime startOfTodaysRounds = start.withTimeAtStartOfDay().plusHours(roundsInterval.getStartHour())
                .plusMinutes(roundsInterval.getStartMinute());

        DateTime tasksEnd = start.withTimeAtStartOfDay().plusHours(roundsInterval.getEndHour())
                .plusMinutes(roundsInterval.getEndMinute());

        if (start.isAfter(startOfTodaysRounds) && tasksEnd.isBefore(end)) {
            if (tasksEnd.plusDays(1).isAfter(end)) {
                tasksEnd = end;
            } else {
                tasksEnd = tasksEnd.plusDays(1);
            }
        }
        int daysFrequency = 1;
        if (timing != null && timing.getInterval() != null) {
            final int days = DataValueUtils.getPeriod(timing.getInterval()).getDays();
            if (days > 0) {
                daysFrequency = days;
            }
        }

        boolean previousDayWasValid = isInValidDaysOfWeek(tasksStart, timing);
        if (!previousDayWasValid) {
            tasksStart = startOfTodaysRounds.plusDays(1);
        }
        int dayIndex = 1;
        while (tasksEnd.isBefore(end) || tasksEnd.equals(end)) {
            final boolean validDayOfWeek = isInValidDaysOfWeek(tasksEnd, timing);
            final boolean validFrequency = dayIndex % daysFrequency == 0;
            if (validDayOfWeek && validFrequency) {
                previousDayWasValid = true;
            } else {
                final DateTime startOfRounds = tasksEnd.withTimeAtStartOfDay()
                        .plusHours(roundsInterval.getStartHour()).plusMinutes(roundsInterval.getStartMinute());
                if (previousDayWasValid) {
                    intervals.add(new Interval(tasksStart, startOfRounds));
                }
                previousDayWasValid = false;
                tasksStart = startOfRounds.plusDays(1);
            }
            tasksEnd = tasksEnd.plusDays(1);
            dayIndex++;
        }
        if (previousDayWasValid && dayIndex > 1 || tasksEnd.minusDays(1).isBefore(end)) {
            if (!tasksStart.isAfter(end)) {
                intervals.add(new Interval(tasksStart, end));
            }
        }
        return intervals;
    }

    private boolean isInValidDaysOfWeek(final DateTime dateTime, final TimingCluster timing) {
        if (timing == null || timing.getDayOfWeek().size() == 7 || timing.getDayOfWeek().isEmpty()) {
            return true;
        }
        final DayOfWeek dayOfWeekEnum = MedicationsEhrUtils.dayOfWeekToEhrEnum(dateTime.withTimeAtStartOfDay());
        for (final DvCodedText validDay : timing.getDayOfWeek()) {
            final DayOfWeek validDayEnum = DataValueUtils.getTerminologyEnum(DayOfWeek.class, validDay);
            if (dayOfWeekEnum == validDayEnum) {
                return true;
            }
        }
        return false;
    }

    private DateTime getNextAdministrationTime(final DateTime fromTime,
            final List<HourMinuteDto> possibleAdministrations, final DosingFrequencyDto dosingFrequency) {
        if (dosingFrequency.getType() == DosingFrequencyTypeEnum.ONCE_THEN_EX) {
            return fromTime;
        }
        if (dosingFrequency.getType() == DosingFrequencyTypeEnum.BETWEEN_DOSES) {
            final Integer betweenDoses = dosingFrequency.getValue();
            return fromTime.plusHours(betweenDoses);
        }
        if (!possibleAdministrations.isEmpty()) {
            return getNextAdministrationTime(fromTime, possibleAdministrations);
        }
        throw new IllegalArgumentException("Cannot calculate next administration timestamp for dosingFrequency: "
                + dosingFrequency + " and possibleAdministrations size: " + possibleAdministrations.size());
    }

    private DateTime getNextAdministrationTime(final DateTime fromTime,
            final List<HourMinuteDto> possibleAdministrations) {
        int index = 0;
        DateTime foundTime = combine(fromTime, possibleAdministrations.get(index));
        while (!foundTime.isAfter(fromTime)) {
            index++;
            if (index >= possibleAdministrations.size()) {
                index = 0;
                foundTime = foundTime.plusDays(1);
            }
            foundTime = combine(foundTime, possibleAdministrations.get(index));
        }
        return foundTime;
    }

    private Pair<DateTime, TherapyDoseDto> getNextAdministrationTimeWithDose(final DateTime fromTime,
            final Map<HourMinuteDto, TherapyDoseDto> administrationTimesWithDoses) {
        int index = 0;
        final List<HourMinuteDto> times = new ArrayList<>(administrationTimesWithDoses.keySet());
        Collections.sort(times);
        DateTime foundTime = combine(fromTime, times.get(index));
        TherapyDoseDto therapyDoseDto = administrationTimesWithDoses.get(times.get(0));
        while (!foundTime.isAfter(fromTime)) {
            index++;
            if (index >= administrationTimesWithDoses.size()) {
                index = 0;
                foundTime = foundTime.plusDays(1);
            }
            final HourMinuteDto hourMinute = times.get(index);
            foundTime = combine(foundTime, hourMinute);
            therapyDoseDto = administrationTimesWithDoses.get(hourMinute);
        }
        return Pair.of(foundTime, therapyDoseDto);
    }

    private List<HourMinuteDto> getPossibleAdministrations(final AdministrationTimingDto administrationTiming,
            final String frequency) {
        Preconditions.checkNotNull(administrationTiming);
        Preconditions.checkNotNull(frequency);

        for (final AdministrationTimingDto.AdministrationTimestampsDto administrationTimestamps : administrationTiming
                .getTimestampsList()) {
            if (frequency.equals(administrationTimestamps.getFrequency())) {
                return administrationTimestamps.getTimesList();
            }
        }
        return null;
    }

    private DateTime combine(final DateTime fromTime, final HourMinuteDto hourMinuteDto) {
        return new DateTime(fromTime.getYear(), fromTime.getMonthOfYear(), fromTime.getDayOfMonth(),
                hourMinuteDto.getHour(), hourMinuteDto.getMinute());
    }

    private List<HourMinuteDto> calculatePossibleAdministrationsForDailyCount(final int dailyCount,
            final RoundsIntervalDto roundsInterval, final DateTime start) {
        final List<HourMinuteDto> administrationTimes = new ArrayList<>();
        if (dailyCount == 1) {
            administrationTimes.add(new HourMinuteDto(start.getHourOfDay(), start.getMinuteOfHour()));
        } else {
            double startMinutes = roundsInterval.getStartHour() * 60 + roundsInterval.getStartMinute();
            final double endMinutes = 24 * 60; //midnight

            final double minutesBetweenAdministrations = (endMinutes - startMinutes) / (dailyCount - 1);
            while (startMinutes <= endMinutes) {
                if (endMinutes - startMinutes < 0.1) //midnight (startMinutes equals endMinutes with precision 0.1)
                {
                    administrationTimes.add(0, new HourMinuteDto(0, 0));
                } else {
                    final long roundsInHours = Math.round(startMinutes / 60);
                    administrationTimes.add(new HourMinuteDto((int) roundsInHours, 0));
                }

                startMinutes += minutesBetweenAdministrations;
            }
        }

        return administrationTimes;
    }

    @Override
    public NewTaskRequestDto createMedicationTaskRequest(final MedicationInstructionInstruction instruction,
            final String compositionUid, final long patientId, final AdministrationTypeEnum administrationTypeEnum,
            final DateTime timestamp, final TherapyDoseDto dose) {
        final String therapyId = InstructionTranslator.translate(instruction, compositionUid);

        return new NewTaskRequestDto(MedicationTaskDef.INSTANCE,
                instruction.getOrder().get(0).getMedicine().getValue(),
                instruction.getOrder().get(0).getMedicine().getValue(),
                instruction.getOrder().get(0).getMedicine().getValue(), "sessionId", //TODO THERAPY_TASK
                timestamp, null, Pair.of(MedicationTaskDef.PATIENT_ID, patientId),
                Pair.of(MedicationTaskDef.THERAPY_ID, therapyId),
                Pair.of(MedicationTaskDef.ADMINISTRATION_TYPE, administrationTypeEnum.name()),
                Pair.of(MedicationTaskDef.DOSE_TYPE, dose != null ? dose.getTherapyDoseTypeEnum().name() : null),
                Pair.of(MedicationTaskDef.DOSE_NUMERATOR, dose != null ? dose.getNumerator() : null),
                Pair.of(MedicationTaskDef.DOSE_NUMERATOR_UNIT, dose != null ? dose.getNumeratorUnit() : null),
                Pair.of(MedicationTaskDef.DOSE_DENOMINATOR, dose != null ? dose.getDenominator() : null),
                Pair.of(MedicationTaskDef.DOSE_DENOMINATOR_UNIT, dose != null ? dose.getDenominatorUnit() : null));
    }

    @Override
    public NewTaskRequestDto createTaskRequestFromTaskDto(final TaskDto taskDto) {
        final NewTaskRequestDto newTaskRequestDto = new NewTaskRequestDto(MedicationTaskDef.INSTANCE,
                taskDto.getName(), taskDto.getDisplayName(), taskDto.getDescription(), taskDto.getAssignee(),
                taskDto.getDueTime(), taskDto.getAssociatedEntity());
        for (final String key : taskDto.getVariables().keySet()) {
            newTaskRequestDto.addVariables(Pair.of(TaskVariable.named(key), taskDto.getVariables().get(key)));
        }
        return newTaskRequestDto;
    }
}