package info.nightscout.androidaps.plugins.treatments;

import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.Nullable;

import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.android.apptools.OrmLiteBaseService;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.db.ICallback;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.events.Event;
import info.nightscout.androidaps.events.EventNsTreatment;
import info.nightscout.androidaps.events.EventReloadTreatmentData;
import info.nightscout.androidaps.events.EventTreatmentChange;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData;
import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.JsonHelper;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;


/**
 * Created by mike on 24.09.2017.
 */

public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> {
    private static Logger log = LoggerFactory.getLogger(L.DATATREATMENTS);
    private CompositeDisposable disposable = new CompositeDisposable();

    private static final ScheduledExecutorService treatmentEventWorker = Executors.newSingleThreadScheduledExecutor();
    private static ScheduledFuture<?> scheduledTreatmentEventPost = null;

    public TreatmentService() {
        onCreate();
        dbInitialize();
        disposable.add(RxBus.INSTANCE
                .toObservable(EventNsTreatment.class)
                .observeOn(Schedulers.io())
                .subscribe(event -> {
                    int mode = event.getMode();
                    JSONObject payload = event.getPayload();

                    if (mode == EventNsTreatment.Companion.getADD() || mode == EventNsTreatment.Companion.getUPDATE()) {
                        this.createTreatmentFromJsonIfNotExists(payload);
                    } else { // EventNsTreatment.REMOVE
                        this.deleteNS(payload);
                    }
                }, FabricPrivacy::logException)
        );
    }

    /**
     * This method is a simple re-implementation of the database create and up/downgrade functionality
     * in SQLiteOpenHelper#getDatabaseLocked method.
     * <p>
     * It is implemented to be able to late initialize separate plugins of the application.
     */
    protected void dbInitialize() {
        DatabaseHelper helper = OpenHelperManager.getHelper(this, DatabaseHelper.class);
        int newVersion = helper.getNewVersion();
        int oldVersion = helper.getOldVersion();

        if (oldVersion > newVersion) {
            onDowngrade(this.getConnectionSource(), oldVersion, newVersion);
        } else {
            onUpgrade(this.getConnectionSource(), oldVersion, newVersion);
        }
    }

    public Dao<Treatment, Long> getDao() {
        try {
            return DaoManager.createDao(this.getConnectionSource(), Treatment.class);
        } catch (SQLException e) {
            log.error("Cannot create Dao for Treatment.class");
        }

        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        try {
            if (L.isEnabled(L.DATATREATMENTS))
                log.info("onCreate");
            TableUtils.createTableIfNotExists(this.getConnectionSource(), Treatment.class);
        } catch (SQLException e) {
            log.error("Can't create database", e);
            throw new RuntimeException(e);
        }
    }

    public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int newVersion) {
        if (oldVersion == 7 && newVersion == 8) {
            log.debug("Upgrading database from v7 to v8");
            try {
                TableUtils.dropTable(connectionSource, Treatment.class, true);
                TableUtils.createTableIfNotExists(connectionSource, Treatment.class);
            } catch (SQLException e) {
                log.error("Can't create database", e);
                throw new RuntimeException(e);
            }
        } else if (oldVersion == 8 && newVersion == 9) {
            log.debug("Upgrading database from v8 to v9");
            try {
                getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` ADD COLUMN boluscalc STRING;");
            } catch (SQLException e) {
                log.error("Unhandled exception", e);
            }
        } else {
            if (L.isEnabled(L.DATATREATMENTS))
                log.info("onUpgrade");
//            this.resetFood();
        }
    }

    public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int newVersion) {
        if (oldVersion == 9 && newVersion == 8) {
            try {
                getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` DROP COLUMN boluscalc STRING;");
            } catch (SQLException e) {
                log.error("Unhandled exception", e);
            }
        }
    }

    public void resetTreatments() {
        try {
            TableUtils.dropTable(this.getConnectionSource(), Treatment.class, true);
            TableUtils.createTableIfNotExists(this.getConnectionSource(), Treatment.class);
            DatabaseHelper.updateEarliestDataChange(0);
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        scheduleTreatmentChange(null, true);
    }


    /**
     * A place to centrally register events to be posted, if any data changed.
     * This should be implemented in an abstract service-class.
     * <p>
     * We do need to make sure, that ICallback is extended to be able to handle multiple
     * events, or handle a list of events.
     * <p>
     * on some methods the earliestDataChange event is handled separatly, in that it is checked if it is
     * set to null by another event already (eg. scheduleExtendedBolusChange).
     *
     * @param event
     * @param eventWorker
     * @param callback
     */
    private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker,
                               final ICallback callback) {

        class PostRunnable implements Runnable {
            public void run() {
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("Firing EventReloadTreatmentData");
                RxBus.INSTANCE.send(event);
                if (DatabaseHelper.earliestDataChange != null) {
                    if (L.isEnabled(L.DATATREATMENTS))
                        log.debug("Firing EventNewHistoryData");
                    RxBus.INSTANCE.send(new EventNewHistoryData(DatabaseHelper.earliestDataChange));
                }
                DatabaseHelper.earliestDataChange = null;
                callback.setPost(null);
            }
        }
        // prepare task for execution in 1 sec
        // cancel waiting task to prevent sending multiple posts
        ScheduledFuture<?> scheduledFuture = callback.getPost();
        if (scheduledFuture != null)
            scheduledFuture.cancel(false);
        Runnable task = new PostRunnable();
        final int sec = 1;
        callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS));
    }

    /**
     * Schedule a foodChange Event.
     */
    public void scheduleTreatmentChange(@Nullable final Treatment treatment, boolean runImmediately) {
        if (runImmediately) {
            if (L.isEnabled(L.DATATREATMENTS))
                log.debug("Firing EventReloadTreatmentData");
            RxBus.INSTANCE.send(new EventReloadTreatmentData(new EventTreatmentChange(treatment)));
            if (DatabaseHelper.earliestDataChange != null) {
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("Firing EventNewHistoryData");
                RxBus.INSTANCE.send(new EventNewHistoryData(DatabaseHelper.earliestDataChange));
            }
            DatabaseHelper.earliestDataChange = null;
        } else {
            this.scheduleEvent(new EventReloadTreatmentData(new EventTreatmentChange(treatment)), treatmentEventWorker, new ICallback() {
                @Override
                public void setPost(ScheduledFuture<?> post) {
                    scheduledTreatmentEventPost = post;
                }

                @Override
                public ScheduledFuture<?> getPost() {
                    return scheduledTreatmentEventPost;
                }
            });
        }
    }

    public List<Treatment> getTreatmentData() {
        try {
            return this.getDao().queryForAll();
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }

        return new ArrayList<>();
    }

    /*
    {
        "_id": "551ee3ad368e06e80856e6a9",
        "type": "food",
        "category": "Zakladni",
        "subcategory": "Napoje",
        "name": "Mleko",
        "portion": 250,
        "carbs": 12,
        "gi": 1,
        "created_at": "2015-04-14T06:59:16.500Z",
        "unit": "ml"
    }
     */
    public void createTreatmentFromJsonIfNotExists(JSONObject json) {
        try {
            Treatment treatment = Treatment.createFromJson(json);
            if (treatment != null) {

                if (MedtronicHistoryData.doubleBolusDebug)
                    log.debug("DoubleBolusDebug: createTreatmentFromJsonIfNotExists:: medtronicPump={}", MedtronicUtil.isMedtronicPump());

                if (!MedtronicUtil.isMedtronicPump())
                    createOrUpdate(treatment);
                else
                    createOrUpdateMedtronic(treatment, true);
            } else
                log.error("Date is null: " + treatment.toString());
        } catch (JSONException e) {
            log.error("Unhandled exception", e);
        }
    }


    // return true if new record is created
    public UpdateReturn createOrUpdate(Treatment treatment) {
        if (treatment != null && treatment.source == Source.NONE) {
            log.error("Coder error: source is not set for treatment: " + treatment, new Exception());
            FabricPrivacy.logException(new Exception("Coder error: source is not set for treatment: " + treatment));
        }
        try {
            Treatment old;
            treatment.date = DatabaseHelper.roundDateToSec(treatment.date);

            if (treatment.source == Source.PUMP) {
                // check for changed from pump change in NS
                Treatment existingTreatment = getPumpRecordById(treatment.pumpId);
                if (existingTreatment != null) {
                    boolean equalRePumpHistory = existingTreatment.equalsRePumpHistory(treatment);
                    boolean sameSource = existingTreatment.source == treatment.source;
                    if (!equalRePumpHistory) {
                        // another treatment exists. Update it with the treatment coming from the pump
                        if (L.isEnabled(L.DATATREATMENTS))
                            log.debug("Pump record already found in database: " + existingTreatment.toString() + " wanting to add " + treatment.toString());
                        long oldDate = existingTreatment.date;

                        //preserve carbs
                        if (existingTreatment.isValid && existingTreatment.carbs > 0 && treatment.carbs == 0) {
                            treatment.carbs = existingTreatment.carbs;
                        }

                        getDao().delete(existingTreatment); // need to delete/create because date may change too
                        existingTreatment.copyBasics(treatment);
                        getDao().create(existingTreatment);
                        DatabaseHelper.updateEarliestDataChange(oldDate);
                        DatabaseHelper.updateEarliestDataChange(existingTreatment.date);
                        scheduleTreatmentChange(treatment, true);
                        return new UpdateReturn(sameSource, false); //updating a pump treatment with another one from the pump is not counted as clash
                    }
                    return new UpdateReturn(equalRePumpHistory, false);
                }
                existingTreatment = getDao().queryForId(treatment.date);
                if (existingTreatment != null) {
                    // another treatment exists with different pumpID. Update it with the treatment coming from the pump
                    boolean equalRePumpHistory = existingTreatment.equalsRePumpHistory(treatment);
                    boolean sameSource = existingTreatment.source == treatment.source;
                    long oldDate = existingTreatment.date;
                    if (L.isEnabled(L.DATATREATMENTS))
                        log.debug("Pump record already found in database: " + existingTreatment.toString() + " wanting to add " + treatment.toString());

                    //preserve carbs
                    if (existingTreatment.isValid && existingTreatment.carbs > 0 && treatment.carbs == 0) {
                        treatment.carbs = existingTreatment.carbs;
                    }

                    getDao().delete(existingTreatment); // need to delete/create because date may change too
                    existingTreatment.copyFrom(treatment);
                    getDao().create(existingTreatment);
                    DatabaseHelper.updateEarliestDataChange(oldDate);
                    DatabaseHelper.updateEarliestDataChange(existingTreatment.date);
                    scheduleTreatmentChange(treatment, true);
                    return new UpdateReturn(equalRePumpHistory || sameSource, false);
                }
                getDao().create(treatment);
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("New record from: " + Source.getString(treatment.source) + " " + treatment.toString());
                DatabaseHelper.updateEarliestDataChange(treatment.date);
                scheduleTreatmentChange(treatment, true);
                return new UpdateReturn(true, true);
            }
            if (treatment.source == Source.NIGHTSCOUT) {
                old = getDao().queryForId(treatment.date);
                if (old != null) {
                    if (!old.isEqual(treatment)) {
                        boolean historyChange = old.isDataChanging(treatment);
                        long oldDate = old.date;
                        getDao().delete(old); // need to delete/create because date may change too
                        old.copyFrom(treatment);
                        getDao().create(old);
                        if (L.isEnabled(L.DATATREATMENTS))
                            log.debug("Updating record by date from: " + Source.getString(treatment.source) + " " + old.toString());
                        if (historyChange) {
                            DatabaseHelper.updateEarliestDataChange(oldDate);
                            DatabaseHelper.updateEarliestDataChange(old.date);
                        }
                        scheduleTreatmentChange(treatment, false);
                        return new UpdateReturn(true, true);
                    }
                    if (L.isEnabled(L.DATATREATMENTS))
                        log.debug("Equal record by date from: " + Source.getString(treatment.source) + " " + old.toString());
                    return new UpdateReturn(true, false);
                }
                // find by NS _id
                if (treatment._id != null) {
                    old = findByNSId(treatment._id);
                    if (old != null) {
                        if (!old.isEqual(treatment)) {
                            boolean historyChange = old.isDataChanging(treatment);
                            long oldDate = old.date;
                            getDao().delete(old); // need to delete/create because date may change too
                            old.copyFrom(treatment);
                            getDao().create(old);
                            if (L.isEnabled(L.DATATREATMENTS))
                                log.debug("Updating record by _id from: " + Source.getString(treatment.source) + " " + old.toString());
                            if (historyChange) {
                                DatabaseHelper.updateEarliestDataChange(oldDate);
                                DatabaseHelper.updateEarliestDataChange(old.date);
                            }
                            scheduleTreatmentChange(treatment, false);
                            return new UpdateReturn(true, true);
                        }
                        if (L.isEnabled(L.DATATREATMENTS))
                            log.debug("Equal record by _id from: " + Source.getString(treatment.source) + " " + old.toString());
                        return new UpdateReturn(true, false);
                    }
                }
                getDao().create(treatment);
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("New record from: " + Source.getString(treatment.source) + " " + treatment.toString());
                DatabaseHelper.updateEarliestDataChange(treatment.date);
                scheduleTreatmentChange(treatment, false);
                return new UpdateReturn(true, true);
            }
            if (treatment.source == Source.USER) {
                getDao().create(treatment);
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("New record from: " + Source.getString(treatment.source) + " " + treatment.toString());
                DatabaseHelper.updateEarliestDataChange(treatment.date);
                scheduleTreatmentChange(treatment, true);
                return new UpdateReturn(true, true);
            }
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        return new UpdateReturn(false, false);
    }


    public UpdateReturn createOrUpdateMedtronic(Treatment treatment, boolean fromNightScout) {

        if (MedtronicHistoryData.doubleBolusDebug)
            log.debug("DoubleBolusDebug: createOrUpdateMedtronic:: originalTreatment={}, fromNightScout={}", treatment, fromNightScout);

        try {
            treatment.date = DatabaseHelper.roundDateToSec(treatment.date);

            Treatment existingTreatment = getRecord(treatment.pumpId, treatment.date);

            if (MedtronicHistoryData.doubleBolusDebug)
                log.debug("DoubleBolusDebug: createOrUpdateMedtronic:: existingTreatment={}", treatment);

            if (existingTreatment == null) {
                getDao().create(treatment);
                if (L.isEnabled(L.DATATREATMENTS))
                    log.debug("New record from: " + Source.getString(treatment.source) + " " + treatment.toString());
                DatabaseHelper.updateEarliestDataChange(treatment.date);
                scheduleTreatmentChange(treatment, true);
                return new UpdateReturn(true, true);
            } else {

                if (existingTreatment.date == treatment.date) {
                    if (MedtronicHistoryData.doubleBolusDebug)
                        log.debug("DoubleBolusDebug: createOrUpdateMedtronic::(existingTreatment.date==treatment.date)");

                    // we will do update only, if entry changed
                    if (!optionalTreatmentCopy(existingTreatment, treatment, fromNightScout)) {
                        return new UpdateReturn(true, false);
                    }
                    getDao().update(existingTreatment);
                    DatabaseHelper.updateEarliestDataChange(existingTreatment.date);
                    scheduleTreatmentChange(treatment, true);
                    return new UpdateReturn(true, false);
                } else {
                    if (MedtronicHistoryData.doubleBolusDebug)
                        log.debug("DoubleBolusDebug: createOrUpdateMedtronic::(existingTreatment.date != treatment.date)");

                    // date is different, we need to remove entry
                    getDao().delete(existingTreatment);
                    optionalTreatmentCopy(existingTreatment, treatment, fromNightScout);
                    getDao().create(existingTreatment);
                    DatabaseHelper.updateEarliestDataChange(existingTreatment.date);
                    scheduleTreatmentChange(treatment, true);
                    return new UpdateReturn(true, false); //updating a pump treatment with another one from the pump is not counted as clash
                }
            }

        } catch (SQLException e) {
            log.error("Unhandled SQL exception: {}", e.getMessage(), e);
        }
        return new UpdateReturn(false, false);
    }


    private boolean optionalTreatmentCopy(Treatment oldTreatment, Treatment newTreatment, boolean fromNightScout) {

        log.debug("optionalTreatmentCopy [old={}, new={}]", oldTreatment.toString(), newTreatment.toString());

        boolean changed = false;

        if (oldTreatment.date != newTreatment.date) {
            oldTreatment.date = newTreatment.date;
            changed = true;
        }

        if (oldTreatment.isSMB != newTreatment.isSMB) {
            if (!oldTreatment.isSMB) {
                oldTreatment.isSMB = newTreatment.isSMB;
                changed = true;
            }
        }

        if (!isSame(oldTreatment.carbs, newTreatment.carbs)) {
            if (isSame(oldTreatment.carbs, 0.0d)) {
                oldTreatment.carbs = newTreatment.carbs;
                changed = true;
            }
        }

        if (oldTreatment.mealBolus != (oldTreatment.carbs > 0)) {
            oldTreatment.mealBolus = (oldTreatment.carbs > 0);
            changed = true;
        }

        if (!isSame(oldTreatment.insulin, newTreatment.insulin)) {
            if (!fromNightScout) {
                oldTreatment.insulin = newTreatment.insulin;
                changed = true;
            }
        }

        if (!StringUtils.equals(oldTreatment._id, newTreatment._id)) {
            if (StringUtils.isBlank(oldTreatment._id)) {
                oldTreatment._id = newTreatment._id;
                changed = true;
            }
        }

        int source = Source.NONE;

        if (oldTreatment.pumpId == 0) {
            if (newTreatment.pumpId > 0) {
                oldTreatment.pumpId = newTreatment.pumpId;
                source = Source.PUMP;
                changed = true;
            }
        }

        if (source == Source.NONE) {

            if (oldTreatment.source == newTreatment.source) {
                source = oldTreatment.source;
            } else {
                source = (oldTreatment.source == Source.NIGHTSCOUT || newTreatment.source == Source.NIGHTSCOUT) ? Source.NIGHTSCOUT : Source.USER;
            }
        }

        if (oldTreatment.source != source) {
            oldTreatment.source = source;
            changed = true;
        }

        log.debug("optionalTreatmentCopy [changed={}, newAfterChange={}]", changed, oldTreatment.toString());
        return changed;
    }


    public static boolean isSame(Double d1, Double d2) {
        double diff = d1 - d2;

        return (Math.abs(diff) <= 0.00001);
    }

    @Deprecated
    private void treatmentCopy(Treatment oldTreatment, Treatment newTreatment, boolean fromNightScout) {

        log.debug("treatmentCopy [old={}, new={}]", oldTreatment.toString(), newTreatment.toString());


        if (fromNightScout) {
            long pumpId_old = oldTreatment.pumpId;
            boolean isSMB = (oldTreatment.isSMB || newTreatment.isSMB);

            oldTreatment.copyFrom(newTreatment);

            if (pumpId_old != 0) {
                oldTreatment.pumpId = pumpId_old;
            }

            if (oldTreatment.pumpId != 0 && oldTreatment.source != Source.PUMP) {
                oldTreatment.source = Source.PUMP;
            }

            oldTreatment.isSMB = isSMB;

        } else {
            oldTreatment.copyFrom(newTreatment);
        }

        log.debug("treatmentCopy [newAfterChange={}]", oldTreatment.toString());

    }


    public Treatment getRecord(long pumpId, long date) {

        Treatment record = null;

        if (pumpId > 0) {

            record = getPumpRecordById(pumpId);

            if (record != null) {
                return record;
            }
        }

        try {
            record = getDao().queryForId(date);
        } catch (SQLException ex) {
            log.error("Error getting entry by id ({}", date);
        }

        return record;

    }


    /**
     * Returns the record for the given id, null if none, throws RuntimeException
     * if multiple records with the same pump id exist.
     */
    @Nullable
    public Treatment getPumpRecordById(long pumpId) {
        try {
            QueryBuilder<Treatment, Long> queryBuilder = getDao().queryBuilder();
            Where where = queryBuilder.where();
            where.eq("pumpId", pumpId);
            queryBuilder.orderBy("date", true);

            List<Treatment> result = getDao().query(queryBuilder.prepare());
            if (result.isEmpty())
                return null;
            if (result.size() > 1)
                log.warn("Multiple records with the same pump id found (returning first one): " + result.toString());
            return result.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteNS(JSONObject json) {
        String _id = JsonHelper.safeGetString(json, "_id");
        if (_id != null && !_id.isEmpty())
            this.deleteByNSId(_id);
    }

    /**
     * deletes an entry by its NS Id.
     * <p>
     * Basically a convenience method for findByNSId and delete.
     *
     * @param _id
     */
    private void deleteByNSId(String _id) {
        Treatment stored = findByNSId(_id);
        if (stored != null) {
            if (L.isEnabled(L.DATATREATMENTS))
                log.debug("Removing Treatment record from database: " + stored.toString());
            try {
                getDao().delete(stored);
            } catch (SQLException e) {
                log.error("Unhandled exception", e);
            }
            DatabaseHelper.updateEarliestDataChange(stored.date);
            this.scheduleTreatmentChange(stored, false);
        }
    }

    /**
     * deletes the treatment and sends the treatmentChange Event
     * <p>
     * should be moved ot a Service
     *
     * @param treatment
     */
    public void delete(Treatment treatment) {
        try {
            getDao().delete(treatment);
            DatabaseHelper.updateEarliestDataChange(treatment.date);
            this.scheduleTreatmentChange(treatment, true);
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
    }

    public void update(Treatment treatment) {
        try {
            getDao().update(treatment);
            DatabaseHelper.updateEarliestDataChange(treatment.date);
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        scheduleTreatmentChange(treatment, true);
    }

    /**
     * finds treatment by its NS Id.
     *
     * @param _id
     * @return
     */
    @Nullable
    public Treatment findByNSId(String _id) {
        try {
            Dao<Treatment, Long> daoTreatments = getDao();
            QueryBuilder<Treatment, Long> queryBuilder = daoTreatments.queryBuilder();
            Where where = queryBuilder.where();
            where.eq("_id", _id);
            queryBuilder.limit(10L);
            PreparedQuery<Treatment> preparedQuery = queryBuilder.prepare();
            List<Treatment> trList = daoTreatments.query(preparedQuery);
            if (trList.size() != 1) {
                //log.debug("Treatment findTreatmentById query size: " + trList.size());
                return null;
            } else {
                //log.debug("Treatment findTreatmentById found: " + trList.get(0).log());
                return trList.get(0);
            }
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        return null;
    }

    public List<Treatment> getTreatmentDataFromTime(long mills, boolean ascending) {
        try {
            Dao<Treatment, Long> daoTreatments = getDao();
            List<Treatment> treatments;
            QueryBuilder<Treatment, Long> queryBuilder = daoTreatments.queryBuilder();
            queryBuilder.orderBy("date", ascending);
            Where where = queryBuilder.where();
            where.ge("date", mills);
            PreparedQuery<Treatment> preparedQuery = queryBuilder.prepare();
            treatments = daoTreatments.query(preparedQuery);
            return treatments;
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        return new ArrayList<>();
    }

    public List<Treatment> getTreatmentDataFromTime(long from, long to, boolean ascending) {
        try {
            Dao<Treatment, Long> daoTreatments = getDao();
            List<Treatment> treatments;
            QueryBuilder<Treatment, Long> queryBuilder = daoTreatments.queryBuilder();
            queryBuilder.orderBy("date", ascending);
            Where where = queryBuilder.where();
            where.between("date", from, to);
            PreparedQuery<Treatment> preparedQuery = queryBuilder.prepare();
            treatments = daoTreatments.query(preparedQuery);
            return treatments;
        } catch (SQLException e) {
            log.error("Unhandled exception", e);
        }
        return new ArrayList<>();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public class UpdateReturn {
        public UpdateReturn(boolean success, boolean newRecord) {
            this.success = success;
            this.newRecord = newRecord;
        }

        boolean newRecord;
        boolean success;

        @Override
        public String toString() {
            return "UpdateReturn [" +
                    "newRecord=" + newRecord +
                    ", success=" + success +
                    ']';
        }
    }

}