
var tz = require('moment-timezone');
var basalprofile = require('../profile/basal.js');
var _ = require('lodash');
var moment = require('moment');

function splitTimespanWithOneSplitter(event,splitter) {

    var resultArray = [event];

    if (splitter.type === 'recurring') {

        var startMinutes = event.started_at.getHours() * 60 + event.started_at.getMinutes();
        var endMinutes = startMinutes + event.duration;

        // 1440 = one day; no clean way to check if the event overlaps midnight
        // so checking if end of event in minutes is past midnight

        if (event.duration > 30 || (startMinutes < splitter.minutes && endMinutes > splitter.minutes) || (endMinutes > 1440 && splitter.minutes < (endMinutes - 1440))) {

            var event1 = _.cloneDeep(event);
            var event2 = _.cloneDeep(event);

            var event1Duration = 0;

            if (event.duration > 30) {
                event1Duration = 30;
            } else {
                var splitPoint = splitter.minutes;
                if (endMinutes > 1440) { splitPoint = 1440; }
                event1Duration = splitPoint - startMinutes;
            }

            var event1EndDate = moment(event.started_at).add(event1Duration,'minutes');

            event1.duration = event1Duration;

            event2.duration  = event.duration - event1Duration;
            event2.timestamp = event1EndDate.format();
            event2.started_at = new Date(event2.timestamp);
            event2.date = event2.started_at.getTime();

            resultArray = [event1,event2];
        }
    }

    return resultArray;
}

function splitTimespan(event, splitterMoments) {

    var results = [event];

    var splitFound = true;

    while(splitFound) {

        var resultArray = [];
        splitFound = false;

        _.forEach(results,function split(o) {
            _.forEach(splitterMoments,function split(p) {
                var splitResult = splitTimespanWithOneSplitter(o,p);
                if (splitResult.length > 1) {
                    resultArray = resultArray.concat(splitResult);
                    splitFound = true;
                    return false;
                }
            });

            if (!splitFound) resultArray = resultArray.concat([o]);

        });

        results = resultArray;
    }

    return results;
}

// Split currentEvent around any conflicting suspends
// by removing the time period from the event that
// overlaps with any suspend.
function splitAroundSuspends (currentEvent, pumpSuspends, firstResumeTime, suspendedPrior, lastSuspendTime, currentlySuspended) {
    var events = [];

    var firstResumeStarted = new Date(firstResumeTime);
    var firstResumeDate = firstResumeStarted.getTime()

    var lastSuspendStarted = new Date(lastSuspendTime);
    var lastSuspendDate = lastSuspendStarted.getTime();

    if (suspendedPrior && (currentEvent.date < firstResumeDate)) {
        if ((currentEvent.date+currentEvent.duration*60*1000) < firstResumeDate) {
            currentEvent.duration = 0;
        } else {
            currentEvent.duration = ((currentEvent.date+currentEvent.duration*60*1000)-firstResumeDate)/60/1000;

            currentEvent.started_at = new Date(tz(firstResumeTime));
            currentEvent.date = firstResumeDate
        }
    }

    if (currentlySuspended && ((currentEvent.date+currentEvent.duration*60*1000) > lastSuspendTime)) {
        if (currentEvent.date > lastSuspendTime) {
            currentEvent.duration = 0;
        } else {
            currentEvent.duration = (firstResumeDate - currentEvent.date)/60/1000;
        }
    }

    events.push(currentEvent);

    if (currentEvent.duration === 0) {
        // bail out rather than wasting time going through the rest of the suspend events
        return events;
    }

    for (var i=0; i < pumpSuspends.length; i++) {
        var suspend = pumpSuspends[i];

        for (var j=0; j < events.length; j++) {

            if ((events[j].date <= suspend.date) && (events[j].date+events[j].duration*60*1000) > suspend.date) {
                // event started before the suspend, but finished after the suspend started

                if ((events[j].date+events[j].duration*60*1000) > (suspend.date+suspend.duration*60*1000)) {
                    var event2 = _.cloneDeep(events[j]);

                    var event2StartDate = moment(suspend.started_at).add(suspend.duration,'minutes');

                    event2.timestamp = event2StartDate.format();
                    event2.started_at = new Date(tz(event2.timestamp));
                    event2.date = suspend.date+suspend.duration*60*1000;

                    event2.duration = ((events[j].date+events[j].duration*60*1000) - (suspend.date+suspend.duration*60*1000))/60/1000;

                    events.push(event2);
                }

                events[j].duration = (suspend.date-events[j].date)/60/1000;

            } else if ((suspend.date <= events[j].date) && (suspend.date+suspend.duration*60*1000 > events[j].date)) {
                // suspend started before the event, but finished after the event started
            
                events[j].duration = ((events[j].date+events[j].duration*60*1000) - (suspend.date+suspend.duration*60*1000))/60/1000;

                var eventStartDate = moment(suspend.started_at).add(suspend.duration,'minutes');

                events[j].timestamp = eventStartDate.format();
                events[j].started_at = new Date(tz(events[j].timestamp));
                events[j].date = suspend.date + suspend.duration*60*1000;
            }
        }
    }

    return events;
}

function calcTempTreatments (inputs, zeroTempDuration) {
    var pumpHistory = inputs.history;
    var pumpHistory24 = inputs.history24;
    var profile_data = inputs.profile;
    var autosens_data = inputs.autosens;
    var tempHistory = [];
    var tempBoluses = [];
    var pumpSuspends = [];
    var pumpResumes = [];
    var suspendedPrior = false;
    var firstResumeTime, lastSuspendTime;
    var currentlySuspended = false;
    var suspendError = false;

    var now = new Date(tz(inputs.clock));

    if(inputs.history24) {
        var pumpHistory =  [ ].concat(inputs.history).concat(inputs.history24);
    }

    var lastRecordTime = now;

    // Gather the times the pump was suspended and resumed
    for (var i=0; i < pumpHistory.length; i++) {
        var temp = {};

        var current = pumpHistory[i];

        if (current._type === "PumpSuspend") {
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(current.timestamp));
            temp.date = temp.started_at.getTime();
            pumpSuspends.push(temp);
        } else if (current._type === "PumpResume") {
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(current.timestamp));
            temp.date = temp.started_at.getTime();
            pumpResumes.push(temp);
        }
    }

    pumpSuspends = _.sortBy(pumpSuspends, 'date');

    pumpResumes = _.sortBy(pumpResumes, 'date');

    if (pumpResumes.length > 0) {
        firstResumeTime = pumpResumes[0].timestamp;

        // Check to see if our first resume was prior to our first suspend
        // indicating suspend was prior to our first event.
        if (pumpSuspends.length === 0 || (pumpResumes[0].date < pumpSuspends[0].date)) {
            suspendedPrior = true;
        }

    }

    var j=0;  // matching pumpResumes entry;

    // Match the resumes with the suspends to get durations
    for (i=0; i < pumpSuspends.length; i++) {
        for (; j < pumpResumes.length; j++) {
            if (pumpResumes[j].date > pumpSuspends[i].date) {
                break;
            }
        }

        if ((j >= pumpResumes.length) && !currentlySuspended) {
            // even though it isn't the last suspend, we have reached
            // the final suspend. Set resume last so the
            // algorithm knows to suspend all the way
            // through the last record beginning at the last suspend
            // since we don't have a matching resume.
            currentlySuspended = 1;
            lastSuspendTime = pumpSuspends[i].timestamp;

            break;
        }

        pumpSuspends[i].duration = (pumpResumes[j].date - pumpSuspends[i].date)/60/1000;

    }

    // These checks indicate something isn't quite aligned.
    // Perhaps more resumes that suspends or vice versa...
    if (!suspendedPrior && !currentlySuspended && (pumpResumes.length !== pumpSuspends.length)) {
        console.error("Mismatched number of resumes("+pumpResumes.length+") and suspends("+pumpSuspends.length+")!");
    } else if (suspendedPrior && !currentlySuspended && ((pumpResumes.length-1) !== pumpSuspends.length)) {
        console.error("Mismatched number of resumes("+pumpResumes.length+") and suspends("+pumpSuspends.length+") assuming suspended prior to history block!");
    } else if (!suspendedPrior && currentlySuspended && (pumpResumes.length !== (pumpSuspends.length-1))) {
        console.error("Mismatched number of resumes("+pumpResumes.length+") and suspends("+pumpSuspends.length+") assuming suspended past end of history block!");
    } else if (suspendedPrior && currentlySuspended && (pumpResumes.length !== pumpSuspends.length)) {
        console.error("Mismatched number of resumes("+pumpResumes.length+") and suspends("+pumpSuspends.length+") assuming suspended prior to and past end of history block!");
    }

    if (i < (pumpSuspends.length-1)) {
        // truncate any extra suspends. if we had any extras
        // the error checks above would have issued a error log message
        pumpSuspends.splice(i+1, pumpSuspends.length-i-1);
    }

    // Pick relevant events for processing and clean the data

    for (i=0; i < pumpHistory.length; i++) {
        var current = pumpHistory[i];
        if (current.bolus && current.bolus._type === "Bolus") {
            var temp = current;
            current = temp.bolus;
        }
        if (current.created_at) {
            current.timestamp = current.created_at;
        }
        var currentRecordTime = new Date(tz(current.timestamp));
        //console.error(current);
        //console.error(currentRecordTime,lastRecordTime);
        // ignore duplicate or out-of-order records (due to 1h and 24h overlap, or timezone changes)
        if (currentRecordTime > lastRecordTime) {
            //console.error("",currentRecordTime," > ",lastRecordTime);
            //process.stderr.write(".");
            continue;
        } else {
            lastRecordTime = currentRecordTime;
        }
        if (current._type === "Bolus") {
            var temp = {};
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(current.timestamp));
            if (temp.started_at > now) {
                //console.error("Warning: ignoring",current.amount,"U bolus in the future at",temp.started_at);
                process.stderr.write(" "+current.amount+"U @ "+temp.started_at);
            } else {
                temp.date = temp.started_at.getTime();
                temp.insulin = current.amount;
                tempBoluses.push(temp);
            }
        } else if (current.eventType === "Meal Bolus" || current.eventType === "Correction Bolus" || current.eventType === "Snack Bolus" || current.eventType === "Bolus Wizard") {
            //imports treatments entered through Nightscout Care Portal
            //"Bolus Wizard" refers to the Nightscout Bolus Wizard, not the Medtronic Bolus Wizard
            var temp = {};
            temp.timestamp = current.created_at;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            temp.insulin = current.insulin;
            tempBoluses.push(temp);
        } else if (current.enteredBy === "xdrip") {
            var temp = {};
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            temp.insulin = current.insulin;
            tempBoluses.push(temp);
        } else if (current.enteredBy ==="HAPP_App" && current.insulin) {
            var temp = {};
            temp.timestamp = current.created_at;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            temp.insulin = current.insulin;
            tempBoluses.push(temp);
        } else if (current.eventType === "Temp Basal" && (current.enteredBy === "HAPP_App" || current.enteredBy === "openaps://AndroidAPS")) {
            var temp = {};
            temp.rate = current.absolute;
            temp.duration = current.duration;
            temp.timestamp = current.created_at;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            tempHistory.push(temp);
        } else if (current.eventType === "Temp Basal") {
            var temp = {};
            temp.rate = current.rate;
            temp.duration = current.duration;
            // Loop reports the amount of insulin actually delivered while the temp basal was running
            // use that to calculate the effective temp basal rate
            if (typeof current.amount !== 'undefined') {
                temp.rate = current.amount / current.duration * 60;
            }
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            tempHistory.push(temp);
        } else if (current._type === "TempBasal") {
            if (current.temp === 'percent') {
                continue;
            }
            var rate = current.rate;
            var timestamp = current.timestamp;
            var duration;
            if (i>0 && pumpHistory[i-1].timestamp === timestamp && pumpHistory[i-1]._type === "TempBasalDuration") {
                duration = pumpHistory[i-1]['duration (min)'];
            } else {
                for (var iter=0; iter < pumpHistory.length; iter++) {
                    if (pumpHistory[iter].timestamp === timestamp && pumpHistory[iter]._type === "TempBasalDuration") {
                            duration = pumpHistory[iter]['duration (min)'];
                            break;
                    }
                }

                if (duration === undefined) {
                    console.error("No duration found for "+rate+" U/hr basal "+timestamp, pumpHistory[i - 1], current, pumpHistory[i + 1]);
                }
            }
            var temp = {};
            temp.rate = rate;
            temp.timestamp = current.timestamp;
            temp.started_at = new Date(tz(temp.timestamp));
            temp.date = temp.started_at.getTime();
            temp.duration = duration;
            tempHistory.push(temp);
        }
        // Add a temp basal cancel event to ignore future temps and reduce predBG oscillation
        var temp = {};
        temp.rate = 0;
        // start the zero temp 1m in the future to avoid clock skew
        temp.started_at = new Date(now.getTime() + (1 * 60 * 1000));
        temp.date = temp.started_at.getTime();
        if (zeroTempDuration) {
            temp.duration = zeroTempDuration;
        } else {
            temp.duration = 0;
        }
        tempHistory.push(temp);
    }

    // Check for overlapping events and adjust event lengths in case of overlap

    tempHistory = _.sortBy(tempHistory, 'date');

    for (i=0; i+1 < tempHistory.length; i++) {
        if (tempHistory[i].date + tempHistory[i].duration*60*1000 > tempHistory[i+1].date) {
            tempHistory[i].duration = (tempHistory[i+1].date - tempHistory[i].date)/60/1000;
            // Delete AndroidAPS "Cancel TBR records" in which duration is not populated
            if (tempHistory[i+1].duration === null) {
                tempHistory.splice(i+1, 1);
            }
        }
    }

    // Create an array of moments to slit the temps by
    // currently supports basal changes

    var splitterEvents = [];

    _.forEach(profile_data.basalprofile,function addSplitter(o) {
        var splitterEvent = {};
        splitterEvent.type = 'recurring';
        splitterEvent.minutes = o.minutes;
        splitterEvents.push(splitterEvent);
    });

    // iterate through the events and split at basal break points if needed

    var splitHistoryByBasal = [];

    _.forEach(tempHistory, function splitEvent(o) {
        splitHistoryByBasal = splitHistoryByBasal.concat(splitTimespan(o,splitterEvents));
    });

    tempHistory = _.sortBy(tempHistory, function(o) { return o.date; });

    var suspend_zeros_iob = false;

    if (typeof profile_data.suspend_zeros_iob !== 'undefined') {
        suspend_zeros_iob = profile_data.suspend_zeros_iob;
    }

    if (suspend_zeros_iob) {
        // iterate through the events and adjust their 
        // times as required to account for pump suspends
        var splitHistory = [];

        _.forEach(splitHistoryByBasal, function splitSuspendEvent(o) {
            var splitEvents = splitAroundSuspends(o, pumpSuspends, firstResumeTime, suspendedPrior, lastSuspendTime, currentlySuspended);
            splitHistory = splitHistory.concat(splitEvents);
        });

        var zTempSuspendBasals = [];

        // Any existing temp basals during times the pump was suspended are now deleted
        // Add 0 temp basals to negate the profile basal rates during times pump is suspended
        _.forEach(pumpSuspends, function createTempBasal(o) {
            var zTempBasal = [{
                _type: 'SuspendBasal',
                rate: 0,
                duration: o.duration,
                date: o.date,
                started_at: o.started_at
            }];
            zTempSuspendBasals = zTempSuspendBasals.concat(zTempBasal);
        });

        // Add temp suspend basal for maximum DIA (8) up to the resume time
        // if there is no matching suspend in the history before the first
        // resume
        var max_dia_ago = now.getTime() - 8*60*60*1000;
        var firstResumeStarted = new Date(firstResumeTime);
        var firstResumeDate = firstResumeStarted.getTime()

        // impact on IOB only matters if the resume occurred
        // after DIA hours before now.
        // otherwise, first resume date can be ignored. Whatever
        // insulin is present prior to resume will be aged
        // out due to DIA.
        if (suspendedPrior && (max_dia_ago < firstResumeDate)) {

            var suspendStart = new Date(max_dia_ago);
            var suspendStartDate = suspendStart.getTime()
            var started_at = new Date(tz(suspendStart.toISOString()));

            var zTempBasal = [{
               // add _type to aid debugging. It isn't used
               // anywhere.
                _type: 'SuspendBasal',
                rate: 0,
                duration: (firstResumeDate - max_dia_ago)/60/1000,
                date: suspendStartDate,
                started_at: started_at
            }];
            zTempSuspendBasals = zTempSuspendBasals.concat(zTempBasal);
        }

        if (currentlySuspended) {
            var suspendStart = new Date(lastSuspendTime);
            var suspendStartDate = suspendStart.getTime()
            var started_at = new Date(tz(suspendStart.toISOString()));

            var zTempBasal = [{
                _type: 'SuspendBasal',
                rate: 0,
                duration: (now - suspendStartDate)/60/1000,
                date: suspendStartDate,
                timestamp: lastSuspendTime,
                started_at: started_at
            }];
            zTempSuspendBasals = zTempSuspendBasals.concat(zTempBasal);
        }

        // Add the new 0 temp basals to the splitHistory.
        // We have to split the new zero temp basals by the profile
        // basals just like the other temp basals.
        _.forEach(zTempSuspendBasals, function splitEvent(o) {
            splitHistory = splitHistory.concat(splitTimespan(o,splitterEvents));
        });
    } else {
        splitHistory = splitHistoryByBasal;
    }

    splitHistory = _.sortBy(splitHistory, function(o) { return o.date; });

    // tempHistory = splitHistory;

    // iterate through the temp basals and create bolus events from temps that affect IOB

    var tempBolusSize;

    for (i=0; i < splitHistory.length; i++) {

        var currentItem = splitHistory[i];

        if (currentItem.duration > 0) {

            var currentRate = profile_data.current_basal;
            if (!_.isEmpty(profile_data.basalprofile)) {
                currentRate = basalprofile.basalLookup(profile_data.basalprofile,new Date(currentItem.timestamp));
            }

            if (typeof profile_data.min_bg !== 'undefined' && typeof profile_data.max_bg !== 'undefined') {
                target_bg = (profile_data.min_bg + profile_data.max_bg) / 2;
            }
            //if (profile_data.temptargetSet && target_bg > 110) {
                //sensitivityRatio = 2/(2+(target_bg-100)/40);
                //currentRate = profile_data.current_basal * sensitivityRatio;
            //}
            var sensitivityRatio;
            var profile = profile_data;
            var normalTarget = 100; // evaluate high/low temptarget against 100, not scheduled basal (which might change)
            if ( profile.half_basal_exercise_target ) {
                var halfBasalTarget = profile.half_basal_exercise_target;
            } else {
                var halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%)
            }
            if ( profile.exercise_mode && profile.temptargetSet && target_bg >= normalTarget + 5 ) {
                // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44
                // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6
                var c = halfBasalTarget - normalTarget;
                sensitivityRatio = c/(c+target_bg-normalTarget);
            } else if (typeof autosens_data !== 'undefined' ) {
                sensitivityRatio = autosens_data.ratio;
                //process.stderr.write("Autosens ratio: "+sensitivityRatio+"; ");
            }
            if ( sensitivityRatio ) {
                currentRate = currentRate * sensitivityRatio;
            }

            var netBasalRate = currentItem.rate - currentRate;
            if (netBasalRate < 0) { tempBolusSize = -0.05; }
            else { tempBolusSize = 0.05; }
            var netBasalAmount = Math.round(netBasalRate*currentItem.duration*10/6)/100
            var tempBolusCount = Math.round(netBasalAmount / tempBolusSize);
            var tempBolusSpacing = currentItem.duration / tempBolusCount;
            for (j=0; j < tempBolusCount; j++) {
                var tempBolus = {};
                tempBolus.insulin = tempBolusSize;
                tempBolus.date = currentItem.date + j * tempBolusSpacing*60*1000;
                tempBolus.created_at = new Date(tempBolus.date);
                tempBoluses.push(tempBolus);
            }
        }
    }
    var all_data =  [ ].concat(tempBoluses).concat(tempHistory);
    all_data = _.sortBy(all_data, 'date');
    return all_data;
}
exports = module.exports = calcTempTreatments;
